Skip to content

Commit 00aeb87

Browse files
authored
feat(showcase): derive smoke spec integrations from registry (CopilotKit#4286)
## Summary Replace the hardcoded 200-line INTEGRATIONS array in `integration-smoke.spec.ts` with a 6-line derivation from `registry.json`. New demos automatically appear in smoke tests when manifests are updated. - Delete hardcoded Integration interface + 17-entry array - Derive slug, name, backendUrl, deployed, hasToolRendering, demos from registry - Add unit tests verifying derivation correctness + regression guard against re-hardcoding ## Test plan - [x] New vitest tests pass (7/7) - [x] Existing showcase build pipeline tests pass (1183/1183 across 26 files) - [ ] CI green
2 parents 7611f95 + 9582b75 commit 00aeb87

2 files changed

Lines changed: 127 additions & 242 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
2+
import fs from "fs";
3+
import path from "path";
4+
import { execFileSync } from "child_process";
5+
import { FileSnapshotRestorer, execOptsFor } from "./test-cleanup";
6+
import { SCRIPTS_DIR, SHELL_DATA_DIR } from "./paths";
7+
8+
// Ensure registry.json exists (it's generated, gitignored).
9+
const DATA_FILES = [
10+
path.join(SHELL_DATA_DIR, "registry.json"),
11+
path.join(SHELL_DATA_DIR, "constraints.json"),
12+
];
13+
const dataRestorer = new FileSnapshotRestorer(DATA_FILES);
14+
const EXEC_OPTS = execOptsFor(SCRIPTS_DIR);
15+
16+
function runGenerator(): string {
17+
return execFileSync(
18+
"npx",
19+
["tsx", "generate-registry.ts"],
20+
EXEC_OPTS,
21+
).toString();
22+
}
23+
24+
let registry: {
25+
integrations: Array<{
26+
slug: string;
27+
name: string;
28+
backend_url: string;
29+
deployed: boolean;
30+
features: string[];
31+
demos: Array<{ id: string }>;
32+
}>;
33+
};
34+
35+
// Mirror the derivation logic from the smoke spec
36+
let INTEGRATIONS: Array<{
37+
slug: string;
38+
name: string;
39+
backendUrl: string;
40+
deployed: boolean;
41+
hasToolRendering: boolean;
42+
demos: string[];
43+
}>;
44+
45+
beforeAll(() => {
46+
runGenerator();
47+
dataRestorer.snapshot();
48+
49+
const registryPath = path.join(SHELL_DATA_DIR, "registry.json");
50+
registry = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
51+
52+
INTEGRATIONS = registry.integrations.map((i) => ({
53+
slug: i.slug,
54+
name: i.name,
55+
backendUrl: i.backend_url,
56+
deployed: i.deployed,
57+
hasToolRendering: i.features.includes("tool-rendering"),
58+
demos: i.demos.map((d: { id: string }) => d.id),
59+
}));
60+
});
61+
62+
afterEach(() => dataRestorer.restore());
63+
afterAll(() => dataRestorer.restore());
64+
65+
describe("smoke spec integration registry derivation", () => {
66+
it("produces one entry per registry integration", () => {
67+
expect(INTEGRATIONS.length).toBe(registry.integrations.length);
68+
});
69+
70+
it("every integration has a non-empty backendUrl", () => {
71+
for (const i of INTEGRATIONS) {
72+
expect(i.backendUrl, `${i.slug} missing backendUrl`).toBeTruthy();
73+
expect(i.backendUrl).toMatch(/^https?:\/\//);
74+
}
75+
});
76+
77+
it("hasToolRendering matches features list", () => {
78+
for (const reg of registry.integrations) {
79+
const derived = INTEGRATIONS.find((i) => i.slug === reg.slug)!;
80+
expect(derived.hasToolRendering).toBe(
81+
reg.features.includes("tool-rendering"),
82+
);
83+
}
84+
});
85+
86+
it("demo IDs match registry demos", () => {
87+
for (const reg of registry.integrations) {
88+
const derived = INTEGRATIONS.find((i) => i.slug === reg.slug)!;
89+
expect(derived.demos).toEqual(reg.demos.map((d) => d.id));
90+
}
91+
});
92+
93+
it("slugs match registry exactly", () => {
94+
expect(INTEGRATIONS.map((i) => i.slug)).toEqual(
95+
registry.integrations.map((i) => i.slug),
96+
);
97+
});
98+
99+
it("deployed filter works", () => {
100+
const deployed = INTEGRATIONS.filter((i) => i.deployed);
101+
const registryDeployed = registry.integrations.filter((i) => i.deployed);
102+
expect(deployed.length).toBe(registryDeployed.length);
103+
expect(deployed.length).toBeGreaterThan(0);
104+
});
105+
106+
it("no hardcoded integration data remains in smoke spec", () => {
107+
const specPath = path.resolve(
108+
__dirname,
109+
"../../tests/e2e/integration-smoke.spec.ts",
110+
);
111+
const src = fs.readFileSync(specPath, "utf-8");
112+
// The old hardcoded array had Railway URLs inline
113+
expect(src).not.toContain("showcase-langgraph-python-production");
114+
expect(src).not.toContain("showcase-mastra-production");
115+
// Should not have a literal Integration[] type (replaced by inference)
116+
expect(src).not.toContain("const INTEGRATIONS: Integration[]");
117+
});
118+
});

showcase/tests/e2e/integration-smoke.spec.ts

Lines changed: 9 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -35,250 +35,17 @@ const rewriteBackendUrl = (slug: string, railwayUrl: string): string => {
3535
};
3636

3737
// ---------------------------------------------------------------------------
38-
// Hardcoded integration list — must be manually kept in sync with
39-
// showcase/shell/src/data/registry.json. The separate STARTERS collection
40-
// below IS derived from the registry.
38+
// Integration registry — derived from showcase/shell/src/data/registry.json
4139
// ---------------------------------------------------------------------------
4240

43-
interface Integration {
44-
slug: string;
45-
name: string;
46-
backendUrl: string;
47-
backendType: "langgraph" | "ag-ui";
48-
deployed: boolean;
49-
demos: string[];
50-
}
51-
52-
const INTEGRATIONS: Integration[] = [
53-
{
54-
slug: "langgraph-python",
55-
name: "LangGraph (Python)",
56-
backendUrl: "https://showcase-langgraph-python-production.up.railway.app",
57-
backendType: "langgraph",
58-
deployed: true,
59-
demos: [
60-
"agentic-chat",
61-
"hitl-in-chat",
62-
"tool-rendering",
63-
"gen-ui-tool-based",
64-
"gen-ui-agent",
65-
"shared-state-read",
66-
"shared-state-write",
67-
"shared-state-streaming",
68-
"subagents",
69-
],
70-
},
71-
{
72-
slug: "mastra",
73-
name: "Mastra",
74-
backendUrl: "https://showcase-mastra-production.up.railway.app",
75-
backendType: "ag-ui",
76-
deployed: true,
77-
demos: [
78-
"agentic-chat",
79-
"hitl-in-chat",
80-
"tool-rendering",
81-
"gen-ui-tool-based",
82-
],
83-
},
84-
{
85-
slug: "langgraph-typescript",
86-
name: "LangGraph (TypeScript)",
87-
backendUrl:
88-
"https://showcase-langgraph-typescript-production.up.railway.app",
89-
backendType: "langgraph",
90-
deployed: true,
91-
demos: [
92-
"agentic-chat",
93-
"tool-rendering",
94-
"hitl-in-chat",
95-
"gen-ui-tool-based",
96-
],
97-
},
98-
{
99-
slug: "crewai-crews",
100-
name: "CrewAI (Crews)",
101-
backendUrl: "https://showcase-crewai-crews-production.up.railway.app",
102-
backendType: "ag-ui",
103-
deployed: true,
104-
demos: [
105-
"agentic-chat",
106-
"tool-rendering",
107-
"hitl-in-chat",
108-
"gen-ui-tool-based",
109-
],
110-
},
111-
{
112-
slug: "pydantic-ai",
113-
name: "PydanticAI",
114-
backendUrl: "https://showcase-pydantic-ai-production.up.railway.app",
115-
backendType: "ag-ui",
116-
deployed: true,
117-
demos: [
118-
"agentic-chat",
119-
"tool-rendering",
120-
"hitl-in-chat",
121-
"gen-ui-tool-based",
122-
],
123-
},
124-
{
125-
slug: "google-adk",
126-
name: "Google ADK",
127-
backendUrl: "https://showcase-google-adk-production.up.railway.app",
128-
backendType: "ag-ui",
129-
deployed: true,
130-
demos: [
131-
"agentic-chat",
132-
"tool-rendering",
133-
"hitl-in-chat",
134-
"gen-ui-tool-based",
135-
],
136-
},
137-
{
138-
slug: "claude-sdk-python",
139-
name: "Claude Agent SDK (Python)",
140-
backendUrl: "https://showcase-claude-sdk-python-production.up.railway.app",
141-
backendType: "ag-ui",
142-
deployed: true,
143-
demos: [
144-
"agentic-chat",
145-
"tool-rendering",
146-
"hitl-in-chat",
147-
"gen-ui-tool-based",
148-
],
149-
},
150-
{
151-
slug: "claude-sdk-typescript",
152-
name: "Claude Agent SDK (TypeScript)",
153-
backendUrl:
154-
"https://showcase-claude-sdk-typescript-production.up.railway.app",
155-
backendType: "ag-ui",
156-
deployed: true,
157-
demos: [
158-
"agentic-chat",
159-
"tool-rendering",
160-
"hitl-in-chat",
161-
"gen-ui-tool-based",
162-
],
163-
},
164-
{
165-
slug: "agno",
166-
name: "Agno",
167-
backendUrl: "https://showcase-agno-production.up.railway.app",
168-
backendType: "ag-ui",
169-
deployed: true,
170-
demos: [
171-
"agentic-chat",
172-
"tool-rendering",
173-
"hitl-in-chat",
174-
"gen-ui-tool-based",
175-
],
176-
},
177-
{
178-
slug: "ag2",
179-
name: "AG2",
180-
backendUrl: "https://showcase-ag2-production.up.railway.app",
181-
backendType: "ag-ui",
182-
deployed: true,
183-
demos: [
184-
"agentic-chat",
185-
"tool-rendering",
186-
"hitl-in-chat",
187-
"gen-ui-tool-based",
188-
],
189-
},
190-
{
191-
slug: "llamaindex",
192-
name: "LlamaIndex",
193-
backendUrl: "https://showcase-llamaindex-production.up.railway.app",
194-
backendType: "ag-ui",
195-
deployed: true,
196-
demos: [
197-
"agentic-chat",
198-
"tool-rendering",
199-
"hitl-in-chat",
200-
"gen-ui-tool-based",
201-
],
202-
},
203-
{
204-
slug: "langgraph-fastapi",
205-
name: "LangGraph (FastAPI)",
206-
backendUrl: "https://showcase-langgraph-fastapi-production.up.railway.app",
207-
backendType: "langgraph",
208-
deployed: true,
209-
demos: [
210-
"agentic-chat",
211-
"tool-rendering",
212-
"hitl-in-chat",
213-
"gen-ui-tool-based",
214-
],
215-
},
216-
{
217-
slug: "strands",
218-
name: "AWS Strands",
219-
backendUrl: "https://showcase-strands-production.up.railway.app",
220-
backendType: "ag-ui",
221-
deployed: true,
222-
demos: [
223-
"agentic-chat",
224-
"tool-rendering",
225-
"hitl-in-chat",
226-
"gen-ui-tool-based",
227-
],
228-
},
229-
{
230-
slug: "langroid",
231-
name: "Langroid",
232-
backendUrl: "https://showcase-langroid-production.up.railway.app",
233-
backendType: "ag-ui",
234-
deployed: true,
235-
demos: [
236-
"agentic-chat",
237-
"tool-rendering",
238-
"hitl-in-chat",
239-
"gen-ui-tool-based",
240-
],
241-
},
242-
{
243-
slug: "ms-agent-python",
244-
name: "MS Agent Framework (Python)",
245-
backendUrl: "https://showcase-ms-agent-python-production.up.railway.app",
246-
backendType: "ag-ui",
247-
deployed: true,
248-
demos: [
249-
"agentic-chat",
250-
"tool-rendering",
251-
"hitl-in-chat",
252-
"gen-ui-tool-based",
253-
],
254-
},
255-
{
256-
slug: "ms-agent-dotnet",
257-
name: "MS Agent Framework (.NET)",
258-
backendUrl: "https://showcase-ms-agent-dotnet-production.up.railway.app",
259-
backendType: "ag-ui",
260-
deployed: true,
261-
demos: [
262-
"agentic-chat",
263-
"tool-rendering",
264-
"hitl-in-chat",
265-
"gen-ui-tool-based",
266-
],
267-
},
268-
{
269-
slug: "spring-ai",
270-
name: "Spring AI",
271-
backendUrl: "https://showcase-spring-ai-production.up.railway.app",
272-
backendType: "ag-ui",
273-
deployed: true,
274-
demos: [
275-
"agentic-chat",
276-
"tool-rendering",
277-
"hitl-in-chat",
278-
"gen-ui-tool-based",
279-
],
280-
},
281-
];
41+
const INTEGRATIONS = registry.integrations.map((i) => ({
42+
slug: i.slug,
43+
name: i.name,
44+
backendUrl: i.backend_url,
45+
deployed: i.deployed,
46+
hasToolRendering: i.features.includes("tool-rendering"),
47+
demos: i.demos.map((d: { id: string }) => d.id),
48+
}));
28249

28350
// Only test deployed integrations unless SMOKE_ALL=true
28451
const SMOKE_ALL = process.env.SMOKE_ALL === "true";

0 commit comments

Comments
 (0)