History

Merge pull request #278 from RhysSullivan/rs/cloud-cloudflare-integration

test(cloud): exercise addSpec on the full Cloudflare spec through real postgres

Rhys Sullivan committed 6 months ago

Commit 6e4fc792 parents 17c607f ed72d9d1 file · +54 −0Browse files

Unified · Split

apps/cloud/src/services/sources-api.node.test.ts+54−0
@@ -5,6 +5,8 @@ · 4 unmodified lines
55
66 import { describe, expect, it } from "@effect/vitest";
77 import { Effect } from "effect";
8+import { readFileSync } from "node:fs";
9+import { resolve } from "node:path";
810
911 import { ScopeId } from "@executor/sdk";
1012
@@ -24,6 +26,18 @@ const MINIMAL_OPENAPI_SPEC = JSON.stringify({ · 13 unmodified lines
2426 },
2527 });
2628
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+
2741 describe("sources api (HTTP)", () => {
2842 it.effect("addSpec → sources.list includes the new namespace", () =>
2943 Effect.gen(function* () {
@@ -147,4 +161,44 @@ describe("sources api (HTTP)", () => { · 117 unmodified lines
147161 expect(fetched?.config.baseUrl).toBe("https://override.example.com");
148162 }),
149163 );
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+ );
150204 });