package catalogue import ( "reflect" "strings" "testing" ) // A collection is declared and filled by the client (novox/hq ADR 0349 ยง13, design 57 L1, issue 684): a collector // names a collection it collects, a contributor an item from a template of its own, and a handed composition plans // both for the node's provisioner client and renders neither. The three old namespaces read as collections of their // own name. Each file is filled one way: a piece of the other form on the machine is said as not placed. const ( collectorZsh = `{"module":"zsh","version":"1","moved":true, "claims":[{"name":"node-login-shell","scope":"node"}], "collections":[{"name":"widgets","comment":"#"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}, "resources":[{"id":"rc","type":"file","path":"${machine:account-home}/.zshrc","owner":"${machine:account}", "mode":"0644","template":"files/zshrc"}]}` zshrcText = "# zsh\n${shell:zsh:normal}${collected:widgets}" contributorText = "source ${machine:account-home}/.p10k.zsh\n" contributorP10k = `{"module":"prompt","version":"1","moved":true, "items":[{"collection":"shell:zsh:normal","template":"files/zsh"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}` oldFormAliases = `{"module":"aliases","version":"1", "shell":[{"for":"zsh","slot":"normal","code":"alias ll='ls -l'\n"}]}` ) func builtWith(module string, texts map[string]string) []Built { return []Built{{Name: "files", Kind: ArtifactArchive, Reference: ArtifactStoreScheme + module + "/files/blobs/sha256:" + strings.Repeat("e", 64), Digest: "sha256:" + strings.Repeat("e", 64), Texts: texts}} } func collectionsOn(t *testing.T) Resolution { t.Helper() zsh := held(t, collectorZsh, builtWith("zsh", map[string]string{"zshrc": zshrcText})) prompt := held(t, contributorP10k, builtWith("prompt", map[string]string{"zsh": contributorText})) aliases, err := ParseManifest([]byte(oldFormAliases)) if err != nil { t.Fatal(err) } r, err := Resolve(shelf(zsh, prompt, aliases), []string{"zsh", "prompt", "aliases"}, workstation(), World{}) if err != nil { t.Fatal(err) } r.Account = "op" return r } func TestAContributorsBuildKeepsItsItemsTemplateAndText(t *testing.T) { m := held(t, contributorP10k, builtWith("prompt", map[string]string{"zsh": contributorText})) if m.Handed == nil || !reflect.DeepEqual(m.Handed.Items, map[string]string{"shell:zsh:normal": "zsh"}) { t.Fatalf("the build kept %+v", m.Handed) } if !reflect.DeepEqual(m.Items, []Item{{Collection: "shell:zsh:normal", Content: contributorText}}) { t.Fatalf("the held items are %+v: the template's text, read back", m.Items) } } func TestAHandedCollectorAndContributorArePlannedAndNeitherIsRendered(t *testing.T) { c, got := composeWith(t, collectionsOn(t), handingTo("store:5000")) if strings.Contains(got, ".p10k.zsh") || strings.Contains(got, "alias ll") { t.Fatalf("the controller rendered a contribution into a handed collector: %s", got) } zsh, prompt := c.Plans["zsh"], c.Plans["prompt"] wantCollections := []PlanCollection{{Name: "shell:zsh:normal", Comment: "#"}, {Name: "widgets", Comment: "#"}} if !reflect.DeepEqual(zsh.Collections, wantCollections) || len(zsh.Items) != 0 { t.Fatalf("zsh's plan collects %+v and contributes %+v", zsh.Collections, zsh.Items) } if !reflect.DeepEqual(prompt.Items, []PlanItem{{Collection: "shell:zsh:normal", Template: "zsh"}}) || len(prompt.Files) != 0 || prompt.Placement["machine:account-home"] != "/home/op" { t.Fatalf("prompt's plan is %+v: its item, by template, and the placement its template names", prompt) } // The old form on the same machine is not placed, and said so. if len(c.Unplaced) != 1 || !strings.Contains(c.Unplaced[0], "aliases's shell:zsh:normal is not placed") { t.Fatalf("unplaced: %v", c.Unplaced) } } // A collector filled the old way holds the old forms and each moved contributor's item, rendered from the text the // mesh holds as its old form was (design 57 L2, issue 681), so the file loses no line where nothing is handed; a // collection of the new form stays empty. func TestACollectorFilledTheOldWayHoldsTheOldFormsAndEachHeldItem(t *testing.T) { c, got := composeWith(t, collectionsOn(t), Rendering{ArtifactStore: "store:5000"}) if c.Plans != nil { t.Fatalf("handed on a machine that cannot take it: %v", c.Plans) } if !strings.Contains(got, `"content":"# zsh\n# aliases\nalias ll='ls -l'\n# prompt\nsource /home/op/.p10k.zsh\n"`) { t.Fatalf("the old way does not hold the old forms and the held item in module order: %s", got) } if len(c.Unplaced) != 0 { t.Fatalf("unplaced: %v", c.Unplaced) } } // An item the controller cannot render the old way (it names a value but a machine fact) is said as not placed. func TestAHeldItemNamingMoreThanAFactIsSaidNotPlacedTheOldWay(t *testing.T) { zsh := held(t, collectorZsh, builtWith("zsh", map[string]string{"zshrc": zshrcText})) if _, ok := heldItemText(Manifest{Module: "p", Items: []Item{{Collection: "shell:zsh:normal", Content: "export THEME=${config:theme}\n"}}}, "shell:zsh:normal", map[string]string{}); ok { t.Fatal("an item naming a config value was rendered by the controller") } if _, ok := heldItemText(Manifest{Module: "p", Items: []Item{{Collection: "shell:zsh:normal", Content: "cd ${machine:account-home}\n"}}}, "shell:zsh:normal", map[string]string{}); ok { t.Fatal("an item naming a fact this machine lacks was rendered") } if _, ok := heldItemText(Manifest{Module: "p", Items: []Item{{Collection: "widgets", Content: "x\n"}}}, "widgets", nil); ok { t.Fatal("an item of a collection of the new form was rendered the old way") } left := itemsLeftOut([]Manifest{zsh, {Module: "p", Items: []Item{{Collection: "shell:zsh:normal", Content: "export THEME=${config:theme}\n"}}}}, zsh, "shell:zsh:normal", map[string]string{}) if len(left) != 1 || !strings.Contains(left[0], "p's item of shell:zsh:normal is not placed") { t.Fatalf("left out: %v", left) } } // Each old namespace L2 moves renders a moved contributor's item exactly as its old form did, where the collector is // not handed: the same contributor written both ways composes the same bytes (design 57 L2, issue 681). func TestAMovedContributorsItemRendersAsItsOldFormDid(t *testing.T) { code := "# two lines\nalias a=b" asOld := Manifest{Module: "laptop", Shell: []ShellCode{{For: "zsh", Slot: "normal", Code: code}}, Contributions: []SeatContribution{{Seat: HotkeysSeat, Kind: "trigger", Content: "KEY_F21 1 touchpad\n"}}} asItem := Manifest{Module: "laptop", Moved: true, Items: []Item{{Collection: "shell:zsh:normal", Content: code}, {Collection: "contribution:node-hotkeys:trigger", Content: "KEY_F21 1 touchpad\n"}}} other := Manifest{Module: "zz-other", Shell: []ShellCode{{For: "zsh", Slot: "normal", Code: "x=1\n"}}, Contributions: []SeatContribution{{Seat: HotkeysSeat, Kind: "trigger", Content: "KEY_MUTE 1 mute\n"}}} first := Manifest{Module: "aa-first", Shell: []ShellCode{{For: "zsh", Slot: "normal", Code: "y=2\n"}}} for _, mods := range [][]Manifest{{other, first}, {}} { was := append(append([]Manifest(nil), mods...), asOld) is := append(append([]Manifest(nil), mods...), asItem) if a, b := shellCodeOn(was, "zsh", "normal", nil), shellCodeOn(is, "zsh", "normal", nil); a != b || a == "" { t.Errorf("shell: the old form made %q, the item %q", a, b) } a, _, errA := seatContributions(was, hotkeysHolder(), "node-hotkeys:trigger", Rendering{}, nil, nil) b, left, errB := seatContributions(is, hotkeysHolder(), "node-hotkeys:trigger", Rendering{}, nil, nil) if errA != nil || errB != nil || a != b || a == "" || len(left) != 0 { t.Errorf("hotkeys: the old form made %q, the item %q (%v %v %v)", a, b, errA, errB, left) } } } func TestCollectionsAndItemsAreJudged(t *testing.T) { for name, c := range map[string]struct{ raw, says string }{ "an item of a module not moved": {`{"module":"m","version":"1", "items":[{"collection":"shell:zsh:normal","template":"files/x"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, "is marked moved"}, "two items to one collection": {`{"module":"m","version":"1","moved":true, "items":[{"collection":"a","template":"files/x"},{"collection":"a","template":"files/y"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, "contributes to a twice"}, "an item to no slot": {`{"module":"m","version":"1","moved":true, "items":[{"collection":"shell:zsh:middle","template":"files/x"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, "is no slot"}, "an item outside the templates": {`{"module":"m","version":"1","moved":true, "items":[{"collection":"a","template":"x"}], "build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, "is not a file under files/"}, "an item naming a secret": {`{"module":"m","version":"1", "items":[{"collection":"a","content":"t=${secret:token}"}]}`, "where no secret is"}, "a collection not declared": {`{"module":"m","version":"1", "resources":[{"id":"f","type":"file","path":"/f","content":"${collected:a}"}]}`, "does not collect"}, "an old namespace declared": {`{"module":"m","version":"1","collections":[{"name":"shell:zsh:normal"}], "resources":[{"id":"f","type":"file","path":"/f","content":"x"}]}`, "read from its placeholder"}, "a collection placed nowhere": {`{"module":"m","version":"1","collections":[{"name":"a"}], "resources":[{"id":"f","type":"file","path":"/f","content":"x"}]}`, "no file of its places it"}, "a collection in a path": {`{"module":"m","version":"1","collections":[{"name":"a"}], "resources":[{"id":"f","type":"file","path":"/${collected:a}","content":"${collected:a}"}]}`, "only in a file's content"}, } { _, err := ParseManifest([]byte(c.raw)) if err == nil || !strings.Contains(err.Error(), c.says) { t.Errorf("%s: %v, want it to say %q", name, err, c.says) } } if _, err := ParseManifest([]byte(collectorZsh)); err != nil { t.Errorf("a collector: %v", err) } }