Skip to content

Commit c989947

Browse files
committed
test: add production build tests to verify compiled code
Add comprehensive test suite to verify that the compiled production code (dist/) works correctly after TypeScript compilation. This ensures that ESM module imports and configuration loading work properly in the production environment. The new test file test/production-build.test.ts includes: - Module import tests to verify production code can be imported correctly - Configuration structure tests to validate all AI tool configurations are properly loaded and structured - Functionality tests to ensure getCoDevelopedBy and other core functions work in production environment - Development vs production consistency tests to verify behavior matches between dev and prod builds This addresses the concern that development tests using Vitest's esbuild transformation might mask module import issues that only appear in the compiled production code. The tests directly import from dist/ to catch any runtime issues early, ensuring the software works correctly after npm install and package distribution. Change-Id: I8b1d5dc249070f68dd257774e44b0ff720bd0635 Co-developed-by: Cursor <noreply@cursor.com>
1 parent dd2f3a6 commit c989947

1 file changed

Lines changed: 357 additions & 0 deletions

File tree

test/production-build.test.ts

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
/**
2+
* Production Build Tests
3+
*
4+
* These tests verify that the compiled production code (dist/) works correctly.
5+
* This ensures that TypeScript compilation doesn't introduce runtime issues,
6+
* especially with ESM module imports and configuration loading.
7+
*/
8+
9+
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
10+
import { execSync } from 'child_process';
11+
import { existsSync } from 'fs';
12+
import { join } from 'path';
13+
14+
// Import production build modules
15+
let productionAITools: typeof import('../dist/ai-tools/index.js');
16+
let productionExec: typeof import('../dist/commands/exec.js');
17+
18+
describe('Production Build Tests', () => {
19+
beforeAll(async () => {
20+
// Ensure the project is built before running tests
21+
if (!existsSync(join(process.cwd(), 'dist', 'ai-tools', 'index.js'))) {
22+
console.log('Building project before running production tests...');
23+
execSync('npm run build', { stdio: 'inherit' });
24+
}
25+
26+
// Dynamically import production modules
27+
productionAITools = await import('../dist/ai-tools/index.js');
28+
productionExec = await import('../dist/commands/exec.js');
29+
});
30+
31+
describe('Module Import Tests', () => {
32+
it('should successfully import production AI tools module', () => {
33+
expect(productionAITools).toBeDefined();
34+
expect(productionAITools.getAllToolConfigs).toBeDefined();
35+
expect(typeof productionAITools.getAllToolConfigs).toBe('function');
36+
});
37+
38+
it('should successfully import production exec module', () => {
39+
expect(productionExec).toBeDefined();
40+
expect(productionExec.getCoDevelopedBy).toBeDefined();
41+
expect(typeof productionExec.getCoDevelopedBy).toBe('function');
42+
});
43+
44+
it('should load all tool configurations', () => {
45+
const configs = productionAITools.getAllToolConfigs();
46+
expect(configs).toBeDefined();
47+
expect(Array.isArray(configs)).toBe(true);
48+
expect(configs.length).toBeGreaterThan(0);
49+
});
50+
});
51+
52+
describe('Configuration Structure Tests', () => {
53+
it('should have correct number of tool configurations', () => {
54+
const configs = productionAITools.getAllToolConfigs();
55+
// Should have 8 tools: claude, iflow, qwen-code, gemini, qoder-cli, cursor, kiro, qoder-ide
56+
expect(configs.length).toBe(8);
57+
});
58+
59+
it('should have all required fields in each configuration', () => {
60+
const configs = productionAITools.getAllToolConfigs();
61+
for (const config of configs) {
62+
expect(config).toHaveProperty('type');
63+
expect(config).toHaveProperty('userName');
64+
expect(config).toHaveProperty('userEmail');
65+
expect(config).toHaveProperty('envVars');
66+
expect(typeof config.type).toBe('string');
67+
expect(typeof config.userName).toBe('string');
68+
expect(typeof config.userEmail).toBe('string');
69+
expect(Array.isArray(config.envVars)).toBe(true);
70+
expect(config.userName.length).toBeGreaterThan(0);
71+
expect(config.userEmail.length).toBeGreaterThan(0);
72+
expect(config.envVars.length).toBeGreaterThan(0);
73+
}
74+
});
75+
76+
it('should have valid tool types', () => {
77+
const configs = productionAITools.getAllToolConfigs();
78+
const validTypes = ['cli', 'plugin', 'ide', 'others'];
79+
for (const config of configs) {
80+
expect(validTypes).toContain(config.type);
81+
}
82+
});
83+
84+
it('should have valid email format in configurations', () => {
85+
const configs = productionAITools.getAllToolConfigs();
86+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
87+
for (const config of configs) {
88+
expect(emailRegex.test(config.userEmail)).toBe(true);
89+
}
90+
});
91+
92+
it('should have valid environment variable configurations', () => {
93+
const configs = productionAITools.getAllToolConfigs();
94+
for (const config of configs) {
95+
for (const envVar of config.envVars) {
96+
expect(envVar).toHaveProperty('key');
97+
expect(envVar).toHaveProperty('value');
98+
expect(typeof envVar.key).toBe('string');
99+
expect(typeof envVar.value).toBe('string');
100+
expect(envVar.key.length).toBeGreaterThan(0);
101+
expect(envVar.value.length).toBeGreaterThan(0);
102+
}
103+
}
104+
});
105+
106+
it('should be sorted by priority (CLI → PLUGIN → IDE → OTHERS)', () => {
107+
const configs = productionAITools.getAllToolConfigs();
108+
const typePriority: Record<string, number> = {
109+
cli: 1,
110+
plugin: 2,
111+
ide: 3,
112+
others: 4,
113+
};
114+
115+
for (let i = 1; i < configs.length; i++) {
116+
const prevPriority = typePriority[configs[i - 1].type] || 999;
117+
const currPriority = typePriority[configs[i].type] || 999;
118+
expect(currPriority).toBeGreaterThanOrEqual(prevPriority);
119+
}
120+
});
121+
122+
it('should have all expected tool configurations', () => {
123+
const configs = productionAITools.getAllToolConfigs();
124+
const toolNames = configs.map((c) => c.userName);
125+
const expectedTools = [
126+
'Claude',
127+
'iFlow',
128+
'Qwen-Coder',
129+
'Gemini',
130+
'Qoder CLI',
131+
'Cursor',
132+
'Kiro',
133+
'Qoder',
134+
];
135+
136+
expect(toolNames.length).toBe(expectedTools.length);
137+
for (const expectedTool of expectedTools) {
138+
expect(toolNames).toContain(expectedTool);
139+
}
140+
});
141+
});
142+
143+
describe('Functionality Tests', () => {
144+
const originalEnv = process.env;
145+
146+
beforeEach(() => {
147+
// Reset environment variables before each test
148+
process.env = { ...originalEnv };
149+
// Clear all AI tool environment variables
150+
productionExec.clearCoDevelopedByEnvVars();
151+
});
152+
153+
afterEach(() => {
154+
// Restore original environment variables
155+
process.env = originalEnv;
156+
});
157+
158+
it('should return Claude CoDevelopedBy when CLAUDECODE=1 is set', () => {
159+
process.env.CLAUDECODE = '1';
160+
const result = productionExec.getCoDevelopedBy();
161+
expect(result).toBe('Claude <noreply@anthropic.com>');
162+
});
163+
164+
it('should return Qwen-Coder CoDevelopedBy when QWEN_CODE=1 is set', () => {
165+
productionExec.clearCoDevelopedByEnvVars();
166+
process.env.QWEN_CODE = '1';
167+
const result = productionExec.getCoDevelopedBy();
168+
expect(result).toBe('Qwen-Coder <noreply@alibabacloud.com>');
169+
});
170+
171+
it('should return Gemini CoDevelopedBy when GEMINI_CLI=1 is set', () => {
172+
productionExec.clearCoDevelopedByEnvVars();
173+
process.env.GEMINI_CLI = '1';
174+
const result = productionExec.getCoDevelopedBy();
175+
expect(result).toBe('Gemini <noreply@developers.google.com>');
176+
});
177+
178+
it('should return iFlow CoDevelopedBy when IFLOW_CLI=1 is set', () => {
179+
productionExec.clearCoDevelopedByEnvVars();
180+
process.env.IFLOW_CLI = '1';
181+
const result = productionExec.getCoDevelopedBy();
182+
expect(result).toBe('iFlow <noreply@iflow.cn>');
183+
});
184+
185+
it('should return Qoder CLI CoDevelopedBy when QODER_CLI=1 is set', () => {
186+
productionExec.clearCoDevelopedByEnvVars();
187+
process.env.QODER_CLI = '1';
188+
const result = productionExec.getCoDevelopedBy();
189+
expect(result).toBe('Qoder CLI <noreply@qoder.com>');
190+
});
191+
192+
it('should return Cursor CoDevelopedBy when CURSOR_TRACE_ID is set', () => {
193+
productionExec.clearCoDevelopedByEnvVars();
194+
process.env.CURSOR_TRACE_ID = 'any-value';
195+
const result = productionExec.getCoDevelopedBy();
196+
expect(result).toBe('Cursor <noreply@cursor.com>');
197+
});
198+
199+
it('should return Kiro CoDevelopedBy when __CFBundleIdentifier=dev.kiro.desktop is set', () => {
200+
productionExec.clearCoDevelopedByEnvVars();
201+
process.env.__CFBundleIdentifier = 'dev.kiro.desktop';
202+
const result = productionExec.getCoDevelopedBy();
203+
expect(result).toBe('Kiro <noreply@kiro.dev>');
204+
});
205+
206+
it('should return Qoder CoDevelopedBy when VSCODE_BRAND=Qoder is set', () => {
207+
productionExec.clearCoDevelopedByEnvVars();
208+
process.env.VSCODE_BRAND = 'Qoder';
209+
const result = productionExec.getCoDevelopedBy();
210+
expect(result).toBe('Qoder <noreply@qoder.com>');
211+
});
212+
213+
it('should respect priority order (CLI over IDE)', () => {
214+
productionExec.clearCoDevelopedByEnvVars();
215+
process.env.IFLOW_CLI = '1';
216+
process.env.__CFBundleIdentifier = 'dev.kiro.desktop';
217+
process.env.VSCODE_BRAND = 'Qoder';
218+
const result = productionExec.getCoDevelopedBy();
219+
// CLI should have higher priority than IDE
220+
expect(result).toBe('iFlow <noreply@iflow.cn>');
221+
});
222+
223+
it('should respect priority order (CLI over CLI)', () => {
224+
productionExec.clearCoDevelopedByEnvVars();
225+
process.env.CLAUDECODE = '1';
226+
process.env.QWEN_CODE = '1';
227+
process.env.GEMINI_CLI = '1';
228+
const result = productionExec.getCoDevelopedBy();
229+
// First CLI in order should win
230+
expect(result).toBe('Claude <noreply@anthropic.com>');
231+
});
232+
233+
it('should return empty string when no environment variables are set', () => {
234+
productionExec.clearCoDevelopedByEnvVars();
235+
const result = productionExec.getCoDevelopedBy();
236+
expect(result).toBe('');
237+
});
238+
239+
it('should return empty string when environment variables are set to falsy values', () => {
240+
productionExec.clearCoDevelopedByEnvVars();
241+
process.env.CLAUDECODE = '0';
242+
process.env.QWEN_CODE = 'false';
243+
process.env.GEMINI_CLI = '';
244+
const result = productionExec.getCoDevelopedBy();
245+
expect(result).toBe('');
246+
});
247+
248+
it('should handle glob pattern matching for Cursor', () => {
249+
productionExec.clearCoDevelopedByEnvVars();
250+
process.env.VSCODE_GIT_ASKPASS_MAIN =
251+
'/home/user/.cursor-server/bin/askpass-main.js';
252+
const result = productionExec.getCoDevelopedBy();
253+
expect(result).toBe('Cursor <noreply@cursor.com>');
254+
});
255+
256+
it('should handle glob pattern matching for Qoder', () => {
257+
productionExec.clearCoDevelopedByEnvVars();
258+
process.env.VSCODE_GIT_ASKPASS_MAIN =
259+
'/home/user/.qoder-server/bin/askpass-main.js';
260+
const result = productionExec.getCoDevelopedBy();
261+
expect(result).toBe('Qoder <noreply@qoder.com>');
262+
});
263+
264+
it('should handle BROWSER environment variable for Cursor', () => {
265+
productionExec.clearCoDevelopedByEnvVars();
266+
process.env.BROWSER = '/home/user/.cursor-server/bin/helpers/browser.sh';
267+
const result = productionExec.getCoDevelopedBy();
268+
expect(result).toBe('Cursor <noreply@cursor.com>');
269+
});
270+
271+
it('should handle BROWSER environment variable for Qoder', () => {
272+
productionExec.clearCoDevelopedByEnvVars();
273+
process.env.BROWSER = '/home/user/.qoder-server/bin/helpers/browser.sh';
274+
const result = productionExec.getCoDevelopedBy();
275+
expect(result).toBe('Qoder <noreply@qoder.com>');
276+
});
277+
});
278+
279+
describe('Development vs Production Consistency', () => {
280+
// Import development modules for comparison
281+
let devAITools: typeof import('../src/ai-tools/index.js');
282+
let devExec: typeof import('../src/commands/exec.js');
283+
284+
beforeAll(async () => {
285+
// Dynamically import development modules
286+
devAITools = await import('../src/ai-tools/index.js');
287+
devExec = await import('../src/commands/exec.js');
288+
});
289+
290+
it('should have the same number of configurations in dev and production', () => {
291+
const devConfigs = devAITools.getAllToolConfigs();
292+
const prodConfigs = productionAITools.getAllToolConfigs();
293+
expect(prodConfigs.length).toBe(devConfigs.length);
294+
});
295+
296+
it('should have the same tool names in dev and production', () => {
297+
const devConfigs = devAITools.getAllToolConfigs();
298+
const prodConfigs = productionAITools.getAllToolConfigs();
299+
const devNames = devConfigs.map((c) => c.userName).sort();
300+
const prodNames = prodConfigs.map((c) => c.userName).sort();
301+
expect(prodNames).toEqual(devNames);
302+
});
303+
304+
it('should produce the same CoDevelopedBy result for CLAUDECODE=1', () => {
305+
const originalEnv = process.env;
306+
try {
307+
process.env = { ...originalEnv };
308+
devExec.clearCoDevelopedByEnvVars();
309+
productionExec.clearCoDevelopedByEnvVars();
310+
process.env.CLAUDECODE = '1';
311+
312+
const devResult = devExec.getCoDevelopedBy();
313+
const prodResult = productionExec.getCoDevelopedBy();
314+
315+
expect(prodResult).toBe(devResult);
316+
expect(prodResult).toBe('Claude <noreply@anthropic.com>');
317+
} finally {
318+
process.env = originalEnv;
319+
}
320+
});
321+
322+
it('should produce the same CoDevelopedBy result for QWEN_CODE=1', () => {
323+
const originalEnv = process.env;
324+
try {
325+
process.env = { ...originalEnv };
326+
devExec.clearCoDevelopedByEnvVars();
327+
productionExec.clearCoDevelopedByEnvVars();
328+
process.env.QWEN_CODE = '1';
329+
330+
const devResult = devExec.getCoDevelopedBy();
331+
const prodResult = productionExec.getCoDevelopedBy();
332+
333+
expect(prodResult).toBe(devResult);
334+
expect(prodResult).toBe('Qwen-Coder <noreply@alibabacloud.com>');
335+
} finally {
336+
process.env = originalEnv;
337+
}
338+
});
339+
340+
it('should produce the same empty result when no env vars are set', () => {
341+
const originalEnv = process.env;
342+
try {
343+
process.env = { ...originalEnv };
344+
devExec.clearCoDevelopedByEnvVars();
345+
productionExec.clearCoDevelopedByEnvVars();
346+
347+
const devResult = devExec.getCoDevelopedBy();
348+
const prodResult = productionExec.getCoDevelopedBy();
349+
350+
expect(prodResult).toBe(devResult);
351+
expect(prodResult).toBe('');
352+
} finally {
353+
process.env = originalEnv;
354+
}
355+
});
356+
});
357+
});

0 commit comments

Comments
 (0)