package catalogue_test import ( "encoding/json" "strings" "testing" "github.com/novox/mesh-control/internal/catalogue" "github.com/novox/mesh-control/internal/overlay" ) // The manifests the control plane actually ships, resolved. // // Written because the earlier tests built their own manifests and passed while the real one was // missing a claim — a whole mechanism could have been absent from what ships and every test would // still have been green. func provided(t *testing.T) map[string]catalogue.Manifest { t.Helper() out := map[string]catalogue.Manifest{} for _, raw := range []map[string]any{ overlay.Manifest(), overlay.NamesManifest(), overlay.DomainManifest(), } { b, err := json.Marshal(raw) if err != nil { t.Fatal(err) } m, err := catalogue.ParseManifest(b) if err != nil { t.Fatalf("a manifest this control plane ships is not valid: %v", err) } out[m.Module] = m } return out } func TestTheShippedNetworkingModulesResolveOnTheirOwn(t *testing.T) { got, err := catalogue.Resolve(provided(t), []string{overlay.Domain}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{}) if err != nil { t.Fatalf("assigning %s does not work out of the box: %v", overlay.Domain, err) } var have []string for _, m := range got.Modules { have = append(have, m.Module) } for _, want := range []string{overlay.Domain, overlay.Name, overlay.Names} { if !strings.Contains(strings.Join(have, " "), want) { t.Fatalf("%s did not bring in %s: %v", overlay.Domain, want, have) } } } func TestTheShippedWireGuardModuleClaimsBeingTheNetwork(t *testing.T) { // Without this, a person who chose another VPN gets WireGuard as well, dragged in by the // names, and is not told. The claim is the only thing that catches it. shipped := provided(t) _, err := catalogue.Resolve( withTailscale(shipped), []string{overlay.Domain, "tailscale"}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{}) if err == nil { t.Fatal("a machine was given two private networks and nobody was told") } if !strings.Contains(err.Error(), overlay.TheNetwork) { t.Fatalf("the refusal does not say what collided: %v", err) } } func TestTheShippedNamesModuleNeedsTheMeshsOwnAddresses(t *testing.T) { // Over a VPN whose addresses the mesh does not hand out, it has no names to write. Refusing // is what stops a machine getting a hosts file that means nothing on it. shipped := provided(t) delete(shipped, overlay.Name) _, err := catalogue.Resolve(shipped, []string{overlay.Names}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{}) if err == nil { t.Fatal("the mesh's names resolved with nothing handing out the mesh's addresses") } if !strings.Contains(err.Error(), overlay.Addressing) { t.Fatalf("the refusal does not name what is missing: %v", err) } } func withTailscale(shelf map[string]catalogue.Manifest) map[string]catalogue.Manifest { out := map[string]catalogue.Manifest{} for k, v := range shelf { out[k] = v } // Deliberately without name-resolution of its own, which is the case that used to install // both VPNs: the names then needed the mesh's addressing, and only WireGuard has it. out["tailscale"] = catalogue.Manifest{ Module: "tailscale", Version: "1", Provides: catalogue.Offers(overlay.Requirement), Claims: []catalogue.Claim{{Name: overlay.TheNetwork, Scope: catalogue.ScopeNode}}, } return out }