Files
mesh-controller/internal/catalogue/endpoint_test.go
T
jschoubben c68d3a7432 An assignment configures an endpoint as one thing
novox/hq ADR 0138, completing it. One block per endpoint instead of three keys
joined by a port number:

  {"endpoints": {"web":    {"port": 20009, "label": "cinema", "reach": "both"},
                 "stream": {"reach": "internal"}}}

Which machine port it lands on, the subdomain a proxy serves it under, and how far
it reaches are the three things an operator says when a module is assigned, and they
were said in ports, in the route's label and in reach — each keyed by the port. A
module with two endpoints of different shapes could only be configured by a reader
who knew which number was which.

Every field is optional; a block that says only a reach leaves the port to the mesh
and the label to the module, which is the ordinary case. A name the module does not
declare is refused, and the refusal lists what it does declare. A port or a reach
said both here and through the older key is refused rather than merged — two places
saying one thing is what this key exists to end, and merging would follow whichever
was read last.

Eight tests. The reach assertion deliberately narrows what the manifest says, because
a reach that agrees with the manifest proves nothing about whether the block was read
— which I found by writing the weaker version first and watching a revert not fail.
2026-09-29 11:25:40 +02:00

148 lines
5.6 KiB
Go

package catalogue
import (
"strings"
"testing"
)
// aMediaServer is the shape one port number per key cannot express: two endpoints of different kinds.
// A web surface a proxy serves under a subdomain, and a protocol port clients dial directly because
// the client expects that number.
func aMediaServer() Manifest {
return Manifest{
Module: "media",
Listens: []Listening{
{Name: "web", Port: 80, From: FromMesh, Why: "the app, behind the proxy"},
{Name: "stream", Port: 32400, From: FromEverywhere, Fixed: true,
Why: "the client dials this number; the protocol chose it"},
},
Contributes: map[string]map[string]any{
"route": {"label": "media", RouteEndpoint: "web"},
},
// Both endpoints are published by its container, which is what lets a machine port be given
// for either: the mesh moves a port the module publishes, never one it merely listens on.
Resources: []map[string]any{
{"id": "server", "type": "container", "name": "media",
"ports": []any{"80", "32400"}},
},
}
}
// **A route names the endpoint it serves.** A route and a listen both carried a port and nothing said
// they were the same thing; now one of them says so.
func TestARouteNamesTheEndpointItServes(t *testing.T) {
m := aMediaServer()
if port, ok := EndpointPort(m, "web"); !ok || port != 80 {
t.Fatalf("the web endpoint resolves to %d (%v), want 80", port, ok)
}
if port, ok := EndpointPort(m, "stream"); !ok || port != 32400 {
t.Fatalf("the stream endpoint resolves to %d (%v), want 32400", port, ok)
}
if _, ok := EndpointPort(m, "absent"); ok {
t.Fatal("an endpoint the module does not declare resolved to a port")
}
}
// And the routed set is read through the name, so the endpoint the proxy serves is known without a
// reader joining two numbers.
func TestTheRoutedEndpointIsFoundByName(t *testing.T) {
routed := RoutedPorts(aMediaServer())
if !routed[80] {
t.Fatalf("the routed endpoint was not found by name: %v", routed)
}
// And the directly-dialled one is not routed, which is what lets its reach govern its port.
if routed[32400] {
t.Fatalf("the endpoint clients dial directly reads as routed: %v", routed)
}
}
// **Two endpoints of different shapes, configured as themselves.** The web endpoint's reach asks for
// names and leaves its port to the proxy; the stream endpoint's reach governs its port, because
// clients dial it and there is no name.
func TestTwoEndpointsOfDifferentShapesAreConfiguredSeparately(t *testing.T) {
m := aMediaServer()
settings := SettingsBy{"media": {{From: "node anchor", Values: map[string]any{
ReachSetting: map[string]any{"80": ReachBoth, "32400": ReachPublic},
}}}}
r := Resolution{Node: "anchor", Modules: []Manifest{m},
PublicDomain: "example.test", At: "anchor.internal"}
rules, err := r.Rules(Rendering{Settings: settings})
if err != nil {
t.Fatal(err)
}
for _, rule := range rules {
switch rule.Port {
case 80:
if rule.From != FromMesh {
t.Fatalf("the routed endpoint's port opened to %q; the proxy is how it is reached",
rule.From)
}
case 32400:
if rule.From != FromEverywhere {
t.Fatalf("the directly-dialled endpoint's port is %q, want anywhere", rule.From)
}
}
}
// And the routed one carries both names, asked for by the same statement.
given, err := r.contributions(settings, nil, nil)
if err != nil {
t.Fatal(err)
}
var public, internal string
for _, c := range given["route"] {
public, _ = c.Values["name"].(string)
internal, _ = c.Values["internal-name"].(string)
}
if public != "media.example.test" || internal != "media.anchor.internal" {
t.Fatalf("names are %q and %q, want both", public, internal)
}
}
// A route naming an endpoint the module does not declare reaches nothing, and is refused where it is
// written rather than resolving to no port and serving nothing.
func TestARouteNamingAnEndpointTheModuleLacksIsRefused(t *testing.T) {
m := aMediaServer()
m.Contributes["route"][RouteEndpoint] = "absent"
got := strings.Join(RouteProblems(m), "\n")
if !strings.Contains(got, "does not declare") {
t.Fatalf("a route naming an absent endpoint was accepted:\n%s", got)
}
}
// **Two endpoints called the same would make an assignment configure whichever was read last.** The
// point of a name is that it identifies one thing.
func TestTwoEndpointsWithOneNameAreRefused(t *testing.T) {
m := Manifest{Module: "twice", Listens: []Listening{
{Name: "web", Port: 80, From: FromMesh},
{Name: "web", Port: 8080, From: FromMesh},
}}
got := strings.Join(endpointNameProblems(m), "\n")
if !strings.Contains(got, "could mean either") {
t.Fatalf("two endpoints with one name were accepted:\n%s", got)
}
}
// A name that is not a name is refused where it is written: it ends up in something a person types.
func TestAnEndpointNameIsHeldToItsShape(t *testing.T) {
for _, wrong := range []string{"Web", "web port", "3000", "-web", "web_surface"} {
m := Manifest{Module: "odd", Listens: []Listening{{Name: wrong, Port: 80, From: FromMesh}}}
if got := strings.Join(endpointNameProblems(m), "\n"); !strings.Contains(got, "a name is lowercase") {
t.Fatalf("%q was accepted as an endpoint name:\n%s", wrong, got)
}
}
}
// **Every endpoint in the catalogue is unnamed today, and must stay valid.** The word ships one
// release before anything uses it.
func TestAnUnnamedEndpointIsStillValid(t *testing.T) {
m := Manifest{Module: "ordinary", Listens: []Listening{{Port: 443, From: FromEverywhere}}}
if got := endpointNameProblems(m); len(got) != 0 {
t.Fatalf("an unnamed endpoint was refused: %v", got)
}
if got := RouteProblems(m); len(got) != 0 {
t.Fatalf("a module with no route was refused: %v", got)
}
}