Files
mesh-catalog/modules/mesh-provisioner-client/cmd/mesh-provisioner-client/collect_test.go
T

180 lines
8.4 KiB
Go

package main
import (
"context"
"encoding/json"
"strings"
"testing"
"time"
)
// A collection's test of novox/hq ADR 0349 ("How it is checked", design 57 L1, issue 684): zsh's file holds every
// placed contributor's item and loses one whose contributor leaves. Against a real bus, on the client's own account.
const zshrc = "/home/nox/.zshrc"
// zshCollector is zsh as a handed collector: its rc names its normal slot the old way, read as the collection
// shell:zsh:normal, and a collection of the new form.
func zshCollector(t *testing.T) Plan {
tpl, _ := templates(t, map[string]string{"zshrc": "# zsh\n${shell:zsh:normal}# then\n${collected:zsh-widgets}# end\n"})
return Plan{Templates: tpl, Files: []PlannedFile{{ID: "rc", Path: zshrc, Template: "zshrc",
OwnerAtRuntime: "owned"}},
Collections: []PlannedCollection{{Name: "shell:zsh:normal", Comment: "#"}, {Name: "zsh-widgets"}}}
}
// contributor is a module whose only part on the node is its item of collection, from its own template.
func contributor(t *testing.T, collection, text string) Plan {
tpl, _ := templates(t, map[string]string{"item": text})
return Plan{Templates: tpl, Items: []PlannedItem{{Collection: collection, Template: "item"}},
Placement: map[string]string{"machine:account-home": "/home/nox"}}
}
func TestNatsZshsFileHoldsEveryPlacedContributorsItemAndLosesOneWhoseContributorLeaves(t *testing.T) {
b := startBus(t)
heard := b.listen(t)
e := startEngine(t, map[string]string{zshrc: "owned", "/etc/fzf.conf": "owned"})
b.assign(t, "zsh", zshCollector(t))
b.assign(t, "powerlevel10k", contributor(t, "shell:zsh:normal",
"source ${machine:account-home}/.p10k.zsh\n"))
b.assign(t, "mesh-cli", contributor(t, "shell:zsh:normal", "alias nox=mesh-cli"))
b.config(t, "fzf", "binding", `"^T"`)
fzf := contributor(t, "zsh-widgets", "bindkey ${config:binding} fzf-file-widget\n")
b.assign(t, "fzf", fzf)
aClient(t, b, e, t.TempDir(), time.Hour)
every := "# zsh\n# mesh-cli\nalias nox=mesh-cli\n# powerlevel10k\nsource /home/nox/.p10k.zsh\n# then\n" +
"bindkey ^T fzf-file-widget\n# end\n"
eventually(t, "zshrc holds every contributor's item", func() bool { return e.read(t, zshrc) == every })
// Each item recorded under the node, the collection and its contributor; filled, the contributor's report says so.
eventually(t, "the items are recorded", func() bool {
got := b.keys(t, CollectBucket, node+".")
return len(got) == 3 && got[0] == "g14.shell=3Azsh=3Anormal.mesh-cli" &&
got[1] == "g14.shell=3Azsh=3Anormal.powerlevel10k" && got[2] == "g14.zsh-widgets.fzf"
})
kept, err := b.kv[CollectBucket].Get(context.Background(), "g14.shell=3Azsh=3Anormal.powerlevel10k")
if err != nil {
t.Fatal(err)
}
var it ItemRecord
if err := json.Unmarshal(kept.Value(), &it); err != nil || it.Text != "source /home/nox/.p10k.zsh\n" ||
it.Collection != "shell:zsh:normal" || it.Contributor != "powerlevel10k" {
t.Errorf("the item kept is %s (%v)", kept.Value(), err)
}
eventually(t, "powerlevel10k's report says its item", func() bool {
r, _ := heard.last("powerlevel10k")
return r.Status == StatusApplied && len(r.Items) == 1 && r.Items[0] == "shell:zsh:normal"
})
// zsh records the collections it reads as its needs, like any value.
eventually(t, "zsh's needs are recorded", func() bool {
got := b.keys(t, NeedsBucket, node+".zsh.")
return len(got) == 2 && got[0] == "g14.zsh.collected.zsh-widgets" && got[1] == "g14.zsh.shell.zsh=3Anormal"
})
// A contributor's own value changes: its item, and so zshrc, follow.
b.config(t, "fzf", "binding", `"^F"`)
eventually(t, "zshrc follows the item", func() bool { return strings.Contains(e.read(t, zshrc), "bindkey ^F") })
// powerlevel10k leaves the node: its item goes, and zshrc without it.
b.del(t, AssignmentsBucket, node+".powerlevel10k")
without := "# zsh\n# mesh-cli\nalias nox=mesh-cli\n# then\nbindkey ^F fzf-file-widget\n# end\n"
eventually(t, "zshrc loses the item of the contributor that left", func() bool { return e.read(t, zshrc) == without })
if got := b.keys(t, CollectBucket, node+".shell=3Azsh=3Anormal.powerlevel10k"); len(got) != 0 {
t.Errorf("the item of a contributor that left is kept: %v", got)
}
// And a contributor that stops contributing, though it stays: the same.
b.assign(t, "fzf", Plan{Templates: fzf.Templates, Files: []PlannedFile{{ID: "conf", Path: "/etc/fzf.conf",
Template: "item", OwnerAtRuntime: "owned"}}, Placement: fzf.Placement})
eventually(t, "zshrc loses an item no longer contributed", func() bool {
return e.read(t, zshrc) == "# zsh\n# mesh-cli\nalias nox=mesh-cli\n# then\n# end\n"
})
}
func TestNatsACollectorStartingWithItsNodeHoldsTheItemsAlreadyThereAndOnesRecordedWhileAway(t *testing.T) {
b := startBus(t)
e := startEngine(t, map[string]string{zshrc: "owned"})
b.assign(t, "zsh", zshCollector(t))
b.assign(t, "mesh-cli", contributor(t, "shell:zsh:normal", "alias nox=mesh-cli\n"))
state := t.TempDir()
_, stop := aClient(t, b, e, state, time.Hour)
eventually(t, "zshrc holds mesh-cli's item", func() bool { return strings.Contains(e.read(t, zshrc), "alias nox") })
stop()
b.del(t, AssignmentsBucket, node+".mesh-cli")
b.assign(t, "zsh-autosuggestions", contributor(t, "shell:zsh:normal", "source suggestions.zsh\n"))
aClient(t, b, e, state, time.Hour)
want := "# zsh\n# zsh-autosuggestions\nsource suggestions.zsh\n# then\n# end\n"
eventually(t, "zshrc is filled from the items now", func() bool { return e.read(t, zshrc) == want })
eventually(t, "the item of the module that left while away is removed", func() bool {
return len(b.keys(t, CollectBucket, node+".shell=3Azsh=3Anormal.mesh-cli")) == 0
})
}
// A collection the plan does not name, and an item naming a secret, fail with the reason; an item is never
// recorded with a secret in it.
func TestNatsACollectionNotPlannedAndAnItemNamingASecretFail(t *testing.T) {
b := startBus(t)
heard := b.listen(t)
e := startEngine(t, map[string]string{zshrc: "owned"})
p := zshCollector(t)
p.Collections = p.Collections[:1]
b.assign(t, "zsh", p)
b.assign(t, "leaky", contributor(t, "shell:zsh:normal", "export TOKEN=${secret:token}\n"))
aClient(t, b, e, t.TempDir(), time.Hour)
for module, why := range map[string]string{"zsh": "the plan names no collection zsh-widgets",
"leaky": "where no secret is"} {
eventually(t, module+" fails", func() bool {
r, _ := heard.last(module)
return r.Status == StatusFailed && strings.Contains(r.Reason, why)
})
}
if got := b.keys(t, CollectBucket, node+"."); len(got) != 0 {
t.Errorf("recorded %v", got)
}
if _, asked := e.counts(zshrc); asked != 0 {
t.Errorf("a collector naming a collection its plan does not was written")
}
}
func TestAnItemsKeyHoldsAnyCollectionAndReadsBack(t *testing.T) {
for collection, want := range map[string]string{
"zsh-widgets": "g14.zsh-widgets.fzf",
"shell:zsh:normal": "g14.shell=3Azsh=3Anormal.fzf",
"contribution:node-hotkeys:trigger": "g14.contribution=3Anode-hotkeys=3Atrigger.fzf",
} {
key := CollectKey(node, collection, "fzf")
if key != want {
t.Errorf("%s: %s, want %s", collection, key, want)
}
c, who, ok := itemOfKey(node, key)
if !ok || c != collection || who != "fzf" {
t.Errorf("%s reads back as %q %q %v", key, c, who, ok)
}
}
for _, bad := range []string{"g14.x", "ace.zsh-widgets.fzf", "g14.=ZZ.fzf", "g14.a.b.c", "g14.Upper.fzf"} {
if _, _, ok := itemOfKey(node, bad); ok {
t.Errorf("%s read as an item's key", bad)
}
}
}
func TestAPlansItemsAndCollectionsAreJudged(t *testing.T) {
p := Plan{Templates: Templates{Source: "x", Digest: "sha256:" + strings.Repeat("0", 64)},
Items: []PlannedItem{{Collection: "a", Template: "item"}, {Collection: "a", Template: "../x"},
{Collection: "Bad", Template: "y"}},
Collections: []PlannedCollection{{Name: "c", Comment: "#\n"}, {Name: "c"}}}
got := strings.Join(p.problems(), "\n")
for _, want := range []string{"item a is named twice", `template "../x"`, `item "Bad" is not`,
"collection c: its comment holds a line break", "collection c is named twice"} {
if !strings.Contains(got, want) {
t.Errorf("the problems %q do not say %q", got, want)
}
}
// A plan contributing an item and filling no file is one the client acts on.
if wrong := (Plan{Templates: p.Templates, Items: p.Items[:1]}).problems(); len(wrong) != 0 {
t.Errorf("a contributor's plan: %v", wrong)
}
if wrong := (Plan{Templates: p.Templates}).problems(); len(wrong) != 1 {
t.Errorf("a plan doing nothing: %v", wrong)
}
}