package resolution import ( "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) func scrubber() mcat.Manifest { return mcat.Manifest{Module: "zfs", Settings: map[string]mcat.SettingDeclaration{ "scrub-cadence": {Kind: mcat.KindPreference, Default: "weekly", Why: "what the distribution's timer did"}, }, Resources: []map[string]any{ {"id": "distribution-weekly", "type": "service", "unit": "zfs-scrub-weekly@${setting:scrub-pool}.timer", "state": "stopped", "boot": "disabled"}, {"id": "distribution-monthly", "type": "service", "unit": "zfs-scrub-monthly@${setting:scrub-pool}.timer", "state": "stopped", "boot": "disabled"}, {"id": "scrub-config", "type": "file", "path": "/etc/zfs-tools/scrub.conf", "content": "pool=${setting:scrub-pool}\ncadence=${setting:scrub-cadence}\n"}, }, } } func TestASettingNamesAServicesUnit(t *testing.T) { m := scrubber() layers := WithDefaults(m, []mcat.Layer{{From: "ace", Values: map[string]any{"scrub-pool": "storage", "scrub-cadence": "monthly"}}}) for i, want := range []string{"zfs-scrub-weekly@storage.timer", "zfs-scrub-monthly@storage.timer"} { r := map[string]any{} for k, v := range m.Resources[i] { r[k] = v } if err := settingInto(r, layers, m.Module); err != nil { t.Fatal(err) } if r["unit"] != want { t.Errorf("composed as %q, not %q", r["unit"], want) } } } // Composed for a machine, the way a push writes it: the operator's pool and cadence reach the units' names // and the file. func TestAComposedMachineGetsTheUnitTheSettingNames(t *testing.T) { r := anAdoptedAnchor() r.Modules = append(r.Modules, scrubber()) with := anchorRendering(false) with.Settings["zfs"] = []mcat.Layer{{From: "anchor", Values: map[string]any{"scrub-pool": "storage", "scrub-cadence": "monthly"}}} composed, err := Compose(r, with) if err != nil { t.Fatal(err) } if why, left := composed.LeftOut["zfs"]; left { t.Fatalf("left out: %s", why) } got := byID(composed.Resources) for id, want := range map[string]string{"zfs.distribution-weekly": "zfs-scrub-weekly@storage.timer", "zfs.distribution-monthly": "zfs-scrub-monthly@storage.timer"} { if got[id]["unit"] != want { t.Errorf("%s composed as %v, not %s", id, got[id]["unit"], want) } t.Logf("%s: %v", id, got[id]["unit"]) } if c := got["zfs.scrub-config"]["content"]; c != "pool=storage\ncadence=monthly\n" { t.Errorf("the file: %q", c) } } // A value that would not make a unit's name is refused by name, never written: a space, a slash, a // newline that would begin a directive, or a second suffix. func TestASettingThatMakesNoUnitNameIsRefused(t *testing.T) { m := scrubber() for _, bad := range []string{"", "stor age", "a/b", "storage\nExecStart=/bin/sh", "a@b", "$(x)", "*", `a\\x2d`} { r := map[string]any{} for k, v := range m.Resources[0] { r[k] = v } err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"scrub-pool": bad}}}, m.Module) if err == nil || !strings.Contains(err.Error(), "scrub-pool") || !strings.Contains(err.Error(), "unit") { t.Errorf("%q: %v", bad, err) } if r["unit"] != m.Resources[0]["unit"] { t.Errorf("%q: the unit was changed on refusal: %v", bad, r["unit"]) } } r := map[string]any{} for k, v := range m.Resources[0] { r[k] = v } if err := settingInto(r, nil, m.Module); err == nil || !strings.Contains(err.Error(), "${setting:scrub-pool}") { t.Errorf("nothing set: %v", err) } } func TestAnInstanceValueMayNotLeadWithADashNorBeLong(t *testing.T) { m := scrubber() for _, bad := range []string{"-storage", "--help", strings.Repeat("a", 65)} { r := map[string]any{} for k, v := range m.Resources[0] { r[k] = v } err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"scrub-pool": bad}}}, m.Module) if err == nil || !strings.Contains(err.Error(), "scrub-pool") { t.Errorf("%q: %v", bad, err) } } r := map[string]any{} for k, v := range m.Resources[0] { r[k] = v } if err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"scrub-pool": strings.Repeat("a", 64)}}}, m.Module); err != nil { t.Errorf("64 characters: %v", err) } }