package packaging_test import ( "encoding/json" "os" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // The operator's public keys reach every node as a file beside the state file, fed by a setting of the node-engine // (novox/hq ADR 0539 clauses 3 and 11): the node agent reads it, and a composition made by hand is applied only when one // of these keys signed it. The setting may be empty, and then no operator key is trusted. func TestTheManifestDeclaresTheOperatorKeysFileFedByASetting(t *testing.T) { raw, err := os.ReadFile("../module.json") if err != nil { t.Fatal(err) } if _, err := catalogue.ParseManifest(raw); err != nil { t.Fatalf("the manifest no longer parses: %v", err) } var m struct { Config map[string]struct { Type string `json:"type"` Default any `json:"default"` } `json:"config"` Resources []struct { ID string `json:"id"` Type string `json:"type"` Path string `json:"path"` Mode string `json:"mode"` Content string `json:"content"` } `json:"resources"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } setting, ok := m.Config["operator-signers"] if !ok || setting.Type != "string" || setting.Default != "" { t.Fatalf("the setting operator-signers is %+v: a string that may be empty", setting) } for _, r := range m.Resources { if r.ID != "operator-keys" { continue } if r.Type != "file" || r.Path != "/var/lib/mesh-host/operator-keys" || r.Mode != "0644" || !strings.Contains(r.Content, "${config:operator-signers}") { t.Fatalf("the file is %+v: it is declared beside the state file, 0644, its content the setting", r) } return } t.Fatal("module.json declares no operator-keys file") } // The node agent hears the end of a backup on the event of node-backup's holder, so the manifest consumes it. func TestTheManifestConsumesTheBackupEvent(t *testing.T) { raw, err := os.ReadFile("../module.json") if err != nil { t.Fatal(err) } var m struct { Consumes []string `json:"consumes"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } if len(m.Consumes) != 1 || m.Consumes[0] != "restic.backed-up" { t.Fatalf("consumes %v", m.Consumes) } // 774 (ADR 0563 §2, §3): the acts of the node seat and the engine's own asks are its declared state acts, the bucket // mesh-host_acts the grants part creates and each node's engine writes under its own keys. parsed, err := catalogue.ParseManifest(raw) if err != nil { t.Fatal(err) } // 774 (the alert table): beside it the state health, one key per node, the node's own health report, kept until // written again (no TTL: a node that stops writing keeps its last word, which the watchman reads with its time). if len(parsed.State) != 2 || parsed.State[0].Name != "acts" || parsed.State[0].TTLSeconds != 90*24*60*60 || parsed.State[1].Name != "health" || parsed.State[1].TTLSeconds != 0 { t.Fatalf("the manifest declares the state %+v", parsed.State) } }