The manifest carries seats and uses; registration refuses a mesh-* name, a duplicate declarer, an undeclared uses or claim, a seat with no protocol, a scope mismatch, and a holder that does not answer what its seat promises. The parser stops judging unknown claim names, because it cannot: another module may declare that seat, and one manifest cannot tell. The test that encoded the old rule is rewritten to assert the refusal at registration, and a new one pins the case the parser could not have distinguished. Tools are declared for the first time, under their own key — serves already means a provision's facts.
232 lines
8.9 KiB
Go
232 lines
8.9 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
|
|
|
|
// The set is closed, and changing it is a decision.
|
|
//
|
|
// **The count is asserted, and every entry names the record that made it a seat**, so the next
|
|
// person changing the set finds the argument rather than a number to edit — the pattern the host's
|
|
// vocabulary test follows. If this fails because a seat was added, the fix is a record in novox/hq
|
|
// and a row in to-be 26, not a new number here.
|
|
func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
|
|
record := regexp.MustCompile(`^novox/hq ADR \d{4}$`)
|
|
seen := map[string]bool{}
|
|
delivered := map[string]string{}
|
|
for _, s := range Seats() {
|
|
if seen[s.Name] {
|
|
t.Errorf("%s is in the set twice", s.Name)
|
|
}
|
|
seen[s.Name] = true
|
|
if !record.MatchString(s.Decision) {
|
|
t.Errorf("%s names %q as its decision; every seat names the record that made it one",
|
|
s.Name, s.Decision)
|
|
}
|
|
switch s.Scope {
|
|
case ScopeNode, ScopeSite, ScopeMesh:
|
|
default:
|
|
t.Errorf("%s is held per %q, which is not a scope", s.Name, s.Scope)
|
|
}
|
|
if s.Delivers != "" {
|
|
// Two seats answering for one provision would put the question "which one?" back,
|
|
// which is the question a seat exists to answer.
|
|
if other, twice := delivered[s.Delivers]; twice {
|
|
t.Errorf("%s and %s both deliver %q", other, s.Name, s.Delivers)
|
|
}
|
|
delivered[s.Delivers] = s.Name
|
|
}
|
|
}
|
|
if len(Seats()) != 14 {
|
|
t.Errorf("the mesh defines %d seats rather than 14; the set is closed, so a change here is "+
|
|
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
|
|
}
|
|
}
|
|
|
|
func claimed(claims string) []byte {
|
|
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh"}],"claims":` + claims + `}`)
|
|
}
|
|
|
|
// **The refusal moved, it did not go** (novox/hq ADR 0118, superseding 0110). A module may now
|
|
// declare its own seats, so whether a claimed seat exists is a fact about the *catalogue* and not
|
|
// about the manifest in front of the parser: a claim on a seat another registered module declares
|
|
// is perfectly good, and the parser cannot tell the two cases apart. So the parser accepts it and
|
|
// registration refuses it — the same guarantee, at the same moment work would otherwise start,
|
|
// from a set nobody maintains by hand.
|
|
func TestAClaimOnASeatNobodyDeclaresIsRefusedAtRegistration(t *testing.T) {
|
|
m, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatalf("the parser judged a claim it cannot judge alone: %v", err)
|
|
}
|
|
|
|
problems := CatalogueProblems(Shelf{m.Module: m})
|
|
if len(problems) == 0 {
|
|
t.Fatal("a module invented a seat by claiming it, and registration allowed it")
|
|
}
|
|
joined := strings.Join(problems, "; ")
|
|
if !strings.Contains(joined, "the-anything") || !strings.Contains(joined, "no module declares") {
|
|
t.Fatalf("the refusal does not say the seat is nobody's: %v", problems)
|
|
}
|
|
}
|
|
|
|
// And the same claim is fine once something declares that seat, which is the case the parser
|
|
// could not have distinguished.
|
|
func TestAClaimOnASeatAnotherModuleDeclaresIsAccepted(t *testing.T) {
|
|
claimant, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
declarer := Manifest{Module: "someone", Seats: []SeatDeclaration{
|
|
{Name: "the-anything", Scope: ScopeNode, Accepts: []string{"work"}},
|
|
}}
|
|
if problems := CatalogueProblems(Shelf{claimant.Module: claimant, "someone": declarer}); len(problems) != 0 {
|
|
t.Fatalf("a claim on a declared seat was refused: %v", problems)
|
|
}
|
|
}
|
|
|
|
func TestASeatClaimedAtAnotherScopeIsRefused(t *testing.T) {
|
|
_, err := ParseManifest(claimed(`[{"name":"npm-package-registry","scope":"node"}]`))
|
|
if err == nil {
|
|
t.Fatal("a mesh seat was held per node")
|
|
}
|
|
if !strings.Contains(err.Error(), "mesh seat") {
|
|
t.Fatalf("the refusal does not say which scope the seat is: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestADeliveringSeatIsOnlyHeldByAModuleThatProvides(t *testing.T) {
|
|
// Holding it makes the module the mesh's answer for the provision. A module that cannot answer
|
|
// would be the answer anyway, and every consumer would be sent to it.
|
|
raw := []byte(`{"module":"thing","version":"1","claims":[{"name":"git","scope":"mesh"}]}`)
|
|
_, err := ParseManifest(raw)
|
|
if err == nil {
|
|
t.Fatal("a module holding the git seat need not provide git")
|
|
}
|
|
if !strings.Contains(err.Error(), `does not provide "git"`) {
|
|
t.Fatalf("the refusal does not say what is missing: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAClaimThatIsMalformedIsRefusedOnceForThat(t *testing.T) {
|
|
// Not a second time for being unknown: one mistake, one line.
|
|
_, err := ParseManifest(claimed(`[{"name":"Not A Name","scope":"node"}]`))
|
|
if err == nil {
|
|
t.Fatal("a malformed claim was accepted")
|
|
}
|
|
if strings.Contains(err.Error(), "not a seat") {
|
|
t.Fatalf("a malformed claim was also called unknown: %v", err)
|
|
}
|
|
}
|
|
|
|
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
|
|
//
|
|
// Read from the catalogue beside this checkout and from this repository's own manifest, the two
|
|
// places a manifest lives (ADR 0069). The private-network module's manifest is composed in code,
|
|
// and its claim is checked where it is composed.
|
|
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
|
|
paths, _ := filepath.Glob("../../../mesh-catalog/modules/*/module.json")
|
|
if len(paths) == 0 {
|
|
t.Skip("the catalogue is not beside this checkout")
|
|
}
|
|
paths = append(paths, "../../module.json")
|
|
var checked int
|
|
for _, path := range paths {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Leniently, so a manifest refused for something unrelated is not reported as a seat
|
|
// problem, and the seat check below is the only thing this test holds a module to.
|
|
var m Manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatalf("%s: %v", path, err)
|
|
}
|
|
for _, problem := range claimProblems(m) {
|
|
t.Errorf("%s: %s", path, problem)
|
|
}
|
|
checked += len(m.Claims)
|
|
}
|
|
if checked == 0 {
|
|
t.Fatal("no claims were checked, so this proved nothing")
|
|
}
|
|
}
|
|
|
|
// 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]string{"npm-package-registry": "archive"}})
|
|
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)
|
|
}
|
|
}
|