From 150641b5a559bccb2f333cb14f578098e7655549 Mon Sep 17 00:00:00 2001 From: Yury Zialionka Date: Thu, 13 Aug 2026 11:46:02 -0600 Subject: [PATCH] perf(test): make git fixtures opt-in for HttpApi exerciser scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #12986, addresses the fixture half of #12999. Every exerciser scenario defaulted to project: { git: true }, spinning up a full git repo (~6 subprocess spawns) per scenario even though only 13 of the 82 routes exercise VCS, worktree, or project-identity behavior. Flip the default to git: false and opt those 13 in explicitly. Non-git directories resolve to the global project (worktree "/"), so path.get, project.current, and v2.session.permission.create — which assert per-project semantics — keep their git fixture. Local effect-mode wall clock: 6m37s -> ~3m45s (315/315 pass in all modes). Coverage mode was measured at ~3s and is already static; no change needed for that part of #12999. --- .../server/httpapi-exercise-scenarios.ts | 3 +++ .../test/server/httpapi-exercise/dsl.ts | 8 +++++--- .../test/server/httpapi-exercise/index.ts | 17 ++++++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts b/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts index b861b6c34b..7e0df05fce 100644 --- a/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts +++ b/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts @@ -209,14 +209,17 @@ export const kiloScenarios: Scenario[] = [ .json(200, object), http.protected .get("/experimental/worktree/diff", "worktree.diff") + .inProject({ git: true }) .at((ctx) => ({ path: "/experimental/worktree/diff?base=HEAD", headers: ctx.headers() })) .json(200, array), http.protected .get("/experimental/worktree/diff/summary", "worktree.diffSummary") + .inProject({ git: true }) .at((ctx) => ({ path: "/experimental/worktree/diff/summary?base=HEAD", headers: ctx.headers() })) .json(200, array), http.protected .get("/experimental/worktree/diff/file", "worktree.diffFile") + .inProject({ git: true }) .at((ctx) => ({ path: `/experimental/worktree/diff/file?${new URLSearchParams({ base: "HEAD", file: "missing.txt" })}`, headers: ctx.headers(), diff --git a/packages/opencode/test/server/httpapi-exercise/dsl.ts b/packages/opencode/test/server/httpapi-exercise/dsl.ts index 959b1a2141..aeb719a9a7 100644 --- a/packages/opencode/test/server/httpapi-exercise/dsl.ts +++ b/packages/opencode/test/server/httpapi-exercise/dsl.ts @@ -22,7 +22,9 @@ class ScenarioBuilder { method, path, name, - project: { git: true }, + // kilocode_change: non-git by default — a git repo costs ~6 subprocess spawns per + // scenario; the few VCS/worktree routes that need one opt in via .inProject({ git: true }). + project: { git: false }, // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- The unseeded builder state is intentionally undefined until `.seeded(...)` narrows it. seed: () => Effect.succeed(undefined as S), request: (ctx) => ({ path, headers: ctx.headers() }), @@ -39,12 +41,12 @@ class ScenarioBuilder { return this.clone({ project: undefined, request: () => ({ path: this.state.path }) }) } - inProject(project: ProjectOptions = { git: true }) { + inProject(project: ProjectOptions = { git: false }) { return this.clone({ project }) } withLlm() { - return this.clone({ project: { ...(this.state.project ?? { git: true }), llm: true } }) + return this.clone({ project: { ...(this.state.project ?? { git: false }), llm: true } }) } at(request: BuilderState["request"]) { diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index 2b022aeb9a..d4d9c5efea 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -116,18 +116,21 @@ const scenarios: Scenario[] = [ }, "status", ), - http.protected.get("/path", "path.get").json(200, (body, ctx) => { + // Non-git directories resolve to the global project (worktree "/"), so scenarios asserting + // per-project identity or project-scoped state need a real git repo. + http.protected.get("/path", "path.get").inProject({ git: true }).json(200, (body, ctx) => { object(body) check(body.directory === ctx.directory, "directory should resolve from x-kilo-directory") check(body.worktree === ctx.directory, "worktree should resolve from x-kilo-directory") }), http.protected.get("/vcs", "vcs.get").json(), - http.protected.get("/vcs/status", "vcs.status").json(200, array), + http.protected.get("/vcs/status", "vcs.status").inProject({ git: true }).json(200, array), http.protected .get("/vcs/diff", "vcs.diff") + .inProject({ git: true }) .at((ctx) => ({ path: "/vcs/diff?mode=git", headers: ctx.headers() })) .json(200, array), - http.protected.get("/vcs/diff/raw", "vcs.diff.raw").status( + http.protected.get("/vcs/diff/raw", "vcs.diff.raw").inProject({ git: true }).status( 200, (_ctx, result) => Effect.sync(() => { @@ -164,7 +167,7 @@ const scenarios: Scenario[] = [ .status(400), http.protected.get("/config/providers", "config.providers").json(), http.protected.get("/project", "project.list").json(200, array, "status"), - http.protected.get("/project/current", "project.current").json( + http.protected.get("/project/current", "project.current").inProject({ git: true }).json( 200, (body, ctx) => { object(body) @@ -577,9 +580,10 @@ const scenarios: Scenario[] = [ })) .json(200, array, "status"), http.protected.get("/experimental/tool/ids", "tool.ids").json(200, array), - http.protected.get("/experimental/worktree", "worktree.list").json(200, array), + http.protected.get("/experimental/worktree", "worktree.list").inProject({ git: true }).json(200, array), http.protected .post("/experimental/worktree", "worktree.create") + .inProject({ git: true }) .mutating() .at((ctx) => ({ path: "/experimental/worktree", headers: ctx.headers(), body: { name: "api-dsl" } })) .jsonEffect( @@ -598,6 +602,7 @@ const scenarios: Scenario[] = [ .status(400), http.protected .delete("/experimental/worktree", "worktree.remove") + .inProject({ git: true }) .mutating() .seeded((ctx) => ctx.worktree({ name: "api-remove" })) .at((ctx) => ({ path: "/experimental/worktree", headers: ctx.headers(), body: { directory: ctx.state.directory } })) @@ -606,6 +611,7 @@ const scenarios: Scenario[] = [ }), http.protected .post("/experimental/worktree/reset", "worktree.reset") + .inProject({ git: true }) .mutating() .seeded((ctx) => ctx.worktree({ name: "api-reset" })) .at((ctx) => ({ @@ -861,6 +867,7 @@ const scenarios: Scenario[] = [ }), http.protected .post("/api/session/{sessionID}/permission", "v2.session.permission.create") + .inProject({ git: true }) .seeded((ctx) => ctx.session({ title: "Permission create owner" })) .at((ctx) => ({ path: route("/api/session/{sessionID}/permission", { sessionID: ctx.state.id }),