package catalogue import ( "strings" "testing" ) // A setting is judged where it is stored, and an impossible one costs a module, not a machine // (novox/hq ADR 0163, rule 6): the one judgement, used by SetSettings before storing and by // Compose when a definition has moved under a stored setting. func TestASettingThatCannotComposeIsRefusedByNameAndLeavesOnlyItsModuleOut(t *testing.T) { web := Manifest{Module: "hello-web", Listens: []Listening{{Port: 8080, From: FromEverywhere}}, Resources: []map[string]any{{"id": "server", "type": "container", "name": "hello-web", "ports": []any{"8080"}}}} for _, c := range []struct { name string layer map[string]any refuse string }{ {"a port the module does not publish", map[string]any{PortsSetting: map[string]any{"9999": 10000}}, "hello-web gives port 9999 a machine port, and no container of its publishes 9999"}, {"a mesh-wide port", map[string]any{PortsSetting: map[string]any{"8080": 10000}}, "a port is a fact about one machine"}, } { from := "anchor" if c.name == "a mesh-wide port" { from = MeshWideLayer } err := JudgeSettings(web, []Layer{{From: from, Values: c.layer}}, true) if err == nil || !strings.Contains(err.Error(), c.refuse) { t.Errorf("%s: judged %v, want %q", c.name, err, c.refuse) } } if err := JudgeSettings(web, []Layer{{From: "anchor", Values: map[string]any{PortsSetting: map[string]any{"8080": 10000}}}}, true); err != nil { t.Fatalf("a port the module publishes was refused: %v", err) } // Composed, a module whose stored setting no longer works is left out by name; the rest of // the machine is declared. r := anAdoptedAnchor() with := anchorRendering(false) with.Settings = SettingsBy{"hello-web": {{From: "anchor", Values: map[string]any{PortsSetting: map[string]any{"9999": 10000}}}}} composed, err := r.Compose(with) if err != nil { t.Fatal(err) } why, left := composed.LeftOut["hello-web"] if !left || !strings.Contains(why, "no container of its publishes 9999") { t.Fatalf("hello-web is not left out by name: %v", composed.LeftOut) } if len(composed.LeftOut) != 1 { t.Fatalf("more than hello-web is left out: %v", composed.LeftOut) } got := byID(composed.Resources) if _, declared := got["hello-web.server"]; declared { t.Fatal("the left-out module's container is still declared") } if _, declared := got["distribution.store"]; !declared { t.Fatal("the rest of the machine was not declared") } if left := r.LeftOut(with.Settings, false); len(left) != 1 || left["hello-web"] == "" { t.Fatalf("the judgement a plan reads differs from what compose did: %v", left) } } // A taken container keeps a found network by a per-machine setting (novox/hq ADR 0163, rule 4): // on an adopted machine only, for a container the module declares, and it reaches the container's // declaration as the networks it also joins. func TestAKeptNetworkReachesTheContainerOnAnAdoptedMachineOnly(t *testing.T) { r := anAdoptedAnchor() keep := SettingsBy{"hello-web": {{From: "anchor", Values: map[string]any{NetworksSetting: map[string]any{"server": []any{"predecessor_default"}}}}}} with := anchorRendering(true) with.Settings = keep composed, err := r.Compose(with) if err != nil { t.Fatal(err) } if len(composed.LeftOut) != 0 { t.Fatalf("a kept network left a module out: %v", composed.LeftOut) } server := byID(composed.Resources)["hello-web.server"] networks, _ := server["networks"].([]any) if len(networks) != 1 || networks[0] != "predecessor_default" { t.Fatalf("the container does not join the kept network: %v", server) } if _, has := byID(composed.Resources)["distribution.store"]["networks"]; has { t.Fatal("another container joins a network nobody kept for it") } // Converged, the setting reaches nothing it was for, and the module is left out saying so. with = anchorRendering(false) with.Settings = keep composed, err = r.Compose(with) if err != nil { t.Fatal(err) } if why := composed.LeftOut["hello-web"]; !strings.Contains(why, "anchor is converged") { t.Fatalf("a kept network on a converged machine: %v", composed.LeftOut) } web := r.Modules[4] for _, c := range []struct { name string layer Layer want string }{ {"mesh-wide", Layer{From: MeshWideLayer, Values: map[string]any{NetworksSetting: map[string]any{"server": []any{"x"}}}}, "a found network is a fact about one machine"}, {"an unknown container", Layer{From: "anchor", Values: map[string]any{NetworksSetting: map[string]any{"db": []any{"x"}}}}, `names the container "db", which it does not declare`}, {"not a list", Layer{From: "anchor", Values: map[string]any{NetworksSetting: map[string]any{"server": "x"}}}, "is a list of network names"}, {"not a network name", Layer{From: "anchor", Values: map[string]any{NetworksSetting: map[string]any{"server": []any{"a/b"}}}}, "which is not a network name"}, } { _, err := KeptNetworks(web, []Layer{c.layer}, true) if err == nil || !strings.Contains(err.Error(), c.want) { t.Errorf("%s: %v, want %q", c.name, err, c.want) } } if kept, err := KeptNetworks(web, nil, false); err != nil || kept != nil { t.Fatalf("no setting: %v %v", kept, err) } }