Files
opencode/packages/opencode/test/server/httpapi-public-openapi.test.ts

133 lines
4.9 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { OpenApi } from "effect/unstable/httpapi"
import { PublicApi } from "../../src/server/routes/instance/httpapi/public"
type Method = "get" | "post" | "put" | "delete" | "patch"
type OpenApiSchema = { readonly $ref?: string }
type OpenApiResponse = {
readonly description?: string
readonly content?: Record<string, { readonly schema?: OpenApiSchema }>
}
type OpenApiOperation = {
readonly responses?: Record<string, OpenApiResponse>
readonly security?: unknown
}
type OpenApiPathItem = Partial<Record<Method, OpenApiOperation>>
type OpenApiSpec = { readonly paths: Record<string, OpenApiPathItem> }
const methods = ["get", "post", "put", "delete", "patch"] as const
const allowedV2BuiltInEndpointErrors: string[] = []
function v2Operations(spec: OpenApiSpec) {
return Object.entries(spec.paths).flatMap(([path, item]) =>
path.startsWith("/api/")
? methods.flatMap((method) => {
const operation = item[method]
return operation ? [{ method, path, operation }] : []
})
: [],
)
}
function responseRef(response: OpenApiResponse | undefined) {
return response?.content?.["application/json"]?.schema?.$ref
}
function componentName(ref: string) {
return ref.replace("#/components/schemas/", "")
}
function isBuiltInEndpointError(name: string) {
return name.startsWith("EffectHttpApiError") || name.startsWith("effect_HttpApiError_")
}
describe("PublicApi OpenAPI v2 errors", () => {
test("preserves /api auth responses", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
for (const route of v2Operations(spec)) {
expect(route.operation.responses?.["401"], `${route.method.toUpperCase()} ${route.path}`).toBeDefined()
expect(route.operation.security, `${route.method.toUpperCase()} ${route.path}`).toEqual([])
}
})
test("does not rewrite /api endpoint errors to legacy error components", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
const refs = v2Operations(spec)
.flatMap((route) =>
Object.entries(route.operation.responses ?? {}).flatMap(([status, response]) => {
const ref = responseRef(response)
return ref ? [`${route.method.toUpperCase()} ${route.path} ${status} ${componentName(ref)}`] : []
}),
)
.filter((entry) => entry.endsWith(" BadRequestError") || entry.endsWith(" NotFoundError"))
expect(refs).toEqual([])
})
test("new /api endpoint errors cannot use built-in components without an explicit allowlist", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
const builtInEndpointErrors = v2Operations(spec)
.flatMap((route) =>
Object.entries(route.operation.responses ?? {}).flatMap(([status, response]) => {
if (status === "401") return []
const ref = responseRef(response)
if (!ref) return []
const name = componentName(ref)
return isBuiltInEndpointError(name) ? [`${route.method.toUpperCase()} ${route.path} ${status} ${name}`] : []
}),
)
.sort()
expect(builtInEndpointErrors).toEqual(allowedV2BuiltInEndpointErrors)
})
test("documents v2 provider and model catalog errors", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
expect(componentName(responseRef(spec.paths["/api/provider"]?.get?.responses?.["503"]) ?? "")).toBe(
"ServiceUnavailableError",
)
expect(componentName(responseRef(spec.paths["/api/model"]?.get?.responses?.["503"]) ?? "")).toBe(
"ServiceUnavailableError",
)
expect(componentName(responseRef(spec.paths["/api/provider/{providerID}"]?.get?.responses?.["404"]) ?? "")).toBe(
"ProviderNotFoundError",
)
expect(componentName(responseRef(spec.paths["/api/provider/{providerID}"]?.get?.responses?.["503"]) ?? "")).toBe(
"ServiceUnavailableError",
)
})
test("documents v2 session not-found errors", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
for (const route of [
["post", "/api/session/{sessionID}/prompt"],
["post", "/api/session/{sessionID}/compact"],
["post", "/api/session/{sessionID}/wait"],
["get", "/api/session/{sessionID}/context"],
["get", "/api/session/{sessionID}/message"],
] as const) {
expect(componentName(responseRef(spec.paths[route[1]]?.[route[0]]?.responses?.["404"]) ?? "")).toBe(
"SessionNotFoundError",
)
}
})
test("documents v2 unfinished session mutation errors", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
for (const route of [
["post", "/api/session/{sessionID}/prompt"],
["post", "/api/session/{sessionID}/compact"],
["post", "/api/session/{sessionID}/wait"],
] as const) {
expect(componentName(responseRef(spec.paths[route[1]]?.[route[0]]?.responses?.["503"]) ?? "")).toBe(
"ServiceUnavailableError",
)
}
})
})