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") }