package main import ( "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/link" ) // The list of predefined keys (novox/hq ADR 0348 ยง7, design 57 package K2, issue 661): `module check` and the // merge gate judge each manifest against the list at the root of its tree; registration publishes the newest // trunk build's list of the catalogue and judges every module against the list the mesh publishes. const theFirstKeys = `{ "font-size": {"type": "number", "description": "the size text is shown in, in points", "minimum": 6, "maximum": 72}, "agent-managed-settings": {"type": "object", "description": "the managed settings of every agent session, a JSON object", "trusted": true} }` // notifierFallingBackTo is the notifier in the config form, its font size falling back to the key named. func notifierFallingBackTo(key, kind string) string { def := "10" if kind == "string" { def = `"10"` } return `{"module":"notifier","version":"1", "config":{"font-size":{"type":"` + kind + `","description":"the notification's font size, in points", "default":` + def + `,"predefined":"` + key + `"}}, "resources":[{"id":"conf","type":"file","path":"/etc/notifier/notifier.conf","mode":"0644", "content":"font = ${config:font-size}\n"}]}` } // withoutAPublishedList judges the test against no list the mesh published, and puts the process's back after. func withoutAPublishedList(t *testing.T) { t.Helper() was := catalogue.ReplacePublished(nil) t.Cleanup(func() { catalogue.ReplacePublished(was) }) } // aRepositoryTree is a repository's checkout holding one module at modules/notifier, and the list when given. func aRepositoryTree(t *testing.T, manifest string, list *string) string { t.Helper() root := t.TempDir() if err := os.Mkdir(filepath.Join(root, ".git"), 0o755); err != nil { t.Fatal(err) } if list != nil { if err := os.WriteFile(filepath.Join(root, catalogue.PredefinedFile), []byte(*list), 0o644); err != nil { t.Fatal(err) } } return aTemplatedModule(t, filepath.Join(root, "modules", "notifier"), manifest, nil) } func TestModuleCheckJudgesAManifestAgainstTheListAtItsRepositoryRoot(t *testing.T) { withoutAPublishedList(t) list := theFirstKeys check := func(path string) (string, error) { var out bytes.Buffer err := moduleCheck([]string{path}, &out) return out.String(), err } if out, err := check(aRepositoryTree(t, notifierFallingBackTo("font-size", "number"), &list)); err != nil || !strings.Contains(out, "notifier: ok") { t.Fatalf("a module naming font-size was refused: %v\n%s", err, out) } for _, c := range []struct { manifest string list *string says string }{ {notifierFallingBackTo("font-scale", "number"), &list, `"font-scale", which is not on the list`}, {notifierFallingBackTo("font-size", "string"), &list, "which is a number, and this value is a string"}, {notifierFallingBackTo("font-size", "number"), nil, "is not present"}, {notifierFallingBackTo("font-size", "number"), ptr(`{"font-size": {"type": "number"}}`), "no description"}, } { if out, err := check(aRepositoryTree(t, c.manifest, c.list)); err == nil || !strings.Contains(out, c.says) { t.Errorf("wanted the check refused saying %q, got %v:\n%s", c.says, err, out) } } } func ptr(s string) *string { return &s } // writeTheList puts the list at the root of a tree the gate judges. func writeTheList(t *testing.T, tree, list string) string { t.Helper() if err := os.WriteFile(filepath.Join(tree, catalogue.PredefinedFile), []byte(list), 0o644); err != nil { t.Fatal(err) } return tree } // catalogueBuild is a trunk build of the catalogue carrying the list at a generation. func catalogueBuild(id, manifest, list string, generation int) link.BuildResult { r := link.BuildResult{ID: id, Repository: "http://forge.internal:20000/novox/mesh-catalog.git", Path: "modules/notifier", Ref: "main", On: "anchor", Commit: strings.Repeat(id[len(id)-1:], 12), Manifest: json.RawMessage(manifest), Source: &link.SourceOnSeat{Seat: "git", Repository: "novox/mesh-catalog"}} if list != "" { r.Predefined = &link.PredefinedList{Keys: json.RawMessage(list), Generation: generation} } return r }