test(cloud): exercise addSpec on the full Cloudflare spec through real postgres
Unified · Split
| @@ -5,6 +5,8 @@ · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | import { describe, expect, it } from "@effect/vitest"; |
| 7 | 7 | import { Effect } from "effect"; |
| 8 | +import { readFileSync } from "node:fs"; | |
| 9 | +import { resolve } from "node:path"; | |
| 8 | 10 | |
| 9 | 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 | 41 | describe("sources api (HTTP)", () => { |
| 28 | 42 | it.effect("addSpec → sources.list includes the new namespace", () => |
| 29 | 43 | Effect.gen(function* () { |
| @@ -147,4 +161,44 @@ describe("sources api (HTTP)", () => { · 117 unmodified lines | ||
| 147 | 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 | }); |