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"}, }, } } // **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) } }