CodeNomad/scripts/test-websearch-settings-native.mjs
Pascal André ee77c1e491
feat(settings): scoped web search configuration (4/8) (#792)
## Behavior
Global/Project websearch choice, inherited/default, disabled and
automatic/provider selection. Display effective native configuration.
Optional global API keys use native integration/credential endpoints;
environment credentials remain read-only. Scoped failures reconcile
without mutation replay, and late results are fenced.

## Contract adjustment
OpenChamber's /api/config/websearch is its backend route. OpenCode
2.0.18 has no equivalent native config write API. Reuse CodeNomad's
authorized plugin-control configuration targets, JSONC conflict-safe
edits, WSL filesystem adapter and connection/deletion fences. Preserve
unrelated fields and comments; use native watching, never implicit
location.reload.

## Validation
Server/UI typechecks pass. 51 focused server/config regression tests
pass; four real-WSL cases skipped. Real Chromium fixture passes scoped
writes, failure reconciliation, API-key clearing and late mutation
fencing; narrow capture inspected. Isolated native fixtures pass on
2.0.7 and 2.0.18 (global watching, project document persistence/reset,
foreign-directory rejection). Project discovery is disabled in native
fixtures to exclude user ancestor config; deterministic server tests
cover precedence. No shared daemon or personal data used.

## Stack/review
Depends on #791, zero findings at ea3b4ef6. Independent gatekeeper
pending; fix/re-review until zero. User requires all PRs remain
unmerged.

Size notes: existing http-server.ts (~2300), api-types.ts (~1200),
plugin-controls.ts (~810), api-client.ts (~1000) and locale settings
files exceed guidance; focused changes only.


## User-feedback correction
Clarify which search provider agents use, automatic saves, the default
across projects and the current-project override. Move API keys into a
separate collapsed section explaining that credentials are shared
service-wide and saving a key does not change the selected provider.
Environment credentials are explicitly read-only. Updated in all ten
locales; narrow Chromium lifecycle/failure test and UI typecheck pass.
Alpha in fixture captures is synthetic test data.
2026-10-02 14:49:18 +02:00

37 lines
2.6 KiB
JavaScript

import assert from "node:assert/strict"
import { readFile, writeFile } from "node:fs/promises"
import path from "node:path"
import { setTimeout as delay } from "node:timers/promises"
import { withProductRuntime } from "./native-product-fixture.mjs"
import { PluginControls } from "../packages/server/src/opencode/plugin-controls.ts"
import { WebSearchSettings } from "../packages/server/src/opencode/websearch-settings.ts"
let configurationDirectory
await withProductRuntime(process.argv[2], async ({ configDirectory }) => {
configurationDirectory = configDirectory
await writeFile(path.join(configDirectory, "opencode.jsonc"), '{\n// retain fixture comment\n"websearch":false\n}\n')
}, async ({ client, root }) => {
await client.location.get({ location: { directory: root } })
const connection = { client, assertCurrent() {} }
const controls = new PluginControls({ workspaceManager: {
get: () => ({}), getSharedServiceConnection: async () => connection,
getServiceDirectoryForPath: async (_id, directory) => directory === root ? root : undefined,
ownsLocation: async (_id, location) => location.directory === root,
getWorktreeIdentityForPath: async () => "fixture-root", getServicePathStyle: () => process.platform === "win32" ? "win32" : "posix",
getServiceWslDistro: () => undefined, getHostPathForServicePath: async (_id, value) => value,
}, worktreeDeletionFence: { enter: () => () => {} }, logger: {} })
const settings = new WebSearchSettings(controls), location = { directory: root }
assert.equal((await settings.read("fixture", location)).effective, false)
await settings.update("fixture", location, "global", "random")
const deadline = Date.now() + 30_000
while ((await settings.read("fixture", location)).effective !== "random" && Date.now() < deadline) await delay(100)
assert.equal((await settings.read("fixture", location)).effective, "random", "Native watcher must apply global edits without location.reload")
assert.match(await readFile(path.join(configurationDirectory, "opencode.jsonc"), "utf8"), /retain fixture comment/)
await settings.update("fixture", location, "project", false)
const project = (await settings.read("fixture", location)).scopes.find(item => item.scope === "project")
assert.equal(project.selection, false)
await settings.update("fixture", location, "project", null)
assert.equal((await settings.read("fixture", location)).scopes.find(item => item.scope === "project").selection, null)
await assert.rejects(settings.update("fixture", { directory: path.dirname(root) }, "global", false))
console.log("PASS native global discovery/hot reload, scoped project persistence/reset and foreign-directory rejection")
})