Skip to content

Commit 9739068

Browse files
committed
🤖 refactor: optimize integration tests usage of shared repo
- Update setupWorkspaceWithoutProvider to support existing repo reuse - Add withSharedWorkspaceNoProvider helper - Update basic and context tests to use shared repo helpers - Remove duplicate metadata test in context suite - Cleanup unused imports in all suites
1 parent 041bb5b commit 9739068

File tree

7 files changed

+111
-183
lines changed

7 files changed

+111
-183
lines changed

tests/ipcMain/sendMessage.basic.test.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import * as fs from "fs/promises";
22
import * as path from "path";
3-
import {
4-
setupWorkspace,
5-
setupWorkspaceWithoutProvider,
6-
shouldRunIntegrationTests,
7-
validateApiKeys,
8-
} from "./setup";
3+
import { setupWorkspace, shouldRunIntegrationTests, validateApiKeys } from "./setup";
94
import {
105
sendMessageWithModel,
116
sendMessage,
@@ -20,7 +15,12 @@ import {
2015
modelString,
2116
configureTestRetries,
2217
} from "./helpers";
23-
import { createSharedRepo, cleanupSharedRepo, withSharedWorkspace } from "./sendMessageTestHelpers";
18+
import {
19+
createSharedRepo,
20+
cleanupSharedRepo,
21+
withSharedWorkspace,
22+
withSharedWorkspaceNoProvider,
23+
} from "./sendMessageTestHelpers";
2424
import type { StreamDeltaEvent } from "../../src/common/types/stream";
2525
import { IPC_CHANNELS } from "../../src/common/constants/ipc-constants";
2626

@@ -411,8 +411,7 @@ describeIntegration("IpcMain sendMessage integration tests", () => {
411411
test.concurrent(
412412
"should preserve arbitrary frontend metadata through IPC round-trip",
413413
async () => {
414-
const { env, workspaceId, cleanup } = await setupWorkspaceWithoutProvider();
415-
try {
414+
await withSharedWorkspaceNoProvider(async ({ env, workspaceId }) => {
416415
// Create structured metadata
417416
const testMetadata = {
418417
type: "compaction-request" as const,
@@ -466,9 +465,7 @@ describeIntegration("IpcMain sendMessage integration tests", () => {
466465
expect(metadata.muxMetadata.rawCommand).toBe("/compact -c continue working");
467466
expect(metadata.muxMetadata.parsed.continueMessage).toBe("continue working");
468467
expect(metadata.muxMetadata.parsed.maxOutputTokens).toBe(5000);
469-
} finally {
470-
await cleanup();
471-
}
468+
});
472469
},
473470
5000
474471
);

tests/ipcMain/sendMessage.context.test.ts

Lines changed: 64 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import * as fs from "fs/promises";
22
import * as path from "path";
3-
import {
4-
setupWorkspace,
5-
setupWorkspaceWithoutProvider,
6-
shouldRunIntegrationTests,
7-
validateApiKeys,
8-
} from "./setup";
3+
import { shouldRunIntegrationTests, validateApiKeys } from "./setup";
94
import {
105
sendMessageWithModel,
116
sendMessage,
@@ -20,7 +15,12 @@ import {
2015
modelString,
2116
configureTestRetries,
2217
} from "./helpers";
23-
import { createSharedRepo, cleanupSharedRepo, withSharedWorkspace } from "./sendMessageTestHelpers";
18+
import {
19+
createSharedRepo,
20+
cleanupSharedRepo,
21+
withSharedWorkspace,
22+
withSharedWorkspaceNoProvider,
23+
} from "./sendMessageTestHelpers";
2424
import type { StreamDeltaEvent } from "../../src/common/types/stream";
2525
import { IPC_CHANNELS } from "../../src/common/constants/ipc-constants";
2626

@@ -415,10 +415,7 @@ These are general instructions that apply to all modes.
415415
test.each(PROVIDER_CONFIGS)(
416416
"%s should return api_key_not_found error when API key is missing",
417417
async (provider, model) => {
418-
const { env, workspaceId, cleanup } = await setupWorkspaceWithoutProvider(
419-
`noapi-${provider}`
420-
);
421-
try {
418+
await withSharedWorkspaceNoProvider(async ({ env, workspaceId }) => {
422419
// Try to send message without API key configured
423420
const result = await sendMessageWithModel(
424421
env.mockIpcRenderer,
@@ -432,9 +429,7 @@ These are general instructions that apply to all modes.
432429
if (!result.success && result.error.type === "api_key_not_found") {
433430
expect(result.error.provider).toBe(provider);
434431
}
435-
} finally {
436-
await cleanup();
437-
}
432+
});
438433
}
439434
);
440435
});
@@ -487,8 +482,7 @@ These are general instructions that apply to all modes.
487482
test.each(PROVIDER_CONFIGS)(
488483
"%s should include full file_edit diff in UI/history but redact it from the next provider request",
489484
async (provider, model) => {
490-
const { env, workspaceId, workspacePath, cleanup } = await setupWorkspace(provider);
491-
try {
485+
await withSharedWorkspace(provider, async ({ env, workspaceId, workspacePath }) => {
492486
// 1) Create a file and ask the model to edit it to ensure a file_edit tool runs
493487
const testFilePath = path.join(workspacePath, "redaction-edit-test.txt");
494488
await fs.writeFile(testFilePath, "line1\nline2\nline3\n", "utf-8");
@@ -553,131 +547,64 @@ These are general instructions that apply to all modes.
553547

554548
// Note: We don't assert on the exact provider payload (black box), but the fact that
555549
// the second request succeeds proves the redaction path produced valid provider messages
556-
} finally {
557-
await cleanup();
558-
}
550+
});
559551
},
560552
90000
561553
);
562554
});
563555

564-
// Test frontend metadata round-trip (no provider needed - just verifies storage)
565-
test.concurrent(
566-
"should preserve arbitrary frontend metadata through IPC round-trip",
567-
async () => {
568-
const { env, workspaceId, cleanup } = await setupWorkspaceWithoutProvider();
569-
try {
570-
// Create structured metadata
571-
const testMetadata = {
572-
type: "compaction-request" as const,
573-
rawCommand: "/compact -c continue working",
574-
parsed: {
575-
maxOutputTokens: 5000,
576-
continueMessage: "continue working",
577-
},
578-
};
579-
580-
// Send a message with frontend metadata
581-
// Use invalid model to fail fast - we only care about metadata storage
582-
const result = await env.mockIpcRenderer.invoke(
583-
IPC_CHANNELS.WORKSPACE_SEND_MESSAGE,
584-
workspaceId,
585-
"Test message with metadata",
586-
{
587-
model: "openai:gpt-4", // Valid format but provider not configured - will fail after storing message
588-
muxMetadata: testMetadata,
589-
}
590-
);
591-
592-
// Note: IPC call will fail due to missing provider config, but that's okay
593-
// We only care that the user message was written to history with metadata
594-
// (sendMessage writes user message before attempting to stream)
595-
596-
// Use event collector to get messages sent to frontend
597-
const collector = createEventCollector(env.sentEvents, workspaceId);
598-
599-
// Wait for the user message to appear in the chat channel
600-
await waitFor(() => {
601-
const messages = collector.collect();
602-
return messages.some((m) => "role" in m && m.role === "user");
603-
}, 2000);
604-
605-
// Get all messages for this workspace
606-
const allMessages = collector.collect();
607-
608-
// Find the user message we just sent
609-
const userMessage = allMessages.find((msg) => "role" in msg && msg.role === "user");
610-
expect(userMessage).toBeDefined();
611-
612-
// Verify metadata was preserved exactly as sent (black-box)
613-
expect(userMessage).toHaveProperty("metadata");
614-
const metadata = (userMessage as any).metadata;
615-
expect(metadata).toHaveProperty("muxMetadata");
616-
expect(metadata.muxMetadata).toEqual(testMetadata);
617-
618-
// Verify structured fields are accessible
619-
expect(metadata.muxMetadata.type).toBe("compaction-request");
620-
expect(metadata.muxMetadata.rawCommand).toBe("/compact -c continue working");
621-
expect(metadata.muxMetadata.parsed.continueMessage).toBe("continue working");
622-
expect(metadata.muxMetadata.parsed.maxOutputTokens).toBe(5000);
623-
} finally {
624-
await cleanup();
625-
}
626-
},
627-
5000
628-
);
629-
});
556+
// Test multi-turn conversation with response ID persistence
557+
describe.each(PROVIDER_CONFIGS)("%s:%s response ID persistence", (provider, model) => {
558+
test.concurrent(
559+
"should handle multi-turn conversation with response ID persistence",
560+
async () => {
561+
await withSharedWorkspace(provider, async ({ env, workspaceId }) => {
562+
// First message
563+
const result1 = await sendMessageWithModel(
564+
env.mockIpcRenderer,
565+
workspaceId,
566+
"What is 2+2?",
567+
modelString(provider, model)
568+
);
569+
expect(result1.success).toBe(true);
630570

631-
// Test image support across providers
632-
describe.each(PROVIDER_CONFIGS)("%s:%s image support", (provider, model) => {
633-
test.concurrent(
634-
"should handle multi-turn conversation with response ID persistence (openai reasoning models)",
635-
async () => {
636-
const { env, workspaceId, cleanup } = await setupWorkspace("openai");
637-
try {
638-
// First message
639-
const result1 = await sendMessageWithModel(
640-
env.mockIpcRenderer,
641-
workspaceId,
642-
"What is 2+2?",
643-
modelString("openai", KNOWN_MODELS.GPT_MINI.providerModelId)
644-
);
645-
expect(result1.success).toBe(true);
646-
647-
const collector1 = createEventCollector(env.sentEvents, workspaceId);
648-
await collector1.waitForEvent("stream-end", 30000);
649-
assertStreamSuccess(collector1);
650-
env.sentEvents.length = 0; // Clear events
651-
652-
// Second message - should use previousResponseId from first
653-
const result2 = await sendMessageWithModel(
654-
env.mockIpcRenderer,
655-
workspaceId,
656-
"Now add 3 to that",
657-
modelString("openai", KNOWN_MODELS.GPT_MINI.providerModelId)
658-
);
659-
expect(result2.success).toBe(true);
660-
661-
const collector2 = createEventCollector(env.sentEvents, workspaceId);
662-
await collector2.waitForEvent("stream-end", 30000);
663-
assertStreamSuccess(collector2);
664-
665-
// Verify history contains both messages
666-
const history = await readChatHistory(env.tempDir, workspaceId);
667-
expect(history.length).toBeGreaterThanOrEqual(4); // 2 user + 2 assistant
668-
669-
// Verify assistant messages have responseId
670-
const assistantMessages = history.filter((m) => m.role === "assistant");
671-
expect(assistantMessages.length).toBeGreaterThanOrEqual(2);
672-
// Check that responseId exists (type is unknown from JSONL parsing)
673-
const firstAssistant = assistantMessages[0] as any;
674-
const secondAssistant = assistantMessages[1] as any;
675-
expect(firstAssistant.metadata?.providerMetadata?.openai?.responseId).toBeDefined();
676-
expect(secondAssistant.metadata?.providerMetadata?.openai?.responseId).toBeDefined();
677-
} finally {
678-
await cleanup();
679-
}
680-
},
681-
60000
682-
);
571+
const collector1 = createEventCollector(env.sentEvents, workspaceId);
572+
await collector1.waitForEvent("stream-end", 30000);
573+
assertStreamSuccess(collector1);
574+
env.sentEvents.length = 0; // Clear events
575+
576+
// Second message - should use previousResponseId from first
577+
const result2 = await sendMessageWithModel(
578+
env.mockIpcRenderer,
579+
workspaceId,
580+
"Now add 3 to that",
581+
modelString(provider, model)
582+
);
583+
expect(result2.success).toBe(true);
584+
585+
const collector2 = createEventCollector(env.sentEvents, workspaceId);
586+
await collector2.waitForEvent("stream-end", 30000);
587+
assertStreamSuccess(collector2);
588+
589+
// Verify history contains both messages
590+
// Note: readChatHistory needs the temp directory (root of config).
591+
const history = await readChatHistory(env.tempDir, workspaceId);
592+
expect(history.length).toBeGreaterThanOrEqual(4); // 2 user + 2 assistant
593+
594+
// Verify assistant messages have responseId
595+
const assistantMessages = history.filter((m) => m.role === "assistant");
596+
expect(assistantMessages.length).toBeGreaterThanOrEqual(2);
597+
598+
// Check that responseId exists (if provider supports it)
599+
if (provider === "openai") {
600+
const firstAssistant = assistantMessages[0] as any;
601+
const secondAssistant = assistantMessages[1] as any;
602+
expect(firstAssistant.metadata?.providerMetadata?.openai?.responseId).toBeDefined();
603+
expect(secondAssistant.metadata?.providerMetadata?.openai?.responseId).toBeDefined();
604+
}
605+
});
606+
},
607+
60000
608+
);
609+
});
683610
});

tests/ipcMain/sendMessage.errors.test.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import * as fs from "fs/promises";
22
import * as path from "path";
3-
import {
4-
setupWorkspace,
5-
setupWorkspaceWithoutProvider,
6-
shouldRunIntegrationTests,
7-
validateApiKeys,
8-
} from "./setup";
3+
import { shouldRunIntegrationTests, validateApiKeys } from "./setup";
94
import {
105
sendMessageWithModel,
116
sendMessage,
@@ -16,7 +11,6 @@ import {
1611
buildLargeHistory,
1712
waitForStreamSuccess,
1813
readChatHistory,
19-
TEST_IMAGES,
2014
modelString,
2115
configureTestRetries,
2216
} from "./helpers";

tests/ipcMain/sendMessage.heavy.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import * as fs from "fs/promises";
2-
import * as path from "path";
3-
import {
4-
setupWorkspace,
5-
setupWorkspaceWithoutProvider,
6-
shouldRunIntegrationTests,
7-
validateApiKeys,
8-
} from "./setup";
1+
import { shouldRunIntegrationTests, validateApiKeys } from "./setup";
92
import {
103
sendMessageWithModel,
114
sendMessage,
@@ -16,7 +9,6 @@ import {
169
buildLargeHistory,
1710
waitForStreamSuccess,
1811
readChatHistory,
19-
TEST_IMAGES,
2012
modelString,
2113
configureTestRetries,
2214
} from "./helpers";

tests/ipcMain/sendMessage.images.test.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1-
import * as fs from "fs/promises";
2-
import * as path from "path";
3-
import {
4-
setupWorkspace,
5-
setupWorkspaceWithoutProvider,
6-
shouldRunIntegrationTests,
7-
validateApiKeys,
8-
} from "./setup";
1+
import { shouldRunIntegrationTests, validateApiKeys } from "./setup";
92
import {
103
sendMessageWithModel,
114
sendMessage,
125
createEventCollector,
136
assertStreamSuccess,
147
assertError,
158
waitFor,
16-
buildLargeHistory,
179
waitForStreamSuccess,
1810
readChatHistory,
1911
TEST_IMAGES,
@@ -53,7 +45,7 @@ describeIntegration("IpcMain sendMessage integration tests", () => {
5345

5446
// Run tests for each provider concurrently
5547
describe.each(PROVIDER_CONFIGS)("%s:%s provider tests", (provider, model) => {
56-
// Test frontend metadata round-trip (no provider needed - just verifies storage)
48+
// Test image support
5749
test.concurrent(
5850
"should send images to AI model and get response",
5951
async () => {

tests/ipcMain/sendMessageTestHelpers.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createTempGitRepo, cleanupTempGitRepo } from "./helpers";
2-
import { setupWorkspace } from "./setup";
2+
import { setupWorkspace, setupWorkspaceWithoutProvider } from "./setup";
33
import type { TestEnvironment } from "./setup";
44

55
let sharedRepoPath: string | undefined;
@@ -42,3 +42,20 @@ export async function withSharedWorkspace(
4242
await cleanup();
4343
}
4444
}
45+
46+
export async function withSharedWorkspaceNoProvider(
47+
testFn: (context: SharedWorkspaceContext) => Promise<void>
48+
): Promise<void> {
49+
if (!sharedRepoPath) {
50+
throw new Error("Shared repo has not been created yet.");
51+
}
52+
53+
const { env, workspaceId, workspacePath, branchName, tempGitRepo, cleanup } =
54+
await setupWorkspaceWithoutProvider(undefined, sharedRepoPath);
55+
56+
try {
57+
await testFn({ env, workspaceId, workspacePath, branchName, tempGitRepo });
58+
} finally {
59+
await cleanup();
60+
}
61+
}

0 commit comments

Comments
 (0)