// 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 }, );});
// 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 },
);
});