Files
mesh-controller/internal/link/conformance_test.go
T

151 lines
5.7 KiB
Go

package link
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"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 the one this tree's go.mod pins, never the one a desktop happens to hold beside this
// checkout (novox/hq issue 449):
//
// - in a merge check, the fixture as it stands at that pin in the mesh-sdk clone the build seat places
// beside the check — read with `git show`, because the clone is checked out at the *running*
// controller's pin, and a pull request moving the SDK must be judged against the SDK it moves to, or it
// passes the check and fails everywhere after it rolls out. A pin the clone lacks fails the test; a
// missing clone reads the captured copy and says so loudly, until mesh-controller #220 has rolled out
// and the build seat clones mesh-sdk (then its follow-up makes a missing clone fail again);
// - elsewhere, the copy captured in testdata/beside at the commit testdata/beside/CAPTURED names, held to
// go.mod by TestTheCapturedSDKIsTheOneGoModPins.
//
// In a merge check the captured copy is also compared with the clone at the captured commit, byte for byte,
// so a hand-edited copy cannot pass for the SDK's.
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"`
}
// sdkModule is the Go module of the SDK the controller is built against.
const sdkModule = "git.novox.be/novox/mesh-sdk/go"
// The tree's go.mod, and the captured copy, as this package finds them.
var (
goModFile = filepath.Join("..", "..", "go.mod")
capturedSDK = filepath.Join(beside.Captured(), "mesh-sdk")
capturedLog = filepath.Join(beside.Captured(), "CAPTURED")
)
// notJudged opens what a merge check without the SDK's clone says; saidNotJudged says it once on stderr.
const notJudged = "NOT JUDGED AGAINST THE CLONE"
var saidNotJudged sync.Once
// sdkFixture is one conformance fixture and where it was read: from the mesh-sdk clone in root (a merge
// check's MESH_CHECK_BESIDE) at the pin of goMod, or, with no root, from the captured copy.
// gitShow is one file of a repository at a ref. safe.directory, because the clone is the build seat's and
// the test may run as another user.
// pseudoCommit is the commit a Go pseudo-version names: v0.1.11-0.20261009143344-f047d0a4a970 → f047d0a4a970.
var pseudoCommit = regexp.MustCompile(`-([0-9a-f]{12})$`)
// sdkRef is the SDK repository's ref a go.mod pins: a pseudo-version's commit, or a release's tag (the SDK
// tags its Go module under go/).
func sdkRef(goMod string) (string, error) {
raw, err := os.ReadFile(goMod)
if err != nil {
return "", err
}
version := ""
for _, line := range strings.Split(string(raw), "\n") {
fields := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "require "))
if len(fields) >= 2 && fields[0] == sdkModule {
version = fields[1]
}
}
if m := pseudoCommit.FindStringSubmatch(version); m != nil {
return m[1], nil
}
if strings.HasPrefix(version, "v") {
return "go/" + version, nil
}
return "", fmt.Errorf("%s pins no version of %s that names a commit or a tag", goMod, sdkModule)
}
// capturedCommit is the commit testdata/beside/CAPTURED names for mesh-sdk.
func capturedCommit(capturedLog string) (string, error) {
raw, err := os.ReadFile(capturedLog)
if err != nil {
return "", err
}
at := regexp.MustCompile(`(?m)^mesh-sdk\s+([0-9a-f]{40})\s`).FindSubmatch(raw)
if at == nil {
return "", fmt.Errorf("%s names no commit for mesh-sdk", capturedLog)
}
return string(at[1]), nil
}
// 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) {
pinned, err := sdkRef(goModFile)
if err != nil {
t.Fatal(err)
}
at, err := capturedCommit(capturedLog)
if err != nil {
t.Fatal(err)
}
if strings.HasPrefix(pinned, "go/") || !strings.HasPrefix(at, pinned) {
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, pinned)
}
}
// Every header the fixture requires is one this implementation actually sets.
// 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.
// The subject a module's event lands on is derived, not carried — so this implementation must
// derive the same one the fixture names.
// 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",
}
}