Skip to content

Commit ed03395

Browse files
committed
🤖 Fix integration test race condition in AI SDK provider imports
Restore preloadAISDKProviders() functionality to eagerly load @ai-sdk/anthropic and @ai-sdk/openai modules before tests run concurrently. This prevents Jest's module sandboxing from blocking dynamic imports with 'You are trying to import a file outside of the scope of the test code' errors. The function was previously converted to a no-op under the assumption that 'Jest concurrency has been stabilized elsewhere', but tests still failed when run together. Preloading ensures providers are in the module cache before concurrent tests access them via dynamic imports in createModel(). Also add note to AGENTS.md warning that tests/ipcMain takes a long time locally.
1 parent e951624 commit ed03395

File tree

3 files changed

+18
-10
lines changed

3 files changed

+18
-10
lines changed

docs/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ This project uses **Make** as the primary build orchestrator. See `Makefile` for
205205
- **Integration tests:**
206206
- Run specific integration test: `TEST_INTEGRATION=1 bun x jest tests/ipcMain/sendMessage.test.ts -t "test name pattern"`
207207
- Run all integration tests: `TEST_INTEGRATION=1 bun x jest tests` (~35 seconds, runs 40 tests)
208+
- **⚠️ Running `tests/ipcMain` locally takes a very long time.** Prefer running specific test files or use `-t` to filter to specific tests.
208209
- **Performance**: Tests use `test.concurrent()` to run in parallel within each file
209210
- **NEVER bypass IPC in integration tests** - Integration tests must use the real IPC communication paths (e.g., `mockIpcRenderer.invoke()`) even when it's harder. Directly accessing services (HistoryService, PartialService, etc.) or manipulating config/state directly bypasses the integration layer and defeats the purpose of the test.
210211

src/services/aiService.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,9 @@ if (typeof globalFetchWithExtras.certificate === "function") {
9797
* In production, providers are lazy-loaded on first use to optimize startup time.
9898
* In tests, we preload them once during setup to ensure reliable concurrent execution.
9999
*/
100-
// eslint-disable-next-line @typescript-eslint/require-await
101100
export async function preloadAISDKProviders(): Promise<void> {
102-
// No-op: Providers are lazy-loaded in createModel().
103-
// Preloading was previously used to avoid race conditions in concurrent tests,
104-
// but Jest concurrency has been stabilized elsewhere and this is no longer necessary.
105-
return;
101+
// Preload providers to ensure they're in the module cache before concurrent tests run
102+
await Promise.all([import("@ai-sdk/anthropic"), import("@ai-sdk/openai")]);
106103
}
107104

108105
export class AIService extends EventEmitter {

tests/ipcMain/renameWorkspace.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ if (shouldRunIntegrationTests()) {
2020
}
2121

2222
describeIntegration("IpcMain rename workspace integration tests", () => {
23-
// Load tokenizer modules once before all tests (takes ~14s)
24-
// This ensures accurate token counts for API calls without timing out individual tests
23+
// Load tokenizer modules and AI SDK providers once before all tests
24+
// This ensures accurate token counts for API calls and prevents race conditions
25+
// when tests import providers concurrently
2526
beforeAll(async () => {
26-
const { loadTokenizerModules } = await import("../../src/utils/main/tokenizer");
27-
await loadTokenizerModules();
28-
}, 30000); // 30s timeout for tokenizer loading
27+
const [{ loadTokenizerModules }, { preloadAISDKProviders }] = await Promise.all([
28+
import("../../src/utils/main/tokenizer"),
29+
import("../../src/services/aiService"),
30+
]);
31+
await Promise.all([loadTokenizerModules(), preloadAISDKProviders()]);
32+
}, 30000); // 30s timeout for tokenizer and provider loading
2933

3034
test.concurrent(
3135
"should successfully rename workspace and update all paths",
@@ -300,6 +304,9 @@ describeIntegration("IpcMain rename workspace integration tests", () => {
300304
// Send a message to create some history
301305
env.sentEvents.length = 0;
302306
const result = await sendMessageWithModel(env.mockIpcRenderer, workspaceId, "What is 2+2?");
307+
if (!result.success) {
308+
console.error("Send message failed:", result.error);
309+
}
303310
expect(result.success).toBe(true);
304311

305312
// Wait for response
@@ -370,6 +377,9 @@ describeIntegration("IpcMain rename workspace integration tests", () => {
370377
"anthropic",
371378
"claude-sonnet-4-5"
372379
);
380+
if (!result.success) {
381+
console.error("Send message failed:", result.error);
382+
}
373383
expect(result.success).toBe(true);
374384

375385
// Wait for response

0 commit comments

Comments
 (0)