The controller only learns manifests through builds, so the list of predefined keys travels the same way: the builder reads it at the catalogue's root, judges the manifest against it and hands it on.
118 lines
5.8 KiB
Go
118 lines
5.8 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The list of predefined keys is read strictly: a type and a why for each key (novox/hq issue 630).
|
|
func TestThePredefinedListIsReadStrictly(t *testing.T) {
|
|
list, err := ParsePredefined([]byte(`{"font-size": {"type": "number", "why": "the size text is drawn at"},
|
|
"theme": {"type": "string", "why": "light or dark"}}`))
|
|
if err != nil || len(list) != 2 || list["font-size"].Type != TypeNumber {
|
|
t.Fatalf("a good list: %v %v", list, err)
|
|
}
|
|
for raw, want := range map[string]string{
|
|
`{"font-size": {"type": "integer", "why": "x"}}`: `the type "integer"`,
|
|
`{"font-size": {"type": "number"}}`: "no why",
|
|
`{"Font Size": {"type": "number", "why": "x"}}`: "not a usable key",
|
|
`{"font-size": {"type": "number", "why": "x", "a": 1}}`: "unknown field",
|
|
`{"ports": {"type": "number", "why": "x"}}`: "the mesh reads itself",
|
|
} {
|
|
if _, err := ParsePredefined([]byte(raw)); err == nil || !strings.Contains(err.Error(), want) {
|
|
t.Errorf("%s: %v, want %q", raw, err, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A manifest declares preferences with a type, a default of that type, a why and maybe a predefined key;
|
|
// a `settings` entry of kind preference is read as one, its type from its default.
|
|
func TestAManifestDeclaresPreferences(t *testing.T) {
|
|
file := `"resources": [{"id": "rc", "type": "file", "path": "/etc/rc", "mode": "0644", "trusted": false,
|
|
"content": "size = ${setting:font-size} width = ${setting:width}"}]`
|
|
m, err := ParseManifest([]byte(`{"module": "dunst", "version": "1",
|
|
"preferences": {"font-size": {"type": "number", "default": 10, "predefined": "font-size", "why": "100 DPI"}},
|
|
"settings": {"width": {"kind": "preference", "default": 250, "why": "forty characters"}}, ` + file + `}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prefs := PreferencesOf(m)
|
|
if prefs["font-size"].Predefined != "font-size" || prefs["width"].Type != TypeNumber || prefs["width"].Default != float64(250) {
|
|
t.Fatalf("the preferences: %+v", prefs)
|
|
}
|
|
if got := PredefinedNamed(m); len(got) != 1 || got[0] != "font-size" {
|
|
t.Fatalf("the predefined keys named: %v", got)
|
|
}
|
|
for body, want := range map[string]string{
|
|
`"preferences": {"font-size": {"type": "number", "default": "10", "why": "x"}, "width": {"type": "number", "default": 1, "why": "x"}}`: `its default "10" is a string`,
|
|
`"preferences": {"font-size": {"type": "float", "default": 10, "why": "x"}, "width": {"type": "number", "default": 1, "why": "x"}}`: `the type is "float"`,
|
|
`"preferences": {"font-size": {"type": "number", "why": "x"}, "width": {"type": "number", "default": 1, "why": "x"}}`: "no default",
|
|
`"preferences": {"font-size": {"type": "number", "default": 10}, "width": {"type": "number", "default": 1, "why": "x"}}`: "no why",
|
|
} {
|
|
if _, err := ParseManifest([]byte(`{"module": "dunst", "version": "1", ` + body + `, ` + file + `}`)); err == nil ||
|
|
!strings.Contains(err.Error(), want) {
|
|
t.Errorf("%s: %v, want %q", body, err, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A preference falls back only to a key on the list, with the list's type.
|
|
func TestAPreferencesPredefinedKeyIsOnTheListWithItsType(t *testing.T) {
|
|
m := Manifest{Module: "dunst", Preferences: map[string]Preference{
|
|
"font-size": {Type: TypeNumber, Default: float64(10), Predefined: "font-size", Why: "x"}}}
|
|
if p := PredefinedProblems(m, Predefined{"font-size": {Type: TypeNumber, Why: "x"}}); len(p) != 0 {
|
|
t.Fatalf("on the list: %v", p)
|
|
}
|
|
if p := PredefinedProblems(m, Predefined{"theme": {Type: TypeString, Why: "x"}}); len(p) != 1 ||
|
|
!strings.Contains(p[0], "not on the catalogue's list") || !strings.Contains(p[0], "theme") {
|
|
t.Fatalf("not on the list: %v", p)
|
|
}
|
|
if p := PredefinedProblems(m, Predefined{"font-size": {Type: TypeString, Why: "x"}}); len(p) != 1 ||
|
|
!strings.Contains(p[0], "is a string") {
|
|
t.Fatalf("another type: %v", p)
|
|
}
|
|
}
|
|
|
|
// Mesh and node config fill `${setting:<key>}` between the default and the module's own layer, and — like
|
|
// the default — never reach a mergeable file.
|
|
func TestConfigLayersFillAPlaceholderAndNoMergeableFile(t *testing.T) {
|
|
m := Manifest{Module: "dunst", Preferences: map[string]Preference{
|
|
"font-size": {Type: TypeNumber, Default: float64(10), Predefined: "font-size", Why: "x"},
|
|
"width": {Type: TypeNumber, Default: float64(250), Why: "x"}}}
|
|
fill := func(layers []Layer) string {
|
|
t.Helper()
|
|
r := map[string]any{"id": "rc", "type": "file", "content": "font ${setting:font-size} width ${setting:width}"}
|
|
if err := settingInto(r, WithDefaults(m, layers), m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return r["content"].(string)
|
|
}
|
|
mesh := map[string]any{"font-size": float64(11), "width": float64(999)}
|
|
node := map[string]any{"font-size": float64(13)}
|
|
if got := fill(nil); got != "font 10 width 250" {
|
|
t.Fatalf("the defaults: %s", got)
|
|
}
|
|
if got := fill(ConfigLayers(m, mesh, nil, "g14")); got != "font 11 width 250" {
|
|
t.Fatalf("mesh config, which a preference without predefined does not read: %s", got)
|
|
}
|
|
both := ConfigLayers(m, mesh, node, "g14")
|
|
if got := fill(both); got != "font 13 width 250" {
|
|
t.Fatalf("node config over mesh config: %s", got)
|
|
}
|
|
own := append(both, Layer{From: "g14", Values: map[string]any{"font-size": float64(15)}})
|
|
if got := fill(own); got != "font 15 width 250" {
|
|
t.Fatalf("the module's own value over both: %s", got)
|
|
}
|
|
sources := map[string]string{}
|
|
for _, s := range Effective(m, both) {
|
|
sources[s.Key] = s.From
|
|
}
|
|
if sources["font-size"] != NodeConfigLayer("g14") || sources["width"] != DefaultLayer {
|
|
t.Fatalf("where each comes from: %v", sources)
|
|
}
|
|
merged, err := ApplySettings(map[string]any{"id": "conf", "type": "file", "merge": MergeJSON, "content": "{}"}, both)
|
|
if err != nil || merged["content"] != "{}\n" {
|
|
t.Fatalf("config reached a mergeable file: %v %v", merged["content"], err)
|
|
}
|
|
}
|