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