Files
n8n-mcp/tests/setup/msw-setup.ts
czlonkowski 7f4c0ae3a9 fix: prevent MSW from loading globally to fix CI test hanging
- Remove msw-setup.ts from global vitest setupFiles
- Create separate integration-specific MSW setup
- Add vitest.config.integration.ts for integration tests
- Update package.json to use integration config for integration tests
- Update CI workflow to run unit and integration tests separately
- Add aggressive cleanup in integration MSW setup for CI environment

This prevents MSW from being initialized for unit tests where it's not needed,
which was causing tests to hang in CI after all tests completed.
2025-07-29 14:16:13 +02:00

195 lines
5.5 KiB
TypeScript

/**
* MSW Setup for Tests
*
* NOTE: This file is NO LONGER loaded globally via vitest.config.ts to prevent
* hanging in CI. Instead:
* - Unit tests run without MSW
* - Integration tests use ./tests/integration/setup/integration-setup.ts
*
* This file is kept for backwards compatibility and can be imported directly
* by specific tests that need MSW functionality.
*/
import { setupServer } from 'msw/node';
import { HttpResponse, http, RequestHandler } from 'msw';
import { afterAll, afterEach, beforeAll } from 'vitest';
// Import handlers from our centralized location
import { handlers as defaultHandlers } from '../mocks/n8n-api/handlers';
// Create the MSW server instance with default handlers
export const server = setupServer(...defaultHandlers);
// Enable request logging in development/debugging
if (process.env.MSW_DEBUG === 'true' || process.env.TEST_DEBUG === 'true') {
server.events.on('request:start', ({ request }) => {
console.log('[MSW] %s %s', request.method, request.url);
});
server.events.on('request:match', ({ request }) => {
console.log('[MSW] Request matched:', request.method, request.url);
});
server.events.on('request:unhandled', ({ request }) => {
console.warn('[MSW] Unhandled request:', request.method, request.url);
});
server.events.on('response:mocked', ({ request, response }) => {
console.log('[MSW] Mocked response for %s %s: %d',
request.method,
request.url,
response.status
);
});
}
// Start server before all tests
beforeAll(() => {
server.listen({
onUnhandledRequest: process.env.CI === 'true' ? 'error' : 'warn',
});
});
// Reset handlers after each test (important for test isolation)
afterEach(() => {
server.resetHandlers();
});
// Clean up after all tests
afterAll(() => {
server.close();
});
/**
* Utility function to add temporary handlers for specific tests
* @param handlers Array of MSW request handlers
*/
export function useHandlers(...handlers: RequestHandler[]) {
server.use(...handlers);
}
/**
* Utility to wait for a specific request to be made
* Useful for testing async operations
*/
export function waitForRequest(method: string, url: string | RegExp, timeout = 5000): Promise<Request> {
return new Promise((resolve, reject) => {
let timeoutId: NodeJS.Timeout;
const handler = ({ request }: { request: Request }) => {
if (request.method === method &&
(typeof url === 'string' ? request.url === url : url.test(request.url))) {
clearTimeout(timeoutId);
server.events.removeListener('request:match', handler);
resolve(request);
}
};
// Set timeout
timeoutId = setTimeout(() => {
server.events.removeListener('request:match', handler);
reject(new Error(`Timeout waiting for ${method} request to ${url}`));
}, timeout);
server.events.on('request:match', handler);
});
}
/**
* Create a handler factory for common n8n API patterns
*/
export const n8nHandlerFactory = {
// Workflow endpoints
workflow: {
list: (workflows: any[] = []) =>
http.get('*/api/v1/workflows', () => {
return HttpResponse.json({ data: workflows, nextCursor: null });
}),
get: (id: string, workflow: any) =>
http.get(`*/api/v1/workflows/${id}`, () => {
return HttpResponse.json({ data: workflow });
}),
create: () =>
http.post('*/api/v1/workflows', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({
data: {
id: 'mock-workflow-id',
...body,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
});
}),
update: (id: string) =>
http.patch(`*/api/v1/workflows/${id}`, async ({ request }) => {
const body = await request.json();
return HttpResponse.json({
data: {
id,
...body,
updatedAt: new Date().toISOString()
}
});
}),
delete: (id: string) =>
http.delete(`*/api/v1/workflows/${id}`, () => {
return HttpResponse.json({ success: true });
}),
},
// Execution endpoints
execution: {
list: (executions: any[] = []) =>
http.get('*/api/v1/executions', () => {
return HttpResponse.json({ data: executions, nextCursor: null });
}),
get: (id: string, execution: any) =>
http.get(`*/api/v1/executions/${id}`, () => {
return HttpResponse.json({ data: execution });
}),
},
// Webhook endpoints
webhook: {
trigger: (webhookUrl: string, response: any = { success: true }) =>
http.all(webhookUrl, () => {
return HttpResponse.json(response);
}),
},
// Error responses
error: {
notFound: (resource: string = 'resource') =>
HttpResponse.json(
{ message: `${resource} not found`, code: 'NOT_FOUND' },
{ status: 404 }
),
unauthorized: () =>
HttpResponse.json(
{ message: 'Unauthorized', code: 'UNAUTHORIZED' },
{ status: 401 }
),
serverError: (message: string = 'Internal server error') =>
HttpResponse.json(
{ message, code: 'INTERNAL_ERROR' },
{ status: 500 }
),
validationError: (errors: any) =>
HttpResponse.json(
{ message: 'Validation failed', errors, code: 'VALIDATION_ERROR' },
{ status: 400 }
),
}
};
// Export for use in tests
export { http, HttpResponse } from 'msw';