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
125 lines
5.1 KiB
Go
125 lines
5.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
|
|
|
|
func claimed(claims string) []byte {
|
|
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh","credential":{"form":"none"}}],"claims":` + claims + `}`)
|
|
}
|
|
|
|
// The holder of a seat answers among several providers.
|
|
|
|
func registryShelf() map[string]Manifest {
|
|
return shelf(
|
|
Manifest{Module: "gitea", Version: "1", Provides: FromAnywhere("npm-package-registry"),
|
|
Claims: []Claim{{Name: "npm-package-registry", Scope: ScopeMesh}}},
|
|
Manifest{Module: "verdaccio", Version: "1", Provides: FromAnywhere("npm-package-registry")},
|
|
Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}},
|
|
)
|
|
}
|
|
|
|
func twoRegistries() map[string][]Provider {
|
|
return map[string][]Provider{"npm-package-registry": {
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
{Node: "archive", At: "archive.internal", Module: "verdaccio"},
|
|
}}
|
|
}
|
|
|
|
func giteaHoldsTheSeat() []Held {
|
|
return []Held{{Claim: "npm-package-registry", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
|
|
}
|
|
|
|
func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) {
|
|
// The whole point: a second registry beside the holder harms nothing, and nobody pins.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "anchor" {
|
|
t.Fatalf("the seat's holder did not answer: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestAPinStillWinsOverTheSeat(t *testing.T) {
|
|
// A consumer coupled to one provider's contents has said so, and the seat does not overrule it.
|
|
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(),
|
|
Pinned: map[string]Chosen{"npm-package-registry": {Node: "archive", Module: "verdaccio"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
|
|
t.Fatalf("the pin was overruled by the seat: %v", got.Needs)
|
|
}
|
|
}
|
|
|
|
func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) {
|
|
// No seat held is no choice made, and ADR 0009's rule stands: never guessed.
|
|
_, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
|
|
World{Offered: twoRegistries()})
|
|
if err == nil {
|
|
t.Fatal("one of two registries was picked with nobody holding the seat")
|
|
}
|
|
if !strings.Contains(err.Error(), "pin") {
|
|
t.Fatalf("the refusal does not say how to choose: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) {
|
|
// Two modules on one machine could provide the same thing; only the one holding the seat
|
|
// answers. A holder matched by node alone would send consumers to whichever came first.
|
|
providers := []Provider{
|
|
{Node: "anchor", At: "anchor.internal", Module: "verdaccio"},
|
|
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
|
|
}
|
|
holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat())
|
|
if !held || holder.Module != "gitea" {
|
|
t.Fatalf("the holder was not told apart from a neighbour: %+v", holder)
|
|
}
|
|
}
|
|
|
|
// The working set is loaded from the store, and an empty load never erases it (novox/hq ADR 0122).
|
|
func TestUseSeatsReplacesTheSetButNeverEmptiesIt(t *testing.T) {
|
|
before := Seats()
|
|
defer UseSeats(DefaultSeats()) // restore for other tests, whatever this leaves it as
|
|
|
|
// An empty load (store not seeded, or unreadable) leaves the compiled defaults in force.
|
|
UseSeats(nil)
|
|
if len(Seats()) != len(before) {
|
|
t.Fatalf("an empty load changed the set from %d to %d seats", len(before), len(Seats()))
|
|
}
|
|
// A non-empty load replaces it — this is how a rename in the store reaches the lookups.
|
|
UseSeats([]Seat{{Name: "node-firewall", Scope: ScopeNode, Decision: "novox/hq ADR 0122"}})
|
|
if _, known := SeatNamed("node-firewall"); !known {
|
|
t.Fatal("the loaded set did not replace the working set")
|
|
}
|
|
if len(Seats()) != 1 {
|
|
t.Fatalf("the working set is %d seats, not the one that was loaded", len(Seats()))
|
|
}
|
|
}
|
|
|
|
// A former name resolves to the seat it was renamed from (novox/hq ADR 0122), so a manifest's claim
|
|
// and a held record naming the old name break nothing after a rename.
|
|
func TestAFormerNameResolvesAfterARename(t *testing.T) {
|
|
defer func() { UseSeats(DefaultSeats()); UseAliases(nil) }()
|
|
UseSeats([]Seat{{Name: "git", Scope: ScopeMesh, Delivers: "git", Decision: "novox/hq ADR 0121"}})
|
|
UseAliases(map[string]string{"git": "git"})
|
|
|
|
// The old name resolves to the renamed seat.
|
|
if s, ok := SeatNamed("git"); !ok || s.Name != "git" {
|
|
t.Fatalf("the former name did not resolve to the renamed seat: %+v ok=%v", s, ok)
|
|
}
|
|
// And a holder recorded under the old name is still found for the provision the seat delivers.
|
|
providers := []Provider{{Node: "anchor", At: "anchor.internal", Module: "gitea"}}
|
|
held := []Held{{Claim: "git", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
|
|
holder, found := HolderAmong("git", providers, held)
|
|
if !found || holder.Module != "gitea" {
|
|
t.Fatalf("the holder recorded under the former name was not matched: %+v found=%v", holder, found)
|
|
}
|
|
}
|