Files
mesh-controller/internal/catalogue/route_test.go
T
jschoubben 01d57b629f A route says the largest body its proxy may carry, and both proxies honour it
The registry's public name is served by the predecessor with a twenty-gigabyte buffering
middleware, because a registry takes image layers in single requests of gigabytes and a
proxy's default turns every push into a 413 the registry never sees. A route contribution
had no way to say so, so the mesh could not take the name over without losing what made it
usable.

The contribution now carries `max-request-body`, a whole positive number of bytes, and the
catalogue holds every route to an agreed vocabulary — label or name, port, and the limit —
refusing a key no proxy reads (a field that parses cleanly and does nothing is a promise
nobody keeps) and a route with no port (unreachable by the proxy it just asked for, found at
parse time rather than in a proxy's log). The mesh's own proxy reads the limit as written,
refuses a body past it as 413 rather than the 502 the transport would have reported, and
skips a route whose limit it cannot read rather than carrying what the module said not to.

The registry's hand-over itself is read from the catalogue beside this checkout: the store
still resolves with no proxy, the gate beside it pulls the store in, contributes the
predecessor's name on the port the node gave it, and locks only the door that faces the
world.

hq ADR 0082/0104, the registry hand-over.
2026-09-23 23:19:12 +02:00

129 lines
5.1 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
)
// A route contribution has an agreed vocabulary (route.go), and the parser holds a manifest to it:
// a key no proxy reads is refused rather than carried, a route with no port is refused rather than
// skipped by the proxy it asked for, and a body limit is a whole number of bytes or nothing.
//
// The limit is here for the registry's hand-over (novox/hq ADR 0082, ADR 0104): a registry takes
// image layers in single requests of gigabytes, and the predecessor served its public name with a
// twenty-gigabyte middleware. A route that could not say so would have a name it could not be
// pushed to.
func routed(contribution string) ([]byte, error) {
raw := []byte(`{"module":"app","version":"1","contributes":{"route":` + contribution + `}}`)
_, err := ParseManifest(raw)
return raw, err
}
func TestARouteWithALabelAndAPortIsAccepted(t *testing.T) {
if _, err := routed(`{"label":"git","port":3000}`); err != nil {
t.Fatalf("the shape every routed module in the catalogue writes was refused: %v", err)
}
// The legacy shape — a full name and no label — still passes, so the catalogue can migrate
// module by module (ADR 0066).
if _, err := routed(`{"name":"git.example","port":3000}`); err != nil {
t.Fatalf("a legacy full-name contribution was refused: %v", err)
}
// And a limit on what may be pushed through it.
if _, err := routed(`{"label":"registry-api","port":5001,"max-request-body":21474836480}`); err != nil {
t.Fatalf("a route with a body limit was refused: %v", err)
}
}
func TestARouteKeyNoProxyReadsIsRefusedByName(t *testing.T) {
_, err := routed(`{"label":"git","port":3000,"basic-auth":true,"timeout":30}`)
if err == nil {
t.Fatal("a route carrying keys no proxy reads was accepted; the module goes on believing " +
"its route is limited when it is not")
}
for _, want := range []string{`"basic-auth"`, `"timeout"`, "max-request-body"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("the refusal does not name %s, leaving the author guessing: %v", want, err)
}
}
}
func TestARouteWithNoPortIsRefused(t *testing.T) {
// A module that asked for a route and not for the port is unreachable by the proxy it just
// asked for (08-connectivity §3) — found at parse time, not in a proxy's log.
if _, err := routed(`{"label":"git"}`); err == nil || !strings.Contains(err.Error(), "port") {
t.Fatalf("a route with nowhere to send it was accepted: %v", err)
}
for _, bad := range []string{`"3000"`, `0`, `70000`, `3000.5`, `true`} {
if _, err := routed(`{"label":"git","port":` + bad + `}`); err == nil {
t.Errorf("a route on port %s was accepted, and that is not a port", bad)
}
}
// And a route that names nothing.
if _, err := routed(`{"port":3000}`); err == nil || !strings.Contains(err.Error(), "label") {
t.Fatalf("a route naming nothing was accepted: %v", err)
}
if _, err := routed(`{"label":"","port":3000}`); err == nil {
t.Fatal("a route with an empty label was accepted")
}
}
func TestABodyLimitIsAWholePositiveNumberOfBytes(t *testing.T) {
for _, bad := range []string{`"20g"`, `"21474836480"`, `0`, `-1`, `1.5`, `true`, `null`} {
_, err := routed(`{"label":"registry-api","port":5001,"max-request-body":` + bad + `}`)
if err == nil || !strings.Contains(err.Error(), "max-request-body") {
t.Errorf("a body limit of %s was accepted, or refused without naming the key: %v", bad, err)
}
}
for _, v := range []any{float64(1), float64(21474836480), 1024, int64(4096)} {
if _, ok := RouteBodyLimit(v); !ok {
t.Errorf("%v (%T) is a whole positive number of bytes and was refused", v, v)
}
}
for _, v := range []any{"1", float64(0), float64(0.5), nil, true} {
if n, ok := RouteBodyLimit(v); ok {
t.Errorf("%v (%T) was read as a limit of %d bytes", v, v, n)
}
}
}
// The limit reaches the proxy exactly as written, beside the composed name and the port — the
// mesh carries it and interprets nothing.
func TestABodyLimitReachesTheProxyUnchanged(t *testing.T) {
registry := Manifest{Module: "gate", Version: "1",
Contributes: map[string]map[string]any{
"route": {"label": "registry-api", "port": 5001, "max-request-body": float64(21474836480)},
}}
proxy := Manifest{Module: "proxy", Version: "1",
Provides: Offers("route"),
Receives: map[string]string{"route": "/var/lib/proxy/routes.json"}}
got, err := Resolve(shelf(proxy, registry), []string{"gate"}, withDomain("example.test"), World{})
if err != nil {
t.Fatal(err)
}
for _, r := range mustDeclare(t, got) {
if r["path"] != "/var/lib/proxy/routes.json" {
continue
}
var parsed struct {
Given []Contribution `json:"given"`
}
if err := json.Unmarshal([]byte(r["content"].(string)), &parsed); err != nil {
t.Fatal(err)
}
if len(parsed.Given) != 1 {
t.Fatalf("the proxy was given %d routes", len(parsed.Given))
}
v := parsed.Given[0].Values
if v["name"] != "registry-api.example.test" {
t.Errorf("the name did not compose: %v", v)
}
if limit, ok := RouteBodyLimit(v["max-request-body"]); !ok || limit != 21474836480 {
t.Errorf("the body limit did not reach the proxy as written: %v", v["max-request-body"])
}
return
}
t.Fatal("the proxy was given no file")
}