test(cloud): exercise addSpec on the full Cloudflare spec through real postgres
Unified · Split
| @@ -5,6 +5,8 @@ · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | import { describe, expect, it } from "@effect/vitest"; | 6 | import { describe, expect, it } from "@effect/vitest"; |
| 7 | import { Effect } from "effect"; | 7 | import { Effect } from "effect"; |
| 8 | import { readFileSync } from "node:fs"; | ||
| 9 | import { resolve } from "node:path"; | ||
| 8 | 10 | ||
| 9 | import { ScopeId } from "@executor/sdk"; | 11 | import { ScopeId } from "@executor/sdk"; |
| 10 | 12 | ||
| @@ -24,6 +26,18 @@ const MINIMAL_OPENAPI_SPEC = JSON.stringify({ · 13 unmodified lines | |||
| 24 | }, | 26 | }, |
| 25 | }); | 27 | }); |
| 26 | 28 | ||
| 29 | // The Cloudflare OpenAPI spec is the biggest real spec we care about: | ||
| 30 | // 16MB, 2700+ operations, thousands of shared schemas. Exercising | ||
| 31 | // addSpec end-to-end on it through the real postgres adapter is the | ||
| 32 | // load-bearing check that any adapter regression (per-row `createMany`, | ||
| 33 | // accidental N+1 reads, transaction snapshots that copy too much) will | ||
| 34 | // show up as a test failure instead of a prod incident. | ||
| 35 | const CLOUDFLARE_SPEC_PATH = resolve( | ||
| 36 | __dirname, | ||
| 37 | "../../../../packages/plugins/openapi/fixtures/cloudflare.json", | ||
| 38 | ); | ||
| 39 | const CLOUDFLARE_SPEC = readFileSync(CLOUDFLARE_SPEC_PATH, "utf-8"); | ||
| 40 | |||
| 27 | describe("sources api (HTTP)", () => { | 41 | describe("sources api (HTTP)", () => { |
| 28 | it.effect("addSpec → sources.list includes the new namespace", () => | 42 | it.effect("addSpec → sources.list includes the new namespace", () => |
| 29 | Effect.gen(function* () { | 43 | Effect.gen(function* () { |
| @@ -147,4 +161,44 @@ describe("sources api (HTTP)", () => { · 117 unmodified lines | |||
| 147 | expect(fetched?.config.baseUrl).toBe("https://override.example.com"); | 161 | expect(fetched?.config.baseUrl).toBe("https://override.example.com"); |
| 148 | }), | 162 | }), |
| 149 | ); | 163 | ); |
| 164 | |||
| 165 | it.effect( | ||
| 166 | "addSpec persists the full Cloudflare spec through the real adapter", | ||
| 167 | () => | ||
| 168 | Effect.gen(function* () { | ||
| 169 | const org = `org_${crypto.randomUUID()}`; | ||
| 170 | const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`; | ||
| 171 | |||
| 172 | const result = yield* asOrg(org, (client) => | ||
| 173 | client.openapi.addSpec({ | ||
| 174 | path: { scopeId: ScopeId.make(org) }, | ||
| 175 | payload: { spec: CLOUDFLARE_SPEC, namespace }, | ||
| 176 | }), | ||
| 177 | ); | ||
| 178 | expect(result.namespace).toBe(namespace); | ||
| 179 | expect(result.toolCount).toBeGreaterThan(1000); | ||
| 180 | |||
| 181 | const sources = yield* asOrg(org, (client) => | ||
| 182 | client.sources.list({ path: { scopeId: ScopeId.make(org) } }), | ||
| 183 | ); | ||
| 184 | expect(sources.map((s) => s.id)).toContain(namespace); | ||
| 185 | |||
| 186 | // removeSpec on the same size must also land cleanly — catches | ||
| 187 | // symmetrical regressions on the delete side (e.g. deleteMany | ||
| 188 | // fanning out to per-row deletes). | ||
| 189 | yield* asOrg(org, (client) => | ||
| 190 | client.sources.remove({ | ||
| 191 | path: { scopeId: ScopeId.make(org), sourceId: namespace }, | ||
| 192 | }), | ||
| 193 | ); | ||
| 194 | const after = yield* asOrg(org, (client) => | ||
| 195 | client.sources.list({ path: { scopeId: ScopeId.make(org) } }), | ||
| 196 | ); | ||
| 197 | expect(after.map((s) => s.id)).not.toContain(namespace); | ||
| 198 | }), | ||
| 199 | // 60s is generous for a correct O(1) write path on local PGlite; | ||
| 200 | // a per-row regression would take minutes and hit this ceiling | ||
| 201 | // long before the suite would tolerate it. | ||
| 202 | { timeout: 60_000 }, | ||
| 203 | ); | ||
| 150 | }); | 204 | }); |