diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 55d9e91..17fcd92 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -13,7 +13,8 @@ "profile:test": "bun run script/profile-test-files.ts", "build": "bun run script/build.ts", "dev": "bun run --conditions=browser ./src/index.ts", - "dev:temporary": "bun run --conditions=browser ./src/temporary.ts" + "dev:temporary": "bun run --conditions=browser ./src/temporary.ts", + "test:error-contracts": "bun test test/server/httpapi-error-contracts-source.test.ts test/server/httpapi-public-openapi.test.ts" }, "bin": { "opencode": "./bin/opencode" diff --git a/packages/opencode/test/server/httpapi-public-openapi.test.ts b/packages/opencode/test/server/httpapi-public-openapi.test.ts index 5139077..7f189f9 100644 --- a/packages/opencode/test/server/httpapi-public-openapi.test.ts +++ b/packages/opencode/test/server/httpapi-public-openapi.test.ts @@ -257,6 +257,90 @@ describe("PublicApi OpenAPI v2 errors", () => { }) }) + test("documents event subscribe BadRequest errors", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + // Public OpenAPI rewrites pure HttpApi BadRequest to the legacy named shape. + expect(componentNames(spec.paths["/event"]?.get?.responses?.["400"])).toContain("BadRequestError") + }) + + test("documents provider list and auth BadRequest errors", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + for (const route of ["/provider", "/provider/auth"]) { + expect(componentNames(spec.paths[route]?.get?.responses?.["400"]), route).toContain( + "effect_HttpApiError_BadRequest", + ) + } + }) + + test("documents instance read and dispose BadRequest errors", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + for (const route of [ + ["post", "/instance/dispose"], + ["get", "/path"], + ["get", "/vcs"], + ["get", "/vcs/status"], + ["get", "/vcs/diff"], + ["get", "/vcs/diff/raw"], + ["get", "/command"], + ["get", "/agent"], + ["get", "/skill"], + ["get", "/lsp"], + ["get", "/formatter"], + ] as const) { + expect(componentNames(spec.paths[route[1]]?.[route[0]]?.responses?.["400"]), `${route[0]} ${route[1]}`).toContain( + "effect_HttpApiError_BadRequest", + ) + } + }) + + test("documents tui route BadRequest errors", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + for (const route of [ + ["post", "/tui/append-prompt"], + ["post", "/tui/open-help"], + ["post", "/tui/open-sessions"], + ["post", "/tui/open-themes"], + ["post", "/tui/open-models"], + ["post", "/tui/submit-prompt"], + ["post", "/tui/clear-prompt"], + ["post", "/tui/execute-command"], + ["post", "/tui/show-toast"], + ["post", "/tui/publish"], + ["post", "/tui/select-session"], + ["get", "/tui/control/next"], + ["post", "/tui/control/response"], + ] as const) { + expect(componentNames(spec.paths[route[1]]?.[route[0]]?.responses?.["400"]), `${route[0]} ${route[1]}`).toContain( + "effect_HttpApiError_BadRequest", + ) + } + }) + + test("documents experimental route BadRequest errors", () => { + const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec + + // Routes that declare HttpApiError.BadRequest and keep 400 in the public surface. + // Note: POST /experimental/console/switch is intentionally stripped by + // normalizeLegacyOperation for legacy SDK compatibility. + for (const route of [ + ["get", "/experimental/capabilities"], + ["get", "/experimental/tool"], + ["get", "/experimental/tool/ids"], + ["get", "/experimental/session"], + ["post", "/experimental/session/{sessionID}/background"], + ["get", "/experimental/resource"], + ["post", "/experimental/project/{projectID}/copy/generate-name"], + ] as const) { + expect(componentNames(spec.paths[route[1]]?.[route[0]]?.responses?.["400"]), `${route[0]} ${route[1]}`).toContain( + "effect_HttpApiError_BadRequest", + ) + } + }) + test("documents v2 unfinished session mutation errors", () => { const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec diff --git a/scripts/check-httpapi-error-contracts.sh b/scripts/check-httpapi-error-contracts.sh new file mode 100755 index 0000000..e89c29b --- /dev/null +++ b/scripts/check-httpapi-error-contracts.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Run structural + public OpenAPI HttpApi error-contract tests (offline-friendly). +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT}/packages/opencode" +exec bun test test/server/httpapi-error-contracts-source.test.ts test/server/httpapi-public-openapi.test.ts