| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "slices" |
| 5 | "testing" |
| 6 | |
| 7 | "reasonix/internal/browser" |
| 8 | "reasonix/internal/config" |
| 9 | ) |
| 10 | |
| 11 | // A dead loopback port: the backend is lazy, so a build that never calls a |
| 12 | // browser tool must never notice that nothing is listening there. |
| 13 | const configuredBrowserSection = ` |
| 14 | [browser] |
| 15 | enabled = true |
| 16 | endpoint = "http://127.0.0.1:1" |
| 17 | ` |
| 18 | |
| 19 | // TestConfiguredBrowserBackendStaysOffTheProviderSurface is the cache guard |
| 20 | // for the configured backend: turning [browser] on registers the tools for |
| 21 | // use_capability and leaves the provider request's tool array and the system |
| 22 | // prompt byte-identical to a session with no browser at all. |
| 23 | func TestConfiguredBrowserBackendStaysOffTheProviderSurface(t *testing.T) { |
| 24 | isolateConfigHome(t) |
| 25 | dir := robustTempDir(t) |
| 26 | t.Chdir(dir) |
| 27 | registerBootTokenProfileTestProvider() |
| 28 | |
| 29 | writeFile(t, dir, "reasonix.toml", browserBootConfig) |
| 30 | without := captureBrowserSurface(t, nil) |
| 31 | writeFile(t, dir, "reasonix.toml", browserBootConfig+configuredBrowserSection) |
| 32 | with := captureBrowserSurface(t, nil) |
| 33 | |
| 34 | for _, name := range browser.Names() { |
| 35 | if !slices.Contains(with.registry, name) { |
| 36 | t.Errorf("registry with [browser] enabled lacks %s", name) |
| 37 | } |
| 38 | if slices.Contains(without.registry, name) { |
| 39 | t.Errorf("registry without [browser] registered %s", name) |
| 40 | } |
| 41 | if slices.Contains(with.visible, name) || slices.Contains(with.tools, name) { |
| 42 | t.Errorf("%s leaked into the provider-visible surface", name) |
| 43 | } |
| 44 | } |
| 45 | if !slices.Equal(with.tools, without.tools) { |
| 46 | t.Fatalf("provider tool array changed with [browser] enabled:\nwith %v\nwithout %v", with.tools, without.tools) |
| 47 | } |
| 48 | if with.prompt != without.prompt { |
| 49 | t.Fatalf("system prompt changed with [browser] enabled: %s", firstDivergence(without.prompt, with.prompt)) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func TestBrowserBackendPrefersTheHostBrowser(t *testing.T) { |
| 54 | host := bootBrowserExecutor{} |
| 55 | // A host that already brought a browser keeps it, and owes no shutdown: |
| 56 | // the desktop shell and the SSH broker outlive one controller. |
| 57 | roots := []string{t.TempDir()} |
| 58 | exec, shutdown := browserBackend(host, config.BrowserConfig{Enabled: true}, roots) |
| 59 | if exec == nil || shutdown != nil { |
| 60 | t.Fatalf("host browser = %v with shutdown %v, want the host executor and no shutdown", exec, shutdown != nil) |
| 61 | } |
| 62 | if exec, shutdown := browserBackend(nil, config.BrowserConfig{}, roots); exec != nil || shutdown != nil { |
| 63 | t.Fatalf("disabled backend = %v, want no browser", exec) |
| 64 | } |
| 65 | exec, shutdown = browserBackend(nil, config.BrowserConfig{Enabled: true}, roots) |
| 66 | if exec == nil || shutdown == nil { |
| 67 | t.Fatal("an enabled backend must return an executor and the shutdown that reaps it") |
| 68 | } |
| 69 | shutdown() |
| 70 | } |
| 71 |