package catalogue import ( "strings" "testing" ) // A module whose files cannot be written in advance. // // The mesh's private network is the case: a machine's peer list is derived from every other // machine, so it differs on each one and changes when any of them changes. What matters in these // tests is not that it works, but that being computed changes *nothing else* about being a // module — it is assigned, resolved, settled and absent when nobody asked for it. type fake struct { on map[string]bool asked []string err error } func (f *fake) Resources(node string) ([]map[string]any, bool, error) { f.asked = append(f.asked, node) if f.err != nil { return nil, false, f.err } if !f.on[node] { return nil, false, nil } return []map[string]any{{"id": "peers", "type": "file", "path": "/etc/x.conf", "merge": MergeJSON, "content": `{"peer":"` + node + `"}`}}, true, nil } func computedShelf() map[string]Manifest { return shelf(Manifest{Module: "mesh-network", Computed: "mesh-network", Provides: Offers("private-network")}) } func TestAComputedModuleIsAskedAboutTheNodeItIsFor(t *testing.T) { // The generator gets a node name, not a plan. Everything it needs is the whole mesh, which // it was built with — this is the one thing a node could never work out for itself. got, err := Resolve(computedShelf(), []string{"mesh-network"}, workstation(), World{}) if err != nil { t.Fatal(err) } gen := &fake{on: map[string]bool{"workstation": true}} out, err := got.Declaration(Rendering{Generators: map[string]Generator{"mesh-network": gen}}) if err != nil { t.Fatal(err) } if len(gen.asked) != 1 || gen.asked[0] != "workstation" { t.Fatalf("asked about %v, wanted workstation once", gen.asked) } if len(out) != 1 || !strings.Contains(out[0]["content"].(string), `"peer": "workstation"`) { t.Fatalf("got %v", out) } } func TestAMachineNobodyGaveItGetsNothing(t *testing.T) { // The whole point of making the network a module. Before this, every machine with an address // was on the private network and there was no way to say one should stay off. got, err := Resolve(computedShelf(), nil, workstation(), World{}) if err != nil { t.Fatal(err) } gen := &fake{on: map[string]bool{"workstation": true}} out, err := got.Declaration(Rendering{Generators: map[string]Generator{"mesh-network": gen}}) if err != nil { t.Fatal(err) } if len(out) != 0 { t.Fatalf("a machine that was assigned nothing got %d resource(s)", len(out)) } if len(gen.asked) != 0 { t.Fatalf("the generator was asked about %v, and nobody had asked for it", gen.asked) } } func TestAssignedAndNotYetPartOfItIsNotAFailure(t *testing.T) { // A machine given the network module before it has a place on it. Brief and ordinary — the // answer is "nothing yet", and treating it as an error would make an ordering a fault. got, _ := Resolve(computedShelf(), []string{"mesh-network"}, workstation(), World{}) gen := &fake{on: map[string]bool{}} out, err := got.Declaration(Rendering{Generators: map[string]Generator{"mesh-network": gen}}) if err != nil { t.Fatalf("refused a node that is not on the network yet: %v", err) } if len(out) != 0 { t.Fatalf("got %v", out) } } func TestAGeneratorThisControlPlaneDoesNotHaveIsRefused(t *testing.T) { // Sending a machine a module with no files would look like it worked. Named in the message, // because the only fix is a control plane that has it. got, _ := Resolve(shelf(Manifest{Module: "weather", Computed: "the-weather"}), []string{"weather"}, workstation(), World{}) _, err := got.Declaration(Rendering{Generators: map[string]Generator{"mesh-network": &fake{}}}) if err == nil { t.Fatal("a module computed by nothing was accepted") } if !strings.Contains(err.Error(), "the-weather") { t.Fatalf("the refusal does not name what is missing: %v", err) } } func TestAModuleIsEitherWrittenOrComputedNotBoth(t *testing.T) { // Otherwise nobody could say where a given file on a machine came from. _, err := ParseManifest([]byte(`{"module":"mesh-network","version":"1", "computed":"mesh-network", "resources":[{"id":"a","type":"package","package":"wireguard-tools"}]}`)) if err == nil { t.Fatal("a module that both ships files and has them computed was accepted") } if !strings.Contains(err.Error(), "one or the other") { t.Fatalf("unhelpful refusal: %v", err) } } func TestSettingsApplyToAComputedModuleToo(t *testing.T) { // Being computed is about where the files come from, not about whether they are configurable. // A line drawn there would be arbitrary and nobody could predict it. got, _ := Resolve(computedShelf(), []string{"mesh-network"}, workstation(), World{}) gen := &fake{on: map[string]bool{"workstation": true}} out, err := got.Declaration(Rendering{ Generators: map[string]Generator{"mesh-network": gen}, Settings: SettingsBy{"mesh-network": {{From: "the mesh", Values: map[string]any{"keepalive": 25}}}}, }) if err != nil { t.Fatal(err) } content := out[0]["content"].(string) if !strings.Contains(content, `"keepalive": 25`) { t.Fatalf("the setting did not reach a computed file: %s", content) } if !strings.Contains(content, `"peer": "workstation"`) { t.Fatalf("the setting replaced what the generator computed: %s", content) } } func TestTwoWaysToBeOnAPrivateNetworkRefuseAndNameBoth(t *testing.T) { // WireGuard is one way. The refusing rule is what makes a second one safe to add: assigning // both is caught rather than producing a machine on two networks that each half-work. _, err := Resolve(shelf( Manifest{Module: "mesh-network", Computed: "mesh-network", Provides: Offers("private-network")}, Manifest{Module: "tailscale", Provides: Offers("private-network")}, Manifest{Module: "backups", Requires: []string{"private-network"}}, ), []string{"backups"}, workstation(), World{}) if err == nil { t.Fatal("two answers to one requirement were taken silently") } for _, want := range []string{"mesh-network", "tailscale", "private-network"} { if !strings.Contains(err.Error(), want) { t.Fatalf("the refusal does not name %s: %v", want, err) } } }