Issue 127 stood because nothing compared the two halves. Every manifest was well-formed on its own and every derivation correct on its own, and no cross-module subscription in the mesh matched anything — a subscription that matches nothing is not an error, it is silence. Two checks, because the mistake is possible at two scales. Per manifest: an event is a local name, and `module.` is refused with the name to write instead. A module emitting under what reads as another module's name is refused too, pointing at the seat, where a name outlives whoever holds it. Across the catalogue: where a consumed event's emitter is present, it must emit that event. It cannot demand a live emitter for everything — a module lives in its own repository and may be installed long before the one whose events it wants — so the rule is narrower and still catches this. It found two real dangling subscriptions the moment it ran. Wildcards were undecided and two manifests needed them: `*` is one name and `**` is the rest, spelled the mesh's way and derived to `>` here and `#` on the old bus. A manifest naming either would stop being true when the wire changed, which is the whole reason names are local. And the field documentation taught the old form, examples included — which is why the drift was uniform across 37 manifests rather than scattered. Nobody was guessing; everybody followed the comment.
109 lines
4.0 KiB
Go
109 lines
4.0 KiB
Go
package broker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// **Do the catalogue's emitters and consumers agree?**
|
|
//
|
|
// This is the check whose absence let issue 127 stand: every manifest was individually well-formed,
|
|
// every derivation individually correct, and no cross-module subscription in the mesh matched
|
|
// anything. A subscription that matches nothing is not an error — it is silence — so nothing
|
|
// anywhere reported it.
|
|
//
|
|
// It compares what one manifest asks to hear against what another says it emits. It cannot demand
|
|
// that every consumed event have a live emitter, because a module lives in its own repository and
|
|
// may be registered long before the one whose events it wants. Where the emitter *is* here, it must
|
|
// emit what the consumer asked for.
|
|
func TestTheCataloguesEmittersAndConsumersAgree(t *testing.T) {
|
|
emitters, consumers, seats := theCataloguesEvents(t)
|
|
|
|
if bad := Disagreements(emitters, consumers, seats); len(bad) > 0 {
|
|
t.Fatalf("%d subscription(s) in the catalogue would match nothing:\n %s",
|
|
len(bad), strings.Join(bad, "\n "))
|
|
}
|
|
}
|
|
|
|
// And the check itself catches the thing it exists for, so it cannot pass by doing nothing.
|
|
func TestTheAgreementCheckCatchesASubscriptionThatMatchesNothing(t *testing.T) {
|
|
bad := Disagreements(
|
|
[]AnEmitter{{Module: "builder", Emits: []string{"built"}}},
|
|
[]AConsumer{{Module: "mesh-catalog", Consumes: []string{"builder.finished"}}},
|
|
nil)
|
|
if len(bad) != 1 {
|
|
t.Fatalf("a consumer asking for an event its emitter does not emit was not caught: %v", bad)
|
|
}
|
|
if !strings.Contains(bad[0], "builder.finished") || !strings.Contains(bad[0], "built") {
|
|
t.Fatalf("the report names neither what was asked for nor what is emitted: %s", bad[0])
|
|
}
|
|
|
|
// A module that is not here is not a disagreement: it may be registered later.
|
|
if bad := Disagreements(nil,
|
|
[]AConsumer{{Module: "plex", Consumes: []string{"sonarr.download.completed"}}}, nil); len(bad) != 0 {
|
|
t.Fatalf("a consumer whose emitter is not installed was reported: %v", bad)
|
|
}
|
|
|
|
// A wildcard over emitters is deliberate and names no particular event to check.
|
|
if bad := Disagreements([]AnEmitter{{Module: "sonarr", Emits: []string{"download.completed"}}},
|
|
[]AConsumer{{Module: "plex", Consumes: []string{"*.download.completed"}}}, nil); len(bad) != 0 {
|
|
t.Fatalf("a wildcard over emitters was reported: %v", bad)
|
|
}
|
|
|
|
// An event published under a seat's name is real even though no module declares it as its own.
|
|
if bad := Disagreements(nil,
|
|
[]AConsumer{{Module: "watcher", Consumes: []string{"mesh-artifact-store.image.pushed"}}},
|
|
[]DeclaredSeat{{Name: "mesh-artifact-store", Emits: []string{"image.pushed"}}}); len(bad) != 0 {
|
|
t.Fatalf("an event a seat emits was reported as matching nothing: %v", bad)
|
|
}
|
|
}
|
|
|
|
func theCataloguesEvents(t *testing.T) ([]AnEmitter, []AConsumer, []DeclaredSeat) {
|
|
t.Helper()
|
|
root := filepath.Join("..", "..", "..", "mesh-catalog", "modules")
|
|
entries, err := os.ReadDir(root)
|
|
if err != nil {
|
|
t.Skipf("catalogue sibling not present: %v", err)
|
|
}
|
|
var emitters []AnEmitter
|
|
var consumers []AConsumer
|
|
var seats []DeclaredSeat
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(root, e.Name(), "module.json"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
var m struct {
|
|
Module string `json:"module"`
|
|
Emits []string `json:"emits"`
|
|
Consumes []string `json:"consumes"`
|
|
Seats []struct {
|
|
Name string `json:"name"`
|
|
Emits []string `json:"emits"`
|
|
} `json:"seats"`
|
|
}
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatalf("%s: %v", e.Name(), err)
|
|
}
|
|
if len(m.Emits) > 0 {
|
|
emitters = append(emitters, AnEmitter{Module: m.Module, Emits: m.Emits})
|
|
}
|
|
if len(m.Consumes) > 0 {
|
|
consumers = append(consumers, AConsumer{Module: m.Module, Consumes: m.Consumes})
|
|
}
|
|
for _, s := range m.Seats {
|
|
seats = append(seats, DeclaredSeat{Name: s.Name, Emits: s.Emits})
|
|
}
|
|
}
|
|
if len(emitters) == 0 {
|
|
t.Skip("no manifests found beside this checkout")
|
|
}
|
|
return emitters, consumers, seats
|
|
}
|