package main // The package view over the bus (novox/hq ADR 0507 §6, §7; acceptance 1840/AC6): two nodes' `installed` keys and // their `declared-packages`, answered without asking any machine. import ( "context" "encoding/json" "fmt" "strings" "testing" "time" ) // viewStates is two nodes' packages on the bus: node-a current and declared, node-b over the cap since a while, // with a foreign package, and no declaration the controller holds. func viewStates(t *testing.T) (*fakeStates, *Machine) { t.Helper() states := &fakeStates{} inst := states.store(StateInstalled) inst.Put("node-a", InstalledValue{At: t0, Packages: []InstalledPackage{ {"glibc", "2.42-1", false, "core"}, {"tmux", "3.5-1", true, "extra"}, {"vim", "9.1-1", true, "extra"}, {"zsh", "5.9-2", true, "extra"}, }}) inst.Put("node-b", InstalledValue{At: t0.Add(-time.Hour), Packages: []InstalledPackage{ {"glibc", "2.42-1", false, "core"}, {"yay", "12.0-1", true, RepoForeign}, {"zsh", "5.9-1", false, "extra"}, }}) inst.Put("node-b.not-current", NotCurrent{Bytes: 300000, Since: t0.Add(-30 * time.Minute)}) decl := states.store(DeclaredPackagesState) var d declaredValue json.Unmarshal([]byte(`{"node":"node-a","packages":[{"package":"vim","modules":["editor"],"absent":false},`+ `{"package":"tmux","modules":["shell"],"absent":true},{"package":"zsh","modules":["login","shell"],"absent":false}]}`), &d) decl.Put("node-a", d) m := machine(func(_ context.Context, name string, args ...string) Ran { t.Errorf("a machine was asked: %s %v", name, args) return Ran{Status: 99} }, 1000) m.Node, m.States = "node-a", readerOf(states) return states, m } func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) { _, m := viewStates(t) r, err := m.Packages("zsh", 500) if err != nil { t.Fatal(err) } on := r["nodes"].([]PackageOnNode) if len(on) != 2 { t.Fatalf("%+v", on) } a, b := on[0], on[1] if a.Node != "node-a" || !a.Installed || a.Version != "5.9-2" || !a.Explicit || a.Repository != "extra" || strings.Join(a.DeclaredBy, ",") != "login,shell" || !a.Current { t.Errorf("node-a: %+v", a) } if b.Node != "node-b" || b.Version != "5.9-1" || b.Explicit || len(b.DeclaredBy) != 0 || b.Current { t.Errorf("node-b: %+v", b) } r, _ = m.Packages("yay", 500) on = r["nodes"].([]PackageOnNode) if on[0].Installed || !on[1].Installed || !on[1].Foreign || on[1].Repository != "" { t.Errorf("yay: %+v", on) } } func TestTheViewSaysWhatDiffersWhatIsOnSomeNodesWhatNoModuleDeclaresAndTheForeignApart(t *testing.T) { _, m := viewStates(t) r, err := m.Packages("", 500) if err != nil { t.Fatal(err) } nodes := r["nodes"].([]NodeView) if len(nodes) != 2 { t.Fatalf("%+v", nodes) } a, b := nodes[0], nodes[1] if a.Node != "node-a" || a.Packages != 4 || a.Explicit != 3 || !a.Current || fmt.Sprint(a.Undeclared) != "[{tmux 3.5-1}]" || len(a.Foreign) != 0 || !a.At.Equal(t0) { t.Errorf("node-a: %+v", a) } if b.Current || b.NotCurrent == nil || b.NotCurrent.Bytes != 300000 || b.Undeclared != nil || fmt.Sprint(b.Foreign) != "[{yay 12.0-1}]" || !b.At.Equal(t0.Add(-time.Hour)) { t.Errorf("node-b (not current, no declaration, a foreign package): %+v", b) } if d := r["differing"].([]Spread); len(d) != 1 || d[0].Name != "zsh" || d[0].Versions["node-a"] != "5.9-2" || d[0].Versions["node-b"] != "5.9-1" { t.Errorf("differing: %+v", d) } some := r["some_nodes"].([]Spread) var names []string for _, s := range some { names = append(names, s.Name) } if strings.Join(names, ",") != "tmux,vim" { t.Errorf("on some nodes only, the foreign apart: %v", names) } r, _ = m.Packages("", 1) if r["some_nodes_count"] != 2 || r["some_nodes_truncated"] != true || len(r["some_nodes"].([]Spread)) != 1 { t.Errorf("bounded: %v", r) } } func TestInstalledAndForeignAnswerFromTheBus(t *testing.T) { _, m := viewStates(t) r, err := m.InstalledFrom("", "", "", false, 10) if err != nil || r["node"] != "node-a" || r["count"] != 4 || r["current"] != true { t.Fatalf("this node's: %v %v", r, err) } pk := r["packages"].([]Package) if pk[0].Reason != "dependency" || pk[0].Repository != "core" || pk[1].Reason != "explicit" { t.Errorf("%+v", pk) } r, err = m.InstalledFrom("node-b", "", "", true, 10) if err != nil || r["count"] != 1 || r["current"] != false || r["not_current"] == nil { t.Fatalf("node-b's foreign: %v %v", r, err) } if tot := r["totals"].(map[string]int); tot["explicit"] != 1 || tot["dependency"] != 2 || tot["foreign"] != 1 { t.Errorf("totals: %v", tot) } f, err := m.ForeignFrom("node-b") if err != nil || f["count"] != 1 || fmt.Sprint(f["packages"]) != "[{yay 12.0-1}]" { t.Errorf("%v %v", f, err) } if _, err := m.InstalledFrom("node-c", "", "", false, 10); err == nil || !strings.Contains(err.Error(), "node-c") { t.Errorf("a node with no packages on the bus: %v", err) } } func TestUpdatesAnswerEveryNodeAndWhenItsListLastChanged(t *testing.T) { states, m := viewStates(t) up := states.store(StateUpdates) up.now = func() time.Time { return t0.Add(-2 * time.Hour) } up.Put("node-a", UpdatesValue{Reachable: true, Packages: []VersionChange{{"zsh", "5.9-2", "5.9-3"}}}) up.now = func() time.Time { return t0.Add(-3 * time.Hour) } up.Put("node-b", UpdatesValue{Reachable: false, Packages: []VersionChange{}}) r, err := m.UpdatesFor("") if err != nil { t.Fatal(err) } v := r["nodes"].([]UpdatesView) if len(v) != 2 || v[0].Count != 1 || !v[0].Reachable || v[0].Changed == nil || !v[0].Changed.Equal(t0.Add(-2*time.Hour)) || v[1].Reachable || !v[1].Changed.Equal(t0.Add(-3*time.Hour)) { t.Fatalf("%+v", v) } r, _ = m.UpdatesFor("node-b") if v := r["nodes"].([]UpdatesView); len(v) != 1 || v[0].Node != "node-b" { t.Errorf("one node: %+v", v) } } func TestANodeArgumentIsOneKeyToken(t *testing.T) { for _, bad := range []string{"node-a.not-current", "node-a.*", "a b"} { if _, err := nodeOf(map[string]any{"node": bad}); err == nil { t.Errorf("%q was taken as a node", bad) } } if n, err := nodeOf(map[string]any{"node": "node-a"}); err != nil || n != "node-a" { t.Errorf("%q %v", n, err) } }