ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
144 lines
5.5 KiB
Go
144 lines
5.5 KiB
Go
package resolution
|
|
|
|
import (
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// 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() mcat.Manifest {
|
|
return mcat.Manifest{
|
|
Module: "media",
|
|
Listens: []mcat.Listening{
|
|
{Name: "web", Port: 80, From: mcat.FromMesh, Why: "the app, behind the proxy"},
|
|
{Name: "stream", Port: 32400, From: mcat.FromEverywhere, Fixed: true,
|
|
Why: "the client dials this number; the protocol chose it"},
|
|
},
|
|
Contributes: map[string]map[string]any{
|
|
"route": {"label": "media", mcat.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"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
// 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 := mcat.SettingsBy{"media": {{From: "node anchor", Values: map[string]any{mcat.ReachSetting: map[string]any{"80": mcat.ReachBoth, "32400": mcat.ReachPublic}}}}}
|
|
|
|
r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{m},
|
|
PublicDomain: "example.test", At: "anchor.internal"}
|
|
rules, err := Rules(r, mcat.Rendering{Settings: settings})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, rule := range rules {
|
|
switch rule.Port {
|
|
case 80:
|
|
if rule.From != mcat.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 != mcat.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 := resolutionContributions(r, 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 that names an endpoint still carries that endpoint's port.**
|
|
//
|
|
// Everything downstream reads the port: the provider is told where to reach the consumer, and the
|
|
// redirection that turns a declared port into the number the machine published is keyed on it. A route
|
|
// naming only its endpoint left the proxy with no port, and a proxy with no port has nothing to dial.
|
|
//
|
|
// Caught after the catalogue had already been changed to name endpoints, and before the mesh picked
|
|
// those manifests up — which is the only reason nothing broke.
|
|
func TestARouteNamingAnEndpointStillCarriesItsPort(t *testing.T) {
|
|
m := aMediaServer()
|
|
r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{m},
|
|
PublicDomain: "example.test", At: "anchor.internal"}
|
|
given, err := resolutionContributions(r, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var saw bool
|
|
for _, c := range given["route"] {
|
|
saw = true
|
|
port, ok := asPort(c.Values["port"])
|
|
if !ok {
|
|
t.Fatalf("the route carries no port, so the proxy has nothing to dial: %v", c.Values)
|
|
}
|
|
if port != 80 {
|
|
t.Fatalf("the route carries port %d, want the web endpoint's 80", port)
|
|
}
|
|
}
|
|
if !saw {
|
|
t.Fatal("the module contributed no route")
|
|
}
|
|
}
|
|
|
|
// And the declared port, not the machine one: the redirection to where the machine published it
|
|
// happens later and is keyed on the declared number, so filling the machine port in here would be
|
|
// redirected twice or not at all.
|
|
func TestTheEndpointsDeclaredPortIsFilledInNotTheMachineOne(t *testing.T) {
|
|
m := aMediaServer()
|
|
values := map[string]any{mcat.RouteEndpoint: "web", "label": "media"}
|
|
portOfEndpoint(values, endpointPorts(m))
|
|
if got, _ := asPort(values["port"]); got != 80 {
|
|
t.Fatalf("filled in port %d, want the declared 80", got)
|
|
}
|
|
// Then the ordinary redirection puts it where the machine published it.
|
|
moved := atMachinePort(values, m.Module, map[string]map[int]int{"media": {80: 20009}})
|
|
if got, _ := asPort(moved["port"]); got != 20009 {
|
|
t.Fatalf("after redirection the port is %d, want the machine's 20009", got)
|
|
}
|
|
}
|
|
|
|
// A route that repeats a port keeps it, because that is the older shape and still read.
|
|
func TestARouteThatRepeatsItsPortKeepsIt(t *testing.T) {
|
|
values := map[string]any{mcat.RouteEndpoint: "web", "port": 8080}
|
|
portOfEndpoint(values, map[string]int{"web": 80})
|
|
if got, _ := asPort(values["port"]); got != 8080 {
|
|
t.Fatalf("the port it stated was overwritten with %d", got)
|
|
}
|
|
}
|