package main import ( "bytes" "os" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // An act says what it changed about the node it acted on, and nothing about the rest of the mesh // (novox/hq ADR 0207): after the seat dependencies shipped, every `push ` and `assign` printed // every node's unmet dependencies, a hundred lines around the one about the module just assigned. func aContainer(name string) catalogue.Manifest { return catalogue.Manifest{Module: name, Version: "1", Resources: []map[string]any{{"id": name, "type": "container", "image": name}}} } func TestAnAssignmentSaysOnlyWhatItChangedOnItsOwnNode(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, aContainer("web")) register(t, open, aContainer("db")) // anchor already lacks a runtime for db: true, and not this act's to say. if _, err := open.inventory.Assign(ctx, "anchor", "db"); err != nil { t.Fatal(err) } if _, err := open.inventory.Assign(ctx, "laptop", "db"); err != nil { t.Fatal(err) } said, err := assign(ctx, open, "laptop", "web") if err != nil { t.Fatalf("%v\n%s", err, said) } if !strings.Contains(said, "web on laptop depends on "+catalogue.ContainerRuntimeSeat) { t.Errorf("the assignment does not say what the module it assigned depends on:\n%s", said) } for _, not := range []string{"db on laptop", "db on anchor", "anchor:"} { if strings.Contains(said, not) { t.Errorf("the assignment says %q, which it did not change:\n%s", not, said) } } } func TestAnAssignmentThatMeetsADependencySaysSo(t *testing.T) { shelf := map[string]catalogue.Manifest{ "web": aContainer("web"), "docker": {Module: "docker", Claims: []catalogue.Claim{{Name: catalogue.ContainerRuntimeSeat}}, Resources: []map[string]any{{"id": "d", "type": "container", "image": "dind"}}}, } lines := unheldChange(shelf, "laptop", []string{"web"}, []string{"web", "docker"}) if len(lines) != 1 || !strings.Contains(lines[0], "web on laptop now has "+catalogue.ContainerRuntimeSeat+" held") { t.Errorf("meeting a dependency said %v", lines) } // Taking the dependent off says nothing: the dependency went with its module. if lines := unheldChange(shelf, "laptop", []string{"web"}, nil); len(lines) != 0 { t.Errorf("unassigning the dependent said %v", lines) } } func TestAPushSaysANamedNodesDependenciesAndCountsTheRest(t *testing.T) { unheld := map[string][]catalogue.Unheld{ "anchor": {{Node: "anchor", Module: "db", Seat: catalogue.ContainerRuntimeSeat}}, "laptop": {{Node: "laptop", Module: "web", Seat: catalogue.ContainerRuntimeSeat}, {Node: "laptop", Module: "sshd", Seat: catalogue.ServiceManagerSeat}}, } var named bytes.Buffer reportUnheldPushed(&named, true, []string{"laptop"}, unheld) got := named.String() if !strings.Contains(got, "laptop has 2 unmet") || !strings.Contains(got, "web on laptop") || !strings.Contains(got, "sshd on laptop") || strings.Contains(got, "anchor") { t.Errorf("a named push said:\n%s", got) } var all bytes.Buffer reportUnheldPushed(&all, false, []string{"anchor", "laptop", "quiet"}, unheld) want := "anchor: 1 unmet seat dependenc(ies) — see `status`\nlaptop: 2 unmet seat dependenc(ies) — see `status`\n" if all.String() != want { t.Errorf("a push to every node said:\n%s\nwant\n%s", all.String(), want) } } func TestOnlyTheServingControllerLogsEachChange(t *testing.T) { read := func(f func()) string { old := os.Stderr r, w, _ := os.Pipe() os.Stderr = w f() _ = w.Close() os.Stderr = old var b bytes.Buffer _, _ = b.ReadFrom(r) return b.String() } u := []catalogue.Unheld{{Node: "n1", Module: "web", Seat: catalogue.ContainerRuntimeSeat}} if got := read(func() { logUnheld("n1", u) }); got != "" { t.Errorf("a command logged:\n%s", got) } logUnheldChanges = true defer func() { logUnheldChanges = false }() if got := read(func() { logUnheld("n1", u) }); !strings.Contains(got, "web on n1") { t.Errorf("the serving controller did not log a change:\n%s", got) } if got := read(func() { logUnheld("n1", u) }); got != "" { t.Errorf("an unchanged report was logged again:\n%s", got) } }