package resolution import ( "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) type writesHosts struct{} func (writesHosts) Resources(string) ([]map[string]any, bool, error) { return []map[string]any{{"id": "hosts", "type": "file", "path": "/etc/hosts", "into": "block", "content": "10.42.0.1 anchor\n"}}, true, nil } // The controller is bound too: what it generates for a machine on a seat's path is refused at composition. func TestAGeneratedResourceOnASeatsPathIsRefused(t *testing.T) { r := mcat.Resolution{Node: "workstation", Modules: []mcat.Manifest{ {Module: "mesh-names", Computed: "mesh-names"}, }} _, err := Declaration(r, mcat.Rendering{Generators: map[string]mcat.Generator{"mesh-names": writesHosts{}}}) if err == nil { t.Fatal("a generated resource on /etc/hosts was composed") } for _, want := range []string{"mesh-names", "/etc/hosts", "node-hostname"} { if !strings.Contains(err.Error(), want) { t.Errorf("the refusal does not name %s: %v", want, err) } } } // The stepping back comes from the seat, and is of a rendered file only: an uplink holder declaring the // resolver file as a resource beside a node-resolver holder is two writers of one path, refused when the // machine is resolved — never composed twice — as before the seat named its paths. func TestAnUplinkDeclaringTheResolverFileAsAResourceIsRefusedBesideTheResolver(t *testing.T) { shelf := resolverShelf(t) uplink := shelf["systemd-networkd"] uplink.Facts = nil uplink.Resources = append(append([]map[string]any(nil), uplink.Resources...), map[string]any{"id": "resolvers", "type": "file", "path": mcat.ResolverFile, "content": "nameserver 10.42.0.1\n"}) shelf["systemd-networkd"] = uplink shelf["local-resolver"] = localResolver() _, err := Resolve(shelf, []string{"dnsmasq", "systemd-networkd", "local-resolver"}, splitDNSLaptop(), mcat.World{}) if err == nil || !strings.Contains(err.Error(), `both declare the path "/etc/resolv.conf"`) { t.Fatalf("an uplink declaring the resolver file as a resource beside the resolver was not refused: %v", err) } // Its rendered file steps back, by the seat's definition, and nothing else of it does. rendered := mcat.Manifest{Module: "uplink", Version: "1", Claims: []mcat.Claim{{Name: "node-uplink"}}, Facts: map[string]mcat.RosterFile{"resolvers": {Path: "/etc//resolv.conf", Template: "x"}, "other": {Path: "/etc/uplink.conf", Template: "x"}}} got := stepsBack(rendered, []mcat.Manifest{rendered, localResolver()}) if len(got.Facts) != 1 || got.Facts["other"].Path == "" { t.Errorf("the uplink's holder renders %v beside the resolver", got.Facts) } }