Route templates: a route named x-* serves any request route x-<something>

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-25 13:38:24 -07:00
parent 4863e53eb8
commit 4e1dd03d07
10 changed files with 344 additions and 9 deletions
+115
View File
@@ -0,0 +1,115 @@
package config_test
import (
"strings"
"testing"
"git.wntrmute.dev/kyle/crossbar/internal/config"
)
const templateBase = `
listen = "127.0.0.1:1"
[hosts.a]
base_url = "http://a:1"
models = { "m" = { }, "n" = { } }
[routes."opencode-*"]
hosts = ["a"]
default_model = "m"
[routes."opencode-rust-*"]
hosts = ["a"]
default_model = "n"
[routes.opencode-fixed]
hosts = ["a"]
[routes.paper]
hosts = ["a"]
`
// A route whose name ends in "-*" is a template: any request route that starts with the part
// before the star, with something after it, uses that route's config. An exact name wins over a
// template; the longest matching template wins over shorter ones.
func TestRouteTemplatesResolve(t *testing.T) {
c, err := config.Parse(strings.NewReader(templateBase))
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name, wantKey, wantModel string
ok bool
}{
{"paper", "paper", "", true},
{"opencode-fixed", "opencode-fixed", "", true}, // exact beats template
{"opencode-projecta-4242", "opencode-*", "m", true}, // template
{"opencode-rust-a-7", "opencode-rust-*", "n", true}, // longest template wins
{"opencode-", "", "", false}, // nothing after the prefix
{"opencode", "", "", false}, // the dash is part of the prefix
{"opencodex", "", "", false}, // not a prefix match
{"opencode-*", "", "", false}, // a literal star is never a request route
{"Opencode-A", "", "", false}, // not a valid route name
{"nope", "", "", false},
} {
r, key, ok := c.Route(tc.name)
if ok != tc.ok || key != tc.wantKey || (ok && r.DefaultModel != tc.wantModel) {
t.Errorf("Route(%q) = (%+v, %q, %v), want key %q model %q ok %v", tc.name, r, key, ok, tc.wantKey, tc.wantModel, tc.ok)
}
}
}
func TestRouteTemplateNamesAreValidated(t *testing.T) {
for name, tc := range map[string]struct {
route string
wantErr string
}{
"star in the middle": {`"open*code"`, "routes.open*code"},
"star without dash": {`"opencode*"`, "routes.opencode*"},
"bare star": {`"*"`, "routes.*"},
"double star": {`"opencode-**"`, "routes.opencode-**"},
} {
t.Run(name, func(t *testing.T) {
text := "listen = \"127.0.0.1:1\"\n[hosts.a]\nbase_url = \"http://a:1\"\nmodels = { \"m\" = { } }\n[routes." + tc.route + "]\nhosts = [\"a\"]\n"
_, err := config.Parse(strings.NewReader(text))
ce, ok := err.(*config.Error)
if !ok || ce.Field != tc.wantErr {
t.Fatalf("err = %v, want *config.Error on %q", err, tc.wantErr)
}
})
}
// A template alone satisfies "at least one route".
if _, err := config.Parse(strings.NewReader("listen = \"127.0.0.1:1\"\n[hosts.a]\nbase_url = \"http://a:1\"\nmodels = { \"m\" = { } }\n[routes.\"x-*\"]\nhosts = [\"a\"]\n")); err != nil {
t.Errorf("a template-only config must parse: %v", err)
}
}
// broadcasts: a wake target may name several broadcast addresses (a host that roams between two
// Wi-Fi networks). `broadcast` (one) and `broadcasts` (a list) are alternatives: exactly one.
func TestWakeBroadcasts(t *testing.T) {
head := "listen = \"127.0.0.1:1\"\n[hosts.a]\nbase_url = \"http://a:1\"\nmodels = { \"m\" = { } }\n[hosts.a.wake]\nmac = \"aa:bb:cc:dd:ee:ff\"\n"
tail := "\n[routes.r]\nhosts = [\"a\"]\n"
c, err := config.Parse(strings.NewReader(head + `broadcasts = ["192.168.88.255:9", "192.168.1.255:9"]` + tail))
if err != nil {
t.Fatal(err)
}
if got := c.Hosts["a"].Wake.Addresses(); len(got) != 2 || got[0] != "192.168.88.255:9" || got[1] != "192.168.1.255:9" {
t.Errorf("Addresses() = %v, want both, in order", got)
}
c, err = config.Parse(strings.NewReader(head + `broadcast = "192.168.88.255:9"` + tail))
if err != nil {
t.Fatal(err)
}
if got := c.Hosts["a"].Wake.Addresses(); len(got) != 1 || got[0] != "192.168.88.255:9" {
t.Errorf("Addresses() = %v, want the single broadcast", got)
}
for name, body := range map[string]string{
"both": "broadcast = \"192.168.88.255:9\"\nbroadcasts = [\"192.168.1.255:9\"]",
"neither": "wait = \"30s\"",
"empty list": "broadcasts = []",
"bad entry": "broadcasts = [\"192.168.1.255\"]", // no port
} {
t.Run(name, func(t *testing.T) {
_, err := config.Parse(strings.NewReader(head + body + tail))
ce, ok := err.(*config.Error)
if !ok || !strings.HasPrefix(ce.Field, "hosts.a.wake") {
t.Fatalf("err = %v, want *config.Error under hosts.a.wake", err)
}
})
}
}