Files
mesh-controller/internal/link/conformance_test.go
T
jschoubben 4632b6a508 Judge the SDK conformance fixtures against the SDK the controller pins, never a desktop's checkout (issue 449)
A check clones mesh-sdk beside the controller at the commit go.mod pins; elsewhere
the test reads a copy captured at that commit, held to go.mod. A missing clone fails
instead of skipping.
2026-10-11 04:19:01 +02:00

142 lines
5.5 KiB
Go

package link
import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/beside"
)
// The Go implementation, held to the shared fixtures (novox/hq ADR 0074, design 19).
//
// **Read from the SDK's own conformance directory, never copied by hand**: a fixture written into each
// implementation is two fixtures, and two fixtures drift, which is the failure the suite exists to prevent.
// Which SDK is read is named, never the one a desktop happens to hold beside this checkout (novox/hq issue
// 449): in a merge check the SDK the build seat clones beside it, at the commit this controller's go.mod
// pins, and a missing clone fails the test; elsewhere the copy captured in testdata/beside at that same
// commit (internal/beside, testdata/beside/CAPTURED), held to go.mod by TestTheCapturedSDKIsTheOneGoModPins.
type fixture struct {
Name string `json:"name"`
Given struct {
Module string `json:"module"`
Node string `json:"node"`
Key string `json:"key"`
Body map[string]any `json:"body"`
Headers map[string]string `json:"headers"`
} `json:"given"`
Wire struct {
Subject string `json:"subject"`
RequiredHeaders []string `json:"requiredHeaders"`
HeaderFormats map[string]string `json:"headerFormats"`
} `json:"wire"`
}
func loadFixture(t *testing.T, name string) fixture {
t.Helper()
path := filepath.Join(beside.Dir(t, "mesh-sdk"), "conformance", name)
raw, err := os.ReadFile(path)
if err != nil {
// Failed, never skipped: a skip here passed the suite with nothing judged.
t.Fatalf("the SDK's conformance fixture %s: %v", name, err)
}
var f fixture
if err := json.Unmarshal(raw, &f); err != nil {
t.Fatalf("%s: %v", name, err)
}
return f
}
// The captured SDK is the one this controller is built against: when go.mod moves the SDK, the copy moves
// with it, or the tests away from a merge check judge an SDK the controller no longer uses (novox/hq issue
// 449).
func TestTheCapturedSDKIsTheOneGoModPins(t *testing.T) {
mod, err := os.ReadFile(filepath.Join("..", "..", "go.mod"))
if err != nil {
t.Fatal(err)
}
pinned := regexp.MustCompile(`(?m)^\s*git\.novox\.be/novox/mesh-sdk/go v\S+-([0-9a-f]{12})$`).FindSubmatch(mod)
if pinned == nil {
t.Fatal("go.mod pins no commit of git.novox.be/novox/mesh-sdk/go as a pseudo-version; say here how a " +
"release's tag is matched against testdata/beside/CAPTURED")
}
captured, err := os.ReadFile(filepath.Join(beside.Captured(), "CAPTURED"))
if err != nil {
t.Fatal(err)
}
at := regexp.MustCompile(`(?m)^mesh-sdk\s+([0-9a-f]{40})\s`).FindSubmatch(captured)
if at == nil {
t.Fatal("testdata/beside/CAPTURED names no commit for mesh-sdk")
}
if !strings.HasPrefix(string(at[1]), string(pinned[1])) {
t.Errorf("the SDK is captured at %s but go.mod pins %s: capture it again at the pinned commit, as "+
"testdata/beside/CAPTURED says", at[1], pinned[1])
}
}
// Every header the fixture requires is one this implementation actually sets.
func TestTheGoEmitterSetsEveryRequiredHeader(t *testing.T) {
f := loadFixture(t, "events/module-event.json")
sent := goEventHeaders(f.Given.Key, f.Given.Module, f.Given.Node)
for _, want := range f.Wire.RequiredHeaders {
if _, ok := sent[want]; !ok {
t.Errorf("the Go emitter does not set %q, which the fixture requires — an event it "+
"emits is one a conforming consumer refuses", want)
}
}
}
// And each value is in the shape the fixture pins, because a header present but differently
// formatted is the disagreement that does not announce itself.
func TestTheGoEmittersHeaderFormatsMatch(t *testing.T) {
f := loadFixture(t, "events/module-event.json")
sent := goEventHeaders(f.Given.Key, f.Given.Module, f.Given.Node)
if got := sent["content-type"]; got != f.Wire.HeaderFormats["content-type"] {
t.Errorf("content-type is %q, the fixture says %q", got, f.Wire.HeaderFormats["content-type"])
}
if _, err := time.Parse(time.RFC3339, sent["x-time"]); err != nil {
t.Errorf("x-time %q is not RFC3339, which the fixture requires: %v", sent["x-time"], err)
}
if pattern := f.Wire.HeaderFormats["x-event-id"]; pattern != "" {
if !regexp.MustCompile(pattern).MatchString(sent["x-event-id"]) {
t.Errorf("x-event-id %q does not match %q", sent["x-event-id"], pattern)
}
}
// The origin the envelope claims is the one the bus enforces by namespace. A disagreement
// here means the envelope is lying about where it came from.
if sent["x-source"] != f.Given.Module {
t.Errorf("x-source is %q for module %q", sent["x-source"], f.Given.Module)
}
}
// The subject a module's event lands on is derived, not carried — so this implementation must
// derive the same one the fixture names.
func TestTheGoSubjectMatchesTheFixture(t *testing.T) {
f := loadFixture(t, "events/module-event.json")
got := "mesh.mod." + f.Given.Module + ".event." + f.Given.Key
if got != f.Wire.Subject {
t.Errorf("this implementation would publish on %q; the fixture says %q", got, f.Wire.Subject)
}
}
// goEventHeaders is the header set EmitEvent produces, factored so conformance can see it
// without a broker. Kept beside the emitter so the two cannot drift apart silently.
func goEventHeaders(eventType, source, node string) map[string]string {
id, err := eventID()
if err != nil {
panic(err)
}
return map[string]string{
"x-event-id": id,
"x-source": source,
"x-node": node,
"x-time": time.Now().UTC().Format(time.RFC3339),
"content-type": "application/json",
}
}