msw-setup.test.tsβ’9.82 kB
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { mswTestServer, n8nApiMock, testDataBuilders, integrationTestServer } from './setup/msw-test-server';
import { http, HttpResponse } from 'msw';
import axios from 'axios';
import { server } from './setup/integration-setup';
describe('MSW Setup Verification', () => {
const baseUrl = 'http://localhost:5678';
describe('Global MSW Setup', () => {
it('should intercept n8n API requests with default handlers', async () => {
// This uses the global MSW setup from vitest.config.ts
const response = await axios.get(`${baseUrl}/api/v1/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({
status: 'ok',
version: '1.103.2',
features: {
workflows: true,
executions: true,
credentials: true,
webhooks: true,
}
});
});
it('should allow custom handlers for specific tests', async () => {
// Add a custom handler just for this test using the global server
server.use(
http.get('*/api/v1/custom-endpoint', () => {
return HttpResponse.json({ custom: true });
})
);
const response = await axios.get(`${baseUrl}/api/v1/custom-endpoint`);
expect(response.status).toBe(200);
expect(response.data).toEqual({ custom: true });
});
it('should return mock workflows', async () => {
const response = await axios.get(`${baseUrl}/api/v1/workflows`);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('data');
expect(Array.isArray(response.data.data)).toBe(true);
expect(response.data.data.length).toBeGreaterThan(0);
});
});
describe('Integration Test Server', () => {
// Use the global MSW server instance for these tests
afterEach(() => {
// Reset handlers after each test to ensure clean state
server.resetHandlers();
});
it('should handle workflow creation with custom response', async () => {
// Use the global server instance to add custom handler
server.use(
http.post('*/api/v1/workflows', async ({ request }) => {
const body = await request.json() as any;
return HttpResponse.json({
data: {
id: 'custom-workflow-123',
name: 'Test Workflow from MSW',
active: body.active || false,
nodes: body.nodes,
connections: body.connections,
settings: body.settings || {},
tags: body.tags || [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
versionId: '1'
}
}, { status: 201 });
})
);
const workflowData = testDataBuilders.workflow({
name: 'My Test Workflow'
});
const response = await axios.post(`${baseUrl}/api/v1/workflows`, workflowData);
expect(response.status).toBe(201);
expect(response.data.data).toMatchObject({
id: 'custom-workflow-123',
name: 'Test Workflow from MSW',
nodes: workflowData.nodes,
connections: workflowData.connections
});
});
it('should handle error responses', async () => {
server.use(
http.get('*/api/v1/workflows/missing', () => {
return HttpResponse.json(
{
message: 'Workflow not found',
code: 'NOT_FOUND',
timestamp: new Date().toISOString()
},
{ status: 404 }
);
})
);
try {
await axios.get(`${baseUrl}/api/v1/workflows/missing`);
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error.response.status).toBe(404);
expect(error.response.data).toEqual({
message: 'Workflow not found',
code: 'NOT_FOUND',
timestamp: expect.any(String)
});
}
});
it('should simulate rate limiting', async () => {
let requestCount = 0;
const limit = 5;
server.use(
http.get('*/api/v1/rate-limited', () => {
requestCount++;
if (requestCount > limit) {
return HttpResponse.json(
{
message: 'Rate limit exceeded',
code: 'RATE_LIMIT',
retryAfter: 60
},
{
status: 429,
headers: {
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(Date.now() + 60000)
}
}
);
}
return HttpResponse.json({ success: true });
})
);
// Make requests up to the limit
for (let i = 0; i < 5; i++) {
const response = await axios.get(`${baseUrl}/api/v1/rate-limited`);
expect(response.status).toBe(200);
}
// Next request should be rate limited
try {
await axios.get(`${baseUrl}/api/v1/rate-limited`);
expect.fail('Should have been rate limited');
} catch (error: any) {
expect(error.response.status).toBe(429);
expect(error.response.data.code).toBe('RATE_LIMIT');
expect(error.response.headers['x-ratelimit-remaining']).toBe('0');
}
});
it('should handle webhook execution', async () => {
server.use(
http.post('*/webhook/test-webhook', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({
processed: true,
result: 'success',
webhookReceived: {
path: 'test-webhook',
method: 'POST',
body,
timestamp: new Date().toISOString()
}
});
})
);
const webhookData = { message: 'Test webhook payload' };
const response = await axios.post(`${baseUrl}/webhook/test-webhook`, webhookData);
expect(response.status).toBe(200);
expect(response.data).toMatchObject({
processed: true,
result: 'success',
webhookReceived: {
path: 'test-webhook',
method: 'POST',
body: webhookData,
timestamp: expect.any(String)
}
});
});
it('should wait for specific requests', async () => {
// Since the global server is already handling these endpoints,
// we'll just make the requests and verify they succeed
const responses = await Promise.all([
axios.get(`${baseUrl}/api/v1/workflows`),
axios.get(`${baseUrl}/api/v1/executions`)
]);
expect(responses).toHaveLength(2);
expect(responses[0].status).toBe(200);
expect(responses[0].config.url).toContain('/api/v1/workflows');
expect(responses[1].status).toBe(200);
expect(responses[1].config.url).toContain('/api/v1/executions');
}, { timeout: 10000 }); // Increase timeout for this specific test
it('should work with scoped handlers', async () => {
// First add the scoped handler
server.use(
http.get('*/api/v1/scoped', () => {
return HttpResponse.json({ scoped: true });
})
);
// Make the request while handler is active
const response = await axios.get(`${baseUrl}/api/v1/scoped`);
expect(response.data).toEqual({ scoped: true });
// Reset handlers to remove the scoped handler
server.resetHandlers();
// Verify the scoped handler is no longer active
// Since there's no handler for this endpoint now, it should fall through to the catch-all
try {
await axios.get(`${baseUrl}/api/v1/scoped`);
expect.fail('Should have returned 501');
} catch (error: any) {
expect(error.response.status).toBe(501);
}
});
});
describe('Factory Functions', () => {
it('should create workflows using factory', async () => {
const { workflowFactory } = await import('../mocks/n8n-api/data/workflows');
const simpleWorkflow = workflowFactory.simple('n8n-nodes-base.slack', {
resource: 'message',
operation: 'post',
channel: '#general',
text: 'Hello from test'
});
expect(simpleWorkflow).toMatchObject({
id: expect.stringMatching(/^workflow_\d+$/),
name: 'Test n8n-nodes-base.slack Workflow', // Factory uses nodeType in the name
active: true,
nodes: expect.arrayContaining([
expect.objectContaining({ type: 'n8n-nodes-base.start' }),
expect.objectContaining({
type: 'n8n-nodes-base.slack',
parameters: {
resource: 'message',
operation: 'post',
channel: '#general',
text: 'Hello from test'
}
})
])
});
});
it('should create executions using factory', async () => {
const { executionFactory } = await import('../mocks/n8n-api/data/executions');
const successExecution = executionFactory.success('workflow_123');
const errorExecution = executionFactory.error('workflow_456', {
message: 'Connection timeout',
node: 'http_request_1'
});
expect(successExecution).toMatchObject({
workflowId: 'workflow_123',
status: 'success',
mode: 'manual'
});
expect(errorExecution).toMatchObject({
workflowId: 'workflow_456',
status: 'error',
error: {
message: 'Connection timeout',
node: 'http_request_1'
}
});
});
});
});