package catalogue import ( "bytes" "encoding/json" "strings" "testing" ) // A module declares its config (novox/hq ADR 0348 §1, §4, §5, §6, §8, §11; design 57 package K1, issue 660): // one registration refusal per rule of design 57's K1, the old names read as aliases, and a module rewritten // from settings to config composing byte-identically. // inConfigForm is a module in the config form that registers: every value typed, described, defaulted or // required, placed in a file or read at runtime, and its mergeable file naming its keys. func inConfigForm() map[string]any { return map[string]any{ "module": "notifier", "version": "1", "config": map[string]any{ "font-size": map[string]any{"type": "number", "description": "the notification's font size, in points", "minimum": 6, "maximum": 40, "default": 10}, "width": map[string]any{"type": "number", "description": "the notification's width, in pixels", "default": 250}, "position": map[string]any{"type": "string", "description": "the corner it shows in, a word", "enum": []any{"top-right", "top-left"}, "default": "top-right"}, "theme": map[string]any{"type": "string", "description": "the colour theme, a lower-case name", "pattern": "[a-z]+", "default": "dark"}, "history": map[string]any{"type": "number", "description": "how many notifications it keeps, a count", "default": 20, "runtime": true}, "sender": map[string]any{"type": "string", "description": "the address it mails a digest from", "required": true}, }, "resources": []any{ map[string]any{"id": "configuration", "type": "file", "path": "/x/notifierrc", "content": "font = Inter ${config:font-size}\nwidth = ${config:width}\nfrom = ${config:sender}\n"}, map[string]any{"id": "rules", "type": "file", "path": "/x/rules.json", "merge": "json", "takes-config": []any{"position", "theme"}, "content": "{\"position\": \"top-right\"}\n"}, map[string]any{"id": "notifier", "type": "service", "unit": "notifier.service", "reload-on": []any{"configuration"}, "restart-on": []any{"rules"}}, }, } } // declaration is one value of inConfigForm's config, to change in place. func declaration(m map[string]any, key string) map[string]any { return m["config"].(map[string]any)[key].(map[string]any) } func resourceOf(m map[string]any, id string) map[string]any { for _, r := range m["resources"].([]any) { if r.(map[string]any)["id"] == id { return r.(map[string]any) } } return nil } func parseRaw(t *testing.T, m map[string]any) (Manifest, error) { t.Helper() raw, err := json.Marshal(m) if err != nil { t.Fatal(err) } return decodeForTest(raw) } // refused is that the manifest is refused with every one of these words. func refused(t *testing.T, m map[string]any, words ...string) { t.Helper() _, err := parseRaw(t, m) if err == nil { t.Fatalf("registered; wanted a refusal saying %q", words) } for _, w := range words { if !strings.Contains(err.Error(), w) { t.Fatalf("refused without %q:\n%v", w, err) } } } func TestAModuleInTheConfigFormRegisters(t *testing.T) { m, err := parseRaw(t, inConfigForm()) if err != nil { t.Fatal(err) } if !m.DeclaresConfig() { t.Fatal("a module declaring config is not read as in the config form") } d := m.Settings["font-size"] if d.Type != "number" || d.Description == "" || d.Minimum == nil || *d.Minimum != 6 || d.Default != float64(10) { t.Fatalf("font-size read as %+v", d) } if s := m.Settings["sender"]; !s.Required || s.Default != nil { t.Fatalf("sender read as %+v", s) } } // withPredefined is the list of predefined keys the mesh published, for the length of a test. func withPredefined(t *testing.T, keys PredefinedList) { t.Helper() was := ReplacePublished(keys) t.Cleanup(func() { ReplacePublished(was) }) } // What the mesh holds is what was written: a module in the config form is written back in it, and read again // the same. func TestAConfigManifestIsWrittenBackInItsOwnForm(t *testing.T) { m, err := parseRaw(t, inConfigForm()) if err != nil { t.Fatal(err) } raw, err := json.Marshal(m) if err != nil { t.Fatal(err) } for _, said := range []string{`"config":`, `${config:font-size}`, `"takes-config":`, `"required":true`} { if !strings.Contains(string(raw), said) { t.Fatalf("written back without %s: %s", said, raw) } } for _, unsaid := range []string{`"settings":`, `${setting:`, `"takes-settings"`, `"kind"`, `"why"`, `"default":null`} { if strings.Contains(string(raw), unsaid) { t.Fatalf("written back with %s: %s", unsaid, raw) } } again, err := decodeForTest(raw) if err != nil { t.Fatal(err) } twice, _ := json.Marshal(again) if !bytes.Equal(raw, twice) { t.Fatalf("read again it is written\n%s\nwhere it was\n%s", twice, raw) } // The registry milestone's words round-trip the same (design 32): a config value's old name, an artifact's series // and contracts marker, the series a module accepts of a package; absent, a series is 0.1. declared := []byte(`{"module":"c","version":"1","config":{"font":{"type":"string","renamed-from":"font-name"}}, "packages":{"mesh-sdk":"0.2"}, "build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"],"series":"0.2", "generated-from":"seats"},{"name":"lib","kind":"package","language":"typescript"}]}}`) m, err = decodeForTest(declared) if err != nil { t.Fatal(err) } if m.Settings["font"].RenamedFrom != "font-name" || m.AcceptedSeries("mesh-sdk") != "0.2" || m.AcceptedSeries("mesh-contracts") != DefaultSeries || m.Build.Artifacts[0].SeriesOf() != "0.2" || m.Build.Artifacts[0].GeneratedFrom != "seats" || m.Build.Artifacts[1].SeriesOf() != "0.1" { t.Fatalf("read %+v", m) } raw, _ = json.Marshal(m) for _, said := range []string{`"renamed-from":"font-name"`, `"packages":{"mesh-sdk":"0.2"}`, `"series":"0.2"`, `"generated-from":"seats"`} { if !strings.Contains(string(raw), said) { t.Errorf("written back without %s: %s", said, raw) } } if strings.Count(string(raw), `"series"`) != 1 { t.Errorf("a series nobody said is written: %s", raw) } } // A module in the settings form is written back byte for byte as before. func TestASettingsManifestIsWrittenBackAsBefore(t *testing.T) { d := SettingDeclaration{Kind: KindPreference, Default: float64(10), Why: "readable"} raw, err := json.Marshal(d) if err != nil { t.Fatal(err) } if string(raw) != `{"kind":"preference","default":10,"why":"readable"}` { t.Fatalf("written as %s", raw) } } // 1658/AC1: a value whose only placement is ${config:key} in an item's text is placed. func TestConfigCountsAnItemsTextAsAPlacement(t *testing.T) { m := map[string]any{ "module": "m", "version": "1", "config": map[string]any{ "k": map[string]any{"type": "string", "description": "a word the item says", "default": "x"}, }, "items": []any{map[string]any{"collection": "a", "content": "k = ${config:k}\n"}}, } if _, err := parseRaw(t, m); err != nil { t.Fatalf("a value placed only in an item's text is refused: %v", err) } }