History

perf: cache parsed doc in real-specs tests (15s → 9s)

Rhys Sullivan committed 7 months ago

Commit a2e062d1 parent af627af1 file · +29 −27Browse files

Unified · Split

packages/plugins/openapi/src/real-specs.test.ts+29−27
@@ -3,43 +3,58 @@ import { Effect, Option } from "effect"; · 2 unmodified lines
33 import { readFileSync } from "node:fs";
44 import { resolve } from "node:path";
55
6+import type { ParsedDocument } from "./parse";
67 import { parse } from "./parse";
78 import { extract } from "./extract";
9+import type { ExtractionResult } from "./types";
810 import { createExecutor, makeTestConfig } from "@executor/sdk";
911 import { openApiPlugin } from "./plugin";
1012
1113 // ---------------------------------------------------------------------------
12−// Load the Cloudflare OpenAPI spec (~9MB, 1729 paths)
14+// Load + parse once, share across tests
1315 // ---------------------------------------------------------------------------
1416
1517 const specPath = resolve(__dirname, "../fixtures/cloudflare.json");
1618 const specText = readFileSync(specPath, "utf-8");
1719
20+let cachedDoc: ParsedDocument | null = null;
21+let cachedResult: ExtractionResult | null = null;
22+
23+const getDoc = () =>
24+ Effect.gen(function* () {
25+ if (!cachedDoc) cachedDoc = yield* parse(specText);
26+ return cachedDoc;
27+ });
28+
29+const getResult = () =>
30+ Effect.gen(function* () {
31+ if (!cachedResult) {
32+ const doc = yield* getDoc();
33+ cachedResult = yield* extract(doc);
34+ }
35+ return cachedResult;
36+ });
37+
1838 describe("Real specs: Cloudflare API", () => {
1939 it.effect("parses the full Cloudflare spec", () =>
2040 Effect.gen(function* () {
21− const doc = yield* parse(specText);
41+ const doc = yield* getDoc();
2242 expect(doc).toBeDefined();
2343 }),
2444 );
2545
2646 it.effect("extracts operations from the Cloudflare spec", () =>
2747 Effect.gen(function* () {
28− const doc = yield* parse(specText);
29− const result = yield* extract(doc);
48+ const result = yield* getResult();
3049
3150 expect(Option.getOrElse(result.title, () => "")).toBe("Cloudflare API");
3251 expect(Option.getOrElse(result.version, () => "")).toBe("4.0.0");
33−
34− // Should extract thousands of operations
3552 expect(result.operations.length).toBeGreaterThan(1000);
3653
37− // Every operation has an operationId
3854 for (const op of result.operations) {
3955 expect(op.operationId).toBeTruthy();
4056 }
4157
42− // Every operation has a valid HTTP method
4358 const validMethods = new Set([
4459 "get", "post", "put", "delete", "patch", "head", "options", "trace",
4560 ]);
@@ -47,13 +62,11 @@ describe("Real specs: Cloudflare API", () => { · 1 unmodified line
4762 expect(validMethods.has(op.method)).toBe(true);
4863 }
4964
50− // Spot-check: there should be zone-related operations
5165 const zoneOps = result.operations.filter((op) =>
5266 op.pathTemplate.includes("/zones"),
5367 );
5468 expect(zoneOps.length).toBeGreaterThan(0);
5569
56− // Spot-check: there should be DNS record operations
5770 const dnsOps = result.operations.filter((op) =>
5871 op.pathTemplate.includes("/dns_records"),
5972 );
@@ -63,10 +76,8 @@ describe("Real specs: Cloudflare API", () => { · 3 unmodified lines
6376
6477 it.effect("operations have input schemas", () =>
6578 Effect.gen(function* () {
66− const doc = yield* parse(specText);
67− const result = yield* extract(doc);
79+ const result = yield* getResult();
6880
69− // Most operations with parameters or request bodies should have input schemas
7081 const opsWithInput = result.operations.filter((op) =>
7182 Option.isSome(op.inputSchema),
7283 );
@@ -76,15 +87,12 @@ describe("Real specs: Cloudflare API", () => { · 3 unmodified lines
7687
7788 it.effect("operations have output schemas", () =>
7889 Effect.gen(function* () {
79− const doc = yield* parse(specText);
80− const result = yield* extract(doc);
90+ const result = yield* getResult();
8191
82− // Most GET operations should have output schemas
8392 const getOps = result.operations.filter((op) => op.method === "get");
8493 const getOpsWithOutput = getOps.filter((op) =>
8594 Option.isSome(op.outputSchema),
8695 );
87− // At least half of GETs should have output schemas
8896 expect(getOpsWithOutput.length).toBeGreaterThan(getOps.length / 2);
8997 }),
9098 );
@@ -107,13 +115,11 @@ describe("Real specs: Cloudflare API", () => { · 16 unmodified lines
107115 const tools = yield* executor.tools.list();
108116 expect(tools.length).toBe(result.toolCount);
109117
110− // All tools should be tagged
111118 for (const tool of tools) {
112119 expect(tool.tags).toContain("openapi");
113120 expect(tool.tags).toContain("cloudflare");
114121 }
115122
116− // Spot-check: can find zone list tool
117123 const zoneTools = yield* executor.tools.list({ query: "zone" });
118124 expect(zoneTools.length).toBeGreaterThan(0);
119125 }),
@@ -132,27 +138,23 @@ describe("Real specs: Cloudflare API", () => { · 12 unmodified lines
132138 namespace: "cloudflare",
133139 });
134140
135− // Shared definitions store contains all component schemas
136141 const definitions = yield* executor.tools.definitions();
137142 expect(Object.keys(definitions).length).toBeGreaterThan(5000);
138143 expect(definitions["dns-records_dns_response_single"]).toBeDefined();
139144
140− // Tool schemas use $ref pointers with re-attached definitions
141145 const tools = yield* executor.tools.list({ query: "dns-record-details" });
142146 expect(tools.length).toBeGreaterThan(0);
143−
147+
144148 const schema = yield* executor.tools.schema(tools[0]!.id);
145149 const output = schema.outputSchema as Record<string, unknown>;
146−
147− // Root is a $ref pointer
150+
148151 expect(output["$ref"]).toBeTypeOf("string");
149152 expect((output["$ref"] as string).startsWith("#/components/schemas/")).toBe(true);
150−
151− // Referenced definitions are re-attached for self-contained use
153+
152154 expect(output["$defs"]).toBeDefined();
153155 const defs = output["$defs"] as Record<string, unknown>;
154156 expect(Object.keys(defs).length).toBeGreaterThan(0);
155− expect(Object.keys(defs).length).toBeLessThan(100); // Only referenced ones, not all 5611
157+ expect(Object.keys(defs).length).toBeLessThan(100);
156158 }),
157159 );
158160