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
5 5
6import { describe, expect, it } from "@effect/vitest";6import { describe, expect, it } from "@effect/vitest";
7import { Effect } from "effect";7import { Effect } from "effect";
8import { readFileSync } from "node:fs";
9import { resolve } from "node:path";
8 10
9import { ScopeId } from "@executor/sdk";11import { 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.
35const CLOUDFLARE_SPEC_PATH = resolve(
36 __dirname,
37 "../../../../packages/plugins/openapi/fixtures/cloudflare.json",
38);
39const CLOUDFLARE_SPEC = readFileSync(CLOUDFLARE_SPEC_PATH, "utf-8");
40
27describe("sources api (HTTP)", () => {41describe("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});