Hold the Go emitter to the shared fixtures
Every required header set, each value in the pinned shape, and the subject derived the same way. Read from the sdk's conformance directory by sibling path, never copied.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The Go implementation, held to the shared fixtures (novox/hq ADR 0074, design 19).
|
||||
//
|
||||
// **Read from the sdk's conformance directory by sibling path**, the way the lab finds its
|
||||
// siblings — deliberately not copied here. A fixture copied into each implementation is two
|
||||
// fixtures, and two fixtures drift, which is the exact failure the suite exists to prevent.
|
||||
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("..", "..", "..", "mesh-sdk", "conformance", name)
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Skipf("the sdk's conformance fixtures are not beside this checkout: %v", err)
|
||||
}
|
||||
var f fixture
|
||||
if err := json.Unmarshal(raw, &f); err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// 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",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user