ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
120 lines
5.0 KiB
Go
120 lines
5.0 KiB
Go
package resolution
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func networkingShelf(extra ...mcat.Manifest) map[string]mcat.Manifest {
|
|
base := []mcat.Manifest{
|
|
{Module: "networking", Requires: []string{"private-network", "name-resolution"}},
|
|
{Module: "mesh-wireguard", Computed: "mesh-wireguard",
|
|
Provides: mcat.Offers("private-network", "mesh-addressing"),
|
|
Claims: []mcat.Claim{{Name: "the-private-network", Scope: mcat.ScopeNode}}},
|
|
{Module: "mesh-names", Computed: "mesh-names",
|
|
Provides: mcat.Offers("name-resolution"), Requires: []string{"mesh-addressing"}},
|
|
}
|
|
return shelf(append(base, extra...)...)
|
|
}
|
|
|
|
func TestOneWordBringsUpTheNetwork(t *testing.T) {
|
|
// The case that has to stay easy. Nothing is asked, because with one answer to each
|
|
// requirement there was never a question.
|
|
got, err := Resolve(networkingShelf(), []string{"networking"}, workstation(), mcat.World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
have := strings.Join(names(got), " ")
|
|
for _, want := range []string{"networking", "mesh-wireguard", "mesh-names"} {
|
|
if !strings.Contains(have, want) {
|
|
t.Fatalf("assigning networking did not bring in %s: %s", want, have)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestASecondVPNTurnsItIntoAChoice(t *testing.T) {
|
|
// And the choice is offered rather than made. A default here would be the flavor field coming
|
|
// back under another name.
|
|
_, err := Resolve(networkingShelf(mcat.Manifest{Module: "tailscale", Provides: mcat.Offers("private-network"),
|
|
Claims: []mcat.Claim{{Name: "the-private-network", Scope: mcat.ScopeNode}}},
|
|
), []string{"networking"}, workstation(), mcat.World{})
|
|
|
|
if err == nil {
|
|
t.Fatal("two VPNs and one was picked silently")
|
|
}
|
|
for _, want := range []string{"mesh-wireguard", "tailscale"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("the refusal does not name %s: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChoosingIsAssigning(t *testing.T) {
|
|
// No second verb. Assigning the one you want answers the requirement, and the bundle takes it.
|
|
got, err := Resolve(networkingShelf(mcat.Manifest{Module: "tailscale",
|
|
Provides: mcat.Offers("private-network", "name-resolution"),
|
|
Claims: []mcat.Claim{{Name: "the-private-network", Scope: mcat.ScopeNode}}},
|
|
), []string{"networking", "tailscale"}, workstation(), mcat.World{})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
have := strings.Join(names(got), " ")
|
|
if !strings.Contains(have, "tailscale") {
|
|
t.Fatalf("the chosen VPN is not in the set: %s", have)
|
|
}
|
|
if strings.Contains(have, "mesh-wireguard") {
|
|
t.Fatalf("choosing tailscale still installed WireGuard: %s", have)
|
|
}
|
|
}
|
|
|
|
func TestChoosingOneVPNCannotDragTheOtherBackIn(t *testing.T) {
|
|
// This happened. The person chose tailscale; the names module required the mesh's own
|
|
// addressing; only WireGuard provides that; so both were installed and nobody was told.
|
|
//
|
|
// The claim is what catches it. Providing a private network is not the singular part — a
|
|
// machine could run two VPNs for two purposes — but being *the* one the mesh runs over is.
|
|
_, err := Resolve(networkingShelf(mcat.Manifest{Module: "tailscale", Provides: mcat.Offers("private-network"),
|
|
Claims: []mcat.Claim{{Name: "the-private-network", Scope: mcat.ScopeNode}}},
|
|
), []string{"networking", "tailscale"}, workstation(), mcat.World{})
|
|
|
|
if err == nil {
|
|
t.Fatal("a machine was given two private networks without being told")
|
|
}
|
|
if !strings.Contains(err.Error(), "the-private-network") {
|
|
t.Fatalf("the refusal does not say what collided: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNamesNeedTheMeshsOwnAddresses(t *testing.T) {
|
|
// Names are computed from addresses the mesh handed out. Over a VPN that hands out its own,
|
|
// the mesh has nothing to write, so the names module requires the addressing rather than a
|
|
// private network in general — otherwise a machine gets a hosts file full of addresses that
|
|
// mean nothing on it.
|
|
_, err := Resolve(shelf(mcat.Manifest{Module: "mesh-names", Computed: "mesh-names",
|
|
Provides: mcat.Offers("name-resolution"), Requires: []string{"mesh-addressing"}}, mcat.Manifest{Module: "tailscale", Provides: mcat.Offers("private-network")},
|
|
), []string{"mesh-names", "tailscale"}, workstation(), mcat.World{})
|
|
|
|
if err == nil {
|
|
t.Fatal("the mesh's names were installed over a VPN whose addresses it does not hand out")
|
|
}
|
|
if !strings.Contains(err.Error(), "mesh-addressing") {
|
|
t.Fatalf("the refusal does not say what is missing: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestARequirementWantedTwiceIsReportedOnce(t *testing.T) {
|
|
// Two identical lines make a person hunt for the difference between them before realising
|
|
// there is none.
|
|
_, err := Resolve(shelf(mcat.Manifest{Module: "one", Requires: []string{"shell"}}, mcat.Manifest{Module: "two", Requires: []string{"shell"}}, mcat.Manifest{Module: "bash", Provides: mcat.Offers("shell")}, mcat.Manifest{Module: "zsh", Provides: mcat.Offers("shell")}), []string{"one", "two"}, workstation(), mcat.World{})
|
|
|
|
if err == nil {
|
|
t.Fatal("two shells and one was picked silently")
|
|
}
|
|
if n := strings.Count(err.Error(), `"shell" is wanted by`); n != 1 {
|
|
t.Fatalf("the same requirement was reported %d times:\n%v", n, err)
|
|
}
|
|
}
|