mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-24 13:25:01 +00:00
86 lines
3.2 KiB
TypeScript
86 lines
3.2 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.includes("BadRequestError") || entry.includes("NotFoundError"))
|
|
|
|
expect(refs).toEqual(["GET /api/provider/{providerID} 404 NotFoundError"])
|
|
})
|
|
|
|
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)
|
|
})
|
|
})
|