Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations, about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs, CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
130 lines
5.3 KiB
Go
130 lines
5.3 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0287 (issue 412): a seat names the paths its holder owns, a claim without them is
|
|
// refused, and only a claimant declares a seat's path.
|
|
|
|
// The starting set, exactly: node-hostname names /etc/hosts and /etc/hostname, node-uplink the resolver
|
|
// file stepping back for node-resolver, node-resolver the resolver file, and every other seat names none.
|
|
func TestTheSeatsThatNamePathsAreTheStartingSet(t *testing.T) {
|
|
want := map[string]string{
|
|
"node-hostname": "/etc/hosts /etc/hostname",
|
|
"node-uplink": "/etc/resolv.conf>node-resolver",
|
|
ResolverSeat: "/etc/resolv.conf",
|
|
}
|
|
for _, s := range DefaultSeats() {
|
|
var got []string
|
|
for _, p := range s.Paths {
|
|
if p.StepsBackFor != "" {
|
|
got = append(got, p.Path+">"+p.StepsBackFor)
|
|
} else {
|
|
got = append(got, p.Path)
|
|
}
|
|
}
|
|
if strings.Join(got, " ") != want[s.Name] {
|
|
t.Errorf("%s names %v; ADR 0287 gives it %q", s.Name, got, want[s.Name])
|
|
}
|
|
}
|
|
}
|
|
|
|
// What the store's rows carry is never the paths: a row read back keeps the compiled ones.
|
|
func TestTheSeatPathsSurviveTheStoresRows(t *testing.T) {
|
|
before := seats
|
|
t.Cleanup(func() { seats = before })
|
|
UseSeats([]Seat{{Name: "node-hostname", Scope: ScopeNode}})
|
|
s, _ := SeatNamed("node-hostname")
|
|
if len(s.Paths) != 2 {
|
|
t.Fatalf("node-hostname read back from its row names %v", s.Paths)
|
|
}
|
|
}
|
|
|
|
func hostnameSeat(t *testing.T) Seat {
|
|
t.Helper()
|
|
s, ok := SeatNamed("node-hostname")
|
|
if !ok {
|
|
t.Fatal("no node-hostname seat")
|
|
}
|
|
return s
|
|
}
|
|
|
|
func hostnameHolder(resources ...map[string]any) Manifest {
|
|
return Manifest{Module: "names", Version: "1", Claims: []Claim{{Name: "node-hostname", Scope: ScopeNode,
|
|
Serves: []string{"entries", "add", "remove"}}}, Resources: resources}
|
|
}
|
|
|
|
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 := Resolution{Node: "workstation", Modules: []Manifest{
|
|
{Module: "mesh-names", Computed: "mesh-names"},
|
|
}}
|
|
_, err := r.Declaration(Rendering{Generators: map[string]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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Who owns a seat's path on a machine: the seat naming it and its holder there, with the stepping back
|
|
// applied — the resolver's holder where node-resolver is held, else the uplink's.
|
|
func TestPathOwnersApplyTheSteppingBack(t *testing.T) {
|
|
held := []Held{
|
|
{Claim: "node-uplink", Scope: ScopeNode, Node: "laptop", Module: "networkmanager"},
|
|
{Claim: ResolverSeat, Scope: ScopeNode, Node: "laptop", Module: "systemd-resolved"},
|
|
{Claim: "node-uplink", Scope: ScopeNode, Node: "server", Module: "systemd-networkd"},
|
|
{Claim: "node-hostname", Scope: ScopeNode, Node: "server", Module: "hostname"},
|
|
}
|
|
var got []string
|
|
for _, o := range PathOwners(Seats(), held) {
|
|
got = append(got, o.Path+" "+o.Node+" "+o.Seat+" "+o.Module)
|
|
}
|
|
want := []string{
|
|
"/etc/hostname server node-hostname hostname",
|
|
"/etc/hosts server node-hostname hostname",
|
|
"/etc/resolv.conf laptop node-resolver systemd-resolved",
|
|
"/etc/resolv.conf server node-uplink systemd-networkd",
|
|
}
|
|
if strings.Join(got, "\n") != strings.Join(want, "\n") {
|
|
t.Errorf("the owners are\n%s\nnot\n%s", strings.Join(got, "\n"), strings.Join(want, "\n"))
|
|
}
|
|
}
|
|
|
|
// 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": 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(), 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 := Manifest{Module: "uplink", Version: "1", Claims: []Claim{{Name: "node-uplink"}},
|
|
Facts: map[string]RosterFile{"resolvers": {Path: "/etc//resolv.conf", Template: "x"},
|
|
"other": {Path: "/etc/uplink.conf", Template: "x"}}}
|
|
got := stepsBack(rendered, []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)
|
|
}
|
|
}
|