204 lines · 7.2 KB · TypeScript
RawDownload
// Source endpoints — CRUD through HttpApiClient. Complements tenant// isolation tests by exercising add → get → update → remove flows and// the error paths (remove non-existent, remove static, etc.) within a// single org. import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect";import { readFileSync } from "node:fs";import { resolve } from "node:path"; import { ScopeId } from "@executor/sdk"; import { asOrg } from "./__test-harness__/api-harness"; const MINIMAL_OPENAPI_SPEC = JSON.stringify({  openapi: "3.0.0",  info: { title: "Sources API Test", version: "1.0.0" },  paths: {    "/ping": {      get: {        operationId: "ping",        summary: "ping",        responses: { "200": { description: "ok" } },      },    },  },}); // The Cloudflare OpenAPI spec is the biggest real spec we care about:// 16MB, 2700+ operations, thousands of shared schemas. Exercising// addSpec end-to-end on it through the real postgres adapter is the// load-bearing check that any adapter regression (per-row `createMany`,// accidental N+1 reads, transaction snapshots that copy too much) will// show up as a test failure instead of a prod incident.const CLOUDFLARE_SPEC_PATH = resolve(  __dirname,  "../../../../packages/plugins/openapi/fixtures/cloudflare.json",);const CLOUDFLARE_SPEC = readFileSync(CLOUDFLARE_SPEC_PATH, "utf-8"); describe("sources api (HTTP)", () => {  it.effect("addSpec → sources.list includes the new namespace", () =>    Effect.gen(function* () {      const org = `org_${crypto.randomUUID()}`;      const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`;       yield* asOrg(org, (client) =>        Effect.gen(function* () {          const result = yield* client.openapi.addSpec({            path: { scopeId: ScopeId.make(org) },            payload: { spec: MINIMAL_OPENAPI_SPEC, namespace },          });          expect(result.namespace).toBe(namespace);          expect(result.toolCount).toBeGreaterThan(0);        }),      );       const sources = yield* asOrg(org, (client) =>        client.sources.list({ path: { scopeId: ScopeId.make(org) } }),      );      expect(sources.map((s) => s.id)).toContain(namespace);    }),  );   it.effect("openapi.getSource returns the stored source after addSpec", () =>    Effect.gen(function* () {      const org = `org_${crypto.randomUUID()}`;      const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`;       yield* asOrg(org, (client) =>        client.openapi.addSpec({          path: { scopeId: ScopeId.make(org) },          payload: { spec: MINIMAL_OPENAPI_SPEC, namespace },        }),      );       const fetched = yield* asOrg(org, (client) =>        client.openapi.getSource({ path: { scopeId: ScopeId.make(org), namespace } }),      );      expect(fetched).not.toBeNull();      expect(fetched?.namespace).toBe(namespace);    }),  );   it.effect("sources.remove deletes the source and it drops off sources.list", () =>    Effect.gen(function* () {      const org = `org_${crypto.randomUUID()}`;      const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`;       yield* asOrg(org, (client) =>        Effect.gen(function* () {          yield* client.openapi.addSpec({            path: { scopeId: ScopeId.make(org) },            payload: { spec: MINIMAL_OPENAPI_SPEC, namespace },          });          yield* client.sources.remove({            path: { scopeId: ScopeId.make(org), sourceId: namespace },          });        }),      );       const after = yield* asOrg(org, (client) =>        client.sources.list({ path: { scopeId: ScopeId.make(org) } }),      );      expect(after.map((s) => s.id)).not.toContain(namespace);    }),  );   it.effect("sources.remove on a non-existent sourceId is a no-op (idempotent)", () =>    Effect.gen(function* () {      const org = `org_${crypto.randomUUID()}`;      const ghost = `missing_${crypto.randomUUID().slice(0, 8)}`;       const result = yield* asOrg(org, (client) =>        client.sources          .remove({ path: { scopeId: ScopeId.make(org), sourceId: ghost } })          .pipe(Effect.either),      );      expect(result._tag).toBe("Right");    }),  );   it.effect("sources.remove on a static source is rejected", () =>    Effect.gen(function* () {      // `canRemove: false` is reserved for static (plugin-declared)      // sources. The openapi plugin declares one with id "openapi"      // for its control tools.      const org = `org_${crypto.randomUUID()}`;       const result = yield* asOrg(org, (client) =>        client.sources          .remove({ path: { scopeId: ScopeId.make(org), sourceId: "openapi" } })          .pipe(Effect.either),      );      expect(result._tag).toBe("Left");    }),  );   it.effect("openapi.updateSource round-trips baseUrl + name changes", () =>    Effect.gen(function* () {      const org = `org_${crypto.randomUUID()}`;      const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`;       yield* asOrg(org, (client) =>        Effect.gen(function* () {          yield* client.openapi.addSpec({            path: { scopeId: ScopeId.make(org) },            payload: { spec: MINIMAL_OPENAPI_SPEC, namespace },          });          yield* client.openapi.updateSource({            path: { scopeId: ScopeId.make(org), namespace },            payload: { name: "Renamed API", baseUrl: "https://override.example.com" },          });        }),      );       const fetched = yield* asOrg(org, (client) =>        client.openapi.getSource({ path: { scopeId: ScopeId.make(org), namespace } }),      );      expect(fetched?.name).toBe("Renamed API");      expect(fetched?.config.baseUrl).toBe("https://override.example.com");    }),  );   it.effect(    "addSpec persists the full Cloudflare spec through the real adapter",    () =>      Effect.gen(function* () {        const org = `org_${crypto.randomUUID()}`;        const namespace = `ns_${crypto.randomUUID().replace(/-/g, "_")}`;         const result = yield* asOrg(org, (client) =>          client.openapi.addSpec({            path: { scopeId: ScopeId.make(org) },            payload: { spec: CLOUDFLARE_SPEC, namespace },          }),        );        expect(result.namespace).toBe(namespace);        expect(result.toolCount).toBeGreaterThan(1000);         const sources = yield* asOrg(org, (client) =>          client.sources.list({ path: { scopeId: ScopeId.make(org) } }),        );        expect(sources.map((s) => s.id)).toContain(namespace);         // removeSpec on the same size must also land cleanly — catches        // symmetrical regressions on the delete side (e.g. deleteMany        // fanning out to per-row deletes).        yield* asOrg(org, (client) =>          client.sources.remove({            path: { scopeId: ScopeId.make(org), sourceId: namespace },          }),        );        const after = yield* asOrg(org, (client) =>          client.sources.list({ path: { scopeId: ScopeId.make(org) } }),        );        expect(after.map((s) => s.id)).not.toContain(namespace);      }),    // 60s is generous for a correct O(1) write path on local PGlite;    // a per-row regression would take minutes and hit this ceiling    // long before the suite would tolerate it.    { timeout: 60_000 },  );});