311 lines · 10.8 KB · TypeScript
RawDownload
// Parse / extract / preview coverage against a big real-world spec.// DB-touching behaviour (addSpec, removeSpec, tool registration) moved// to the apps/cloud integrations API node tests — those run// through the real Drizzle/FumaDB path so storage regressions// (e.g. a per-row createMany fallback) surface automatically instead// of needing a dedicated budget assertion. import { describe, expect, it } from "@effect/vitest";import { Effect, Option } from "effect";import { FetchHttpClient } from "effect/unstable/http";import { readFileSync } from "node:fs";import { resolve } from "node:path"; import {  createExecutor,  AuthTemplateSlug,  ConnectionName,  IntegrationSlug,  ToolAddress,} from "@executor-js/sdk";import type { ToolSchemaView } from "@executor-js/sdk/core";import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testing"; import type { ParsedDocument } from "./parse";import { parse } from "./parse";import { extract } from "./extract";import { openApiPlugin } from "./plugin";import { deriveAuthenticationTemplateFromPreview } from "./derive-auth";import { previewSpec as previewSpecRaw } from "./preview";import type { ExtractionResult } from "./types"; const previewSpec = (input: string) =>  previewSpecRaw(input).pipe(Effect.provide(FetchHttpClient.layer)); // ---------------------------------------------------------------------------// Load + parse once, share across tests// --------------------------------------------------------------------------- const cloudflareSpecPath = resolve(__dirname, "../../fixtures/cloudflare.json");const cloudflareSpecText = readFileSync(cloudflareSpecPath, "utf-8");const vercelSpecPath = resolve(__dirname, "../../fixtures/vercel.json");const vercelSpecText = readFileSync(vercelSpecPath, "utf-8"); let cachedDoc: ParsedDocument | null = null;let cachedResult: ExtractionResult | null = null;let cachedVercelDoc: ParsedDocument | null = null;let cachedVercelResult: ExtractionResult | null = null; const getDoc = () =>  Effect.gen(function* () {    if (!cachedDoc) cachedDoc = yield* parse(cloudflareSpecText);    return cachedDoc;  }); const getResult = () =>  Effect.gen(function* () {    if (!cachedResult) {      const doc = yield* getDoc();      cachedResult = yield* extract(doc);    }    return cachedResult;  }); const getVercelDoc = () =>  Effect.gen(function* () {    if (!cachedVercelDoc) cachedVercelDoc = yield* parse(vercelSpecText);    return cachedVercelDoc;  }); const getVercelResult = () =>  Effect.gen(function* () {    if (!cachedVercelResult) {      const doc = yield* getVercelDoc();      cachedVercelResult = yield* extract(doc);    }    return cachedVercelResult;  }); const schemaCache = new Map<string, ToolSchemaView>(); // A tool's name keeps its openapi `group.leaf` path verbatim (dots and all);// the address grammar treats the `<tool>` segment as the trailing remainder, so// the dots nest naturally. The integration slug is the address's// `<integration>` segment, and a connection is required for the tools to be// produced + addressable.const getRegisteredToolSchema = (slug: string, specText: string, toolName: string) =>  Effect.gen(function* () {    const cacheKey = `${slug}.${toolName}`;    const cached = schemaCache.get(cacheKey);    if (cached) return cached;     const executor = yield* createExecutor(      makeTestConfig({        plugins: [openApiPlugin(), memoryCredentialsPlugin()] as const,      }),    );     yield* executor.openapi.addSpec({      spec: { kind: "blob", value: specText },      slug,    });    yield* executor.connections.create({      owner: "org",      name: ConnectionName.make("main"),      integration: IntegrationSlug.make(slug),      template: AuthTemplateSlug.make("apiKey"),      value: "token",    });     const address = ToolAddress.make(`tools.${slug}.org.main.${toolName}`);    const schema = yield* executor.tools.schema(address);    expect(schema).not.toBeNull();    schemaCache.set(cacheKey, schema!);    return schema!;  }); const operationSummary = (result: ExtractionResult, operationId: string) => {  const op = result.operations.find((candidate) => candidate.operationId === operationId);  expect(op).toBeDefined();  return {    operationId: op!.operationId,    method: op!.method,    pathTemplate: op!.pathTemplate,    tags: op!.tags,    hasInputSchema: Option.isSome(op!.inputSchema),    hasOutputSchema: Option.isSome(op!.outputSchema),  };}; const extractionSummary = (result: ExtractionResult, selectedOperationIds: readonly string[]) => ({  title: Option.getOrUndefined(result.title),  version: Option.getOrUndefined(result.version),  operationCount: result.operations.length,  inputSchemaOperationCount: result.operations.filter((op) => Option.isSome(op.inputSchema)).length,  outputSchemaOperationCount: result.operations.filter((op) => Option.isSome(op.outputSchema))    .length,  selectedOperations: selectedOperationIds.map((operationId) =>    operationSummary(result, operationId),  ),}); const schemaPreviewSummary = (schema: ToolSchemaView) => {  const schemaDefinitions = schema.schemaDefinitions ?? {};  const typeScriptDefinitions = schema.typeScriptDefinitions ?? {};  return {    toolId: schema.address,    inputSchema: schema.inputSchema,    outputSchema: schema.outputSchema,    inputTypeScript: schema.inputTypeScript,    outputTypeScript: schema.outputTypeScript,    schemaDefinitionCount: Object.keys(schemaDefinitions).length,    schemaDefinitionNames: Object.keys(schemaDefinitions).sort(),    typeScriptDefinitionCount: Object.keys(typeScriptDefinitions).length,    typeScriptDefinitions,  };}; describe("Real specs: Cloudflare API", { timeout: 60_000 }, () => {  it.effect("parses the full Cloudflare spec", () =>    Effect.gen(function* () {      const doc = yield* getDoc();      expect(doc).toBeDefined();    }),  );   it.effect("extracts operations from the Cloudflare spec", () =>    Effect.gen(function* () {      const result = yield* getResult();       expect(Option.getOrElse(result.title, () => "")).toBe("Cloudflare API");      expect(Option.getOrElse(result.version, () => "")).toBe("4.0.0");      expect(result.operations.length).toBeGreaterThan(1000);       for (const op of result.operations) {        expect(op.operationId).toBeTruthy();      }       const validMethods = new Set([        "get",        "post",        "put",        "delete",        "patch",        "head",        "options",        "trace",      ]);      for (const op of result.operations) {        expect(validMethods.has(op.method)).toBe(true);      }       const zoneOps = result.operations.filter((op) => op.pathTemplate.includes("/zones"));      expect(zoneOps.length).toBeGreaterThan(0);       const dnsOps = result.operations.filter((op) => op.pathTemplate.includes("/dns_records"));      expect(dnsOps.length).toBeGreaterThan(0);    }),  );   it.effect("operations have input schemas", () =>    Effect.gen(function* () {      const result = yield* getResult();       const opsWithInput = result.operations.filter((op) => Option.isSome(op.inputSchema));      expect(opsWithInput.length).toBeGreaterThan(500);    }),  );   it.effect("operations have output schemas", () =>    Effect.gen(function* () {      const result = yield* getResult();       const getOps = result.operations.filter((op) => op.method === "get");      const getOpsWithOutput = getOps.filter((op) => Option.isSome(op.outputSchema));      expect(getOpsWithOutput.length).toBeGreaterThan(getOps.length / 2);    }),  );   it.effect("previewSpec returns security schemes and header presets", () =>    Effect.gen(function* () {      const preview = yield* previewSpec(cloudflareSpecText);       expect(preview.operationCount).toBeGreaterThan(1000);      expect(Option.isSome(preview.title)).toBe(true);      expect(preview.servers.length).toBeGreaterThan(0);       expect(preview.securitySchemes.length).toBe(4);      const schemeNames = preview.securitySchemes.map((s) => s.name);      expect(schemeNames).toContain("api_token");      expect(schemeNames).toContain("api_key");      expect(schemeNames).toContain("api_email");       expect(preview.headerPresets.length).toBeGreaterThan(0);       const bearerPreset = preview.headerPresets.find((p) => p.label.includes("Bearer"));      expect(bearerPreset).toBeDefined();      expect(bearerPreset!.headers["Authorization"]).toBeNull();      expect(bearerPreset!.secretHeaders).toContain("Authorization");       const keyEmailPreset = preview.headerPresets.find((p) => p.label.includes("api_email"));      expect(keyEmailPreset).toBeDefined();      expect(keyEmailPreset!.headers["X-Auth-Email"]).toBeNull();      expect(keyEmailPreset!.headers["X-Auth-Key"]).toBeNull();       const templates = deriveAuthenticationTemplateFromPreview(preview, undefined);      const keyEmailTemplate = templates.find(        (template) =>          template.kind === "apikey" &&          template.placements.some((placement) => placement.name === "X-Auth-Email"),      );      expect(keyEmailTemplate).toBeDefined();      expect(keyEmailTemplate).toMatchObject({        kind: "apikey",        placements: [          { carrier: "header", name: "X-Auth-Email", variable: "x_auth_email" },          { carrier: "header", name: "X-Auth-Key", variable: "x_auth_key" },        ],      });    }),  );   it.effect("preserves extraction baseline for representative operations", () =>    Effect.gen(function* () {      const result = yield* getResult();       expect(        extractionSummary(result, [          "access-applications-add-an-application",          "dns-records-for-a-zone-create-dns-record",          "worker-script-upload-worker-module",        ]),      ).toMatchSnapshot();    }),  );   it.effect("preserves registered tool schema and TypeScript output for Access app creation", () =>    Effect.gen(function* () {      const schema = yield* getRegisteredToolSchema(        "cloudflare_api",        cloudflareSpecText,        "accessApplications.accessApplicationsAddAnApplication",      );       expect(schemaPreviewSummary(schema)).toMatchSnapshot();    }),  );}); describe("Real specs: Vercel API", { timeout: 60_000 }, () => {  it.effect("preserves extraction baseline for representative operations", () =>    Effect.gen(function* () {      const result = yield* getVercelResult();       expect(        extractionSummary(result, ["createDeployment", "createProject", "updateProject"]),      ).toMatchSnapshot();    }),  );   it.effect("preserves registered tool schema and TypeScript output for deployment creation", () =>    Effect.gen(function* () {      const schema = yield* getRegisteredToolSchema(        "vercel_api",        vercelSpecText,        "deployments.createDeployment",      );       expect(schemaPreviewSummary(schema)).toMatchSnapshot();    }),  );});