Files
cc-switch/tests/msw/handlers.ts
Jason 96a8712f2d test: migrate to MSW testing architecture for App integration test
Major testing infrastructure upgrade from manual mocks to Mock Service Worker (MSW):

New MSW infrastructure (tests/msw/):
- Add state.ts: In-memory state manager with full CRUD operations
  - Manage providers and current selections per app type (Claude/Codex)
  - Auto-switch current provider when deleted
  - Deep clone to prevent reference pollution
- Add handlers.ts: HTTP request handlers for 10 Tauri API endpoints
  - Mock get_providers, switch_provider, add/update/delete_provider
  - Mock update_sort_order, update_tray_menu, import_default_config
  - Support error scenarios (404 for non-existent providers)
- Add tauriMocks.ts: Tauri API mock layer
  - Transparently convert invoke() calls to HTTP requests
  - Mock event listener system with emitTauriEvent helper
  - Use cross-fetch for Node.js environment
- Add server.ts: MSW server setup for Node.js test environment

Refactor App.test.tsx (-170 lines, -43%):
- Remove 23 manual mocks (useProvidersQuery, useProviderActions, etc.)
- Run real hooks with MSW-backed API calls instead of mock implementations
- Test real state changes instead of mock call counts
- Add comprehensive flow: duplicate → create → edit → delete → event listening
- Verify actual provider list changes and current selection updates

Setup integration:
- Add MSW server lifecycle to tests/setupTests.ts
  - Start server before all tests
  - Reset handlers and state after each test
  - Close server after all tests complete
- Clear all mocks in afterEach for test isolation

Dependencies:
- Add msw@^2.11.6 for API mocking
- Add cross-fetch@^4.1.0 for fetch polyfill in Node.js

Type fixes:
- Add missing imports (AppType, Provider) in handlers.ts
- Fix HttpResponse.json generic constraint with as any (MSW best practice)
- Change invalid category "default" to "official" in state.ts

Test results: All 50 tests passing across 8 files, 0 TypeScript errors
2025-10-25 16:48:43 +08:00

101 lines
2.9 KiB
TypeScript

import { http, HttpResponse } from "msw";
import type { AppType } from "@/lib/api/types";
import type { Provider } from "@/types";
import {
addProvider,
deleteProvider,
getCurrentProviderId,
getProviders,
listProviders,
resetProviderState,
setCurrentProviderId,
updateProvider,
updateSortOrder,
} from "./state";
const TAURI_ENDPOINT = "http://tauri.local";
const withJson = async <T>(request: Request): Promise<T> => {
try {
const body = await request.text();
if (!body) return {} as T;
return JSON.parse(body) as T;
} catch {
return {} as T;
}
};
const success = <T>(payload: T) => HttpResponse.json(payload as any);
export const handlers = [
http.post(`${TAURI_ENDPOINT}/get_providers`, async ({ request }) => {
const { app_type } = await withJson<{ app_type: AppType }>(request);
return success(getProviders(app_type));
}),
http.post(`${TAURI_ENDPOINT}/get_current_provider`, async ({ request }) => {
const { app_type } = await withJson<{ app_type: AppType }>(request);
return success(getCurrentProviderId(app_type));
}),
http.post(`${TAURI_ENDPOINT}/update_providers_sort_order`, async ({ request }) => {
const { updates = [], app_type } = await withJson<{
updates: { id: string; sortIndex: number }[];
app_type: AppType;
}>(request);
updateSortOrder(app_type, updates);
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/update_tray_menu`, () => success(true)),
http.post(`${TAURI_ENDPOINT}/switch_provider`, async ({ request }) => {
const { id, app_type } = await withJson<{ id: string; app_type: AppType }>(
request,
);
const providers = listProviders(app_type);
if (!providers[id]) {
return HttpResponse.json(false, { status: 404 });
}
setCurrentProviderId(app_type, id);
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/add_provider`, async ({ request }) => {
const { provider, app_type } = await withJson<{
provider: Provider & { id?: string };
app_type: AppType;
}>(request);
const newId = provider.id ?? `mock-${Date.now()}`;
addProvider(app_type, { ...provider, id: newId });
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/update_provider`, async ({ request }) => {
const { provider, app_type } = await withJson<{
provider: Provider;
app_type: AppType;
}>(request);
updateProvider(app_type, provider);
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/delete_provider`, async ({ request }) => {
const { id, app_type } = await withJson<{ id: string; app_type: AppType }>(
request,
);
deleteProvider(app_type, id);
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/import_default_config`, async () => {
resetProviderState();
return success(true);
}),
http.post(`${TAURI_ENDPOINT}/open_external`, () => success(true)),
http.post(`${TAURI_ENDPOINT}/restart_app`, () => success(true)),
];