- Add comprehensive test utilities for database testing - Implement connection management tests for in-memory and file databases - Add transaction tests including nested transactions and savepoints - Test database lifecycle, error handling, and performance - Include tests for WAL mode, connection pooling, and constraints Part of Phase 4: Integration Testing
287 lines
7.9 KiB
TypeScript
287 lines
7.9 KiB
TypeScript
import { http, HttpResponse, RequestHandler } from 'msw';
|
|
import { mockWorkflows } from './data/workflows';
|
|
import { mockExecutions } from './data/executions';
|
|
import { mockCredentials } from './data/credentials';
|
|
|
|
// Base URL for n8n API (will be overridden by actual URL in tests)
|
|
const API_BASE = process.env.N8N_API_URL || 'http://localhost:5678';
|
|
|
|
/**
|
|
* Default handlers for n8n API endpoints
|
|
* These can be overridden in specific tests using server.use()
|
|
*/
|
|
export const handlers: RequestHandler[] = [
|
|
// Health check endpoint
|
|
http.get('*/api/v1/health', () => {
|
|
return HttpResponse.json({
|
|
status: 'ok',
|
|
version: '1.103.2',
|
|
features: {
|
|
workflows: true,
|
|
executions: true,
|
|
credentials: true,
|
|
webhooks: true,
|
|
}
|
|
});
|
|
}),
|
|
|
|
// Workflow endpoints
|
|
http.get('*/api/v1/workflows', ({ request }) => {
|
|
const url = new URL(request.url);
|
|
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
const cursor = url.searchParams.get('cursor');
|
|
const active = url.searchParams.get('active');
|
|
|
|
let filtered = mockWorkflows;
|
|
|
|
// Filter by active status if provided
|
|
if (active !== null) {
|
|
filtered = filtered.filter(w => w.active === (active === 'true'));
|
|
}
|
|
|
|
// Simple pagination simulation
|
|
const startIndex = cursor ? parseInt(cursor) : 0;
|
|
const paginatedData = filtered.slice(startIndex, startIndex + limit);
|
|
const hasMore = startIndex + limit < filtered.length;
|
|
const nextCursor = hasMore ? String(startIndex + limit) : null;
|
|
|
|
return HttpResponse.json({
|
|
data: paginatedData,
|
|
nextCursor,
|
|
hasMore
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/workflows/:id', ({ params }) => {
|
|
const workflow = mockWorkflows.find(w => w.id === params.id);
|
|
|
|
if (!workflow) {
|
|
return HttpResponse.json(
|
|
{ message: 'Workflow not found', code: 'NOT_FOUND' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
return HttpResponse.json({ data: workflow });
|
|
}),
|
|
|
|
http.post('*/api/v1/workflows', async ({ request }) => {
|
|
const body = await request.json() as any;
|
|
|
|
// Validate required fields
|
|
if (!body.name || !body.nodes || !body.connections) {
|
|
return HttpResponse.json(
|
|
{
|
|
message: 'Validation failed',
|
|
errors: {
|
|
name: !body.name ? 'Name is required' : undefined,
|
|
nodes: !body.nodes ? 'Nodes are required' : undefined,
|
|
connections: !body.connections ? 'Connections are required' : undefined,
|
|
},
|
|
code: 'VALIDATION_ERROR'
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const newWorkflow = {
|
|
id: `workflow_${Date.now()}`,
|
|
name: body.name,
|
|
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'
|
|
};
|
|
|
|
mockWorkflows.push(newWorkflow);
|
|
|
|
return HttpResponse.json({ data: newWorkflow }, { status: 201 });
|
|
}),
|
|
|
|
http.patch('*/api/v1/workflows/:id', async ({ params, request }) => {
|
|
const workflowIndex = mockWorkflows.findIndex(w => w.id === params.id);
|
|
|
|
if (workflowIndex === -1) {
|
|
return HttpResponse.json(
|
|
{ message: 'Workflow not found', code: 'NOT_FOUND' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const body = await request.json() as any;
|
|
const updatedWorkflow = {
|
|
...mockWorkflows[workflowIndex],
|
|
...body,
|
|
id: params.id, // Ensure ID doesn't change
|
|
updatedAt: new Date().toISOString(),
|
|
versionId: String(parseInt(mockWorkflows[workflowIndex].versionId) + 1)
|
|
};
|
|
|
|
mockWorkflows[workflowIndex] = updatedWorkflow;
|
|
|
|
return HttpResponse.json({ data: updatedWorkflow });
|
|
}),
|
|
|
|
http.delete('*/api/v1/workflows/:id', ({ params }) => {
|
|
const workflowIndex = mockWorkflows.findIndex(w => w.id === params.id);
|
|
|
|
if (workflowIndex === -1) {
|
|
return HttpResponse.json(
|
|
{ message: 'Workflow not found', code: 'NOT_FOUND' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
mockWorkflows.splice(workflowIndex, 1);
|
|
|
|
return HttpResponse.json({ success: true });
|
|
}),
|
|
|
|
// Execution endpoints
|
|
http.get('*/api/v1/executions', ({ request }) => {
|
|
const url = new URL(request.url);
|
|
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
const cursor = url.searchParams.get('cursor');
|
|
const workflowId = url.searchParams.get('workflowId');
|
|
const status = url.searchParams.get('status');
|
|
|
|
let filtered = mockExecutions;
|
|
|
|
// Filter by workflow ID if provided
|
|
if (workflowId) {
|
|
filtered = filtered.filter(e => e.workflowId === workflowId);
|
|
}
|
|
|
|
// Filter by status if provided
|
|
if (status) {
|
|
filtered = filtered.filter(e => e.status === status);
|
|
}
|
|
|
|
// Simple pagination simulation
|
|
const startIndex = cursor ? parseInt(cursor) : 0;
|
|
const paginatedData = filtered.slice(startIndex, startIndex + limit);
|
|
const hasMore = startIndex + limit < filtered.length;
|
|
const nextCursor = hasMore ? String(startIndex + limit) : null;
|
|
|
|
return HttpResponse.json({
|
|
data: paginatedData,
|
|
nextCursor,
|
|
hasMore
|
|
});
|
|
}),
|
|
|
|
http.get('*/api/v1/executions/:id', ({ params }) => {
|
|
const execution = mockExecutions.find(e => e.id === params.id);
|
|
|
|
if (!execution) {
|
|
return HttpResponse.json(
|
|
{ message: 'Execution not found', code: 'NOT_FOUND' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
return HttpResponse.json({ data: execution });
|
|
}),
|
|
|
|
http.delete('*/api/v1/executions/:id', ({ params }) => {
|
|
const executionIndex = mockExecutions.findIndex(e => e.id === params.id);
|
|
|
|
if (executionIndex === -1) {
|
|
return HttpResponse.json(
|
|
{ message: 'Execution not found', code: 'NOT_FOUND' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
mockExecutions.splice(executionIndex, 1);
|
|
|
|
return HttpResponse.json({ success: true });
|
|
}),
|
|
|
|
// Webhook endpoints (dynamic handling)
|
|
http.all('*/webhook/*', async ({ request }) => {
|
|
const url = new URL(request.url);
|
|
const method = request.method;
|
|
const body = request.body ? await request.json() : undefined;
|
|
|
|
// Log webhook trigger in debug mode
|
|
if (process.env.MSW_DEBUG === 'true') {
|
|
console.log('[MSW] Webhook triggered:', {
|
|
url: url.pathname,
|
|
method,
|
|
body
|
|
});
|
|
}
|
|
|
|
// Return success response by default
|
|
return HttpResponse.json({
|
|
success: true,
|
|
webhookUrl: url.pathname,
|
|
method,
|
|
timestamp: new Date().toISOString(),
|
|
data: body
|
|
});
|
|
}),
|
|
|
|
// Catch-all for unhandled API routes (helps identify missing handlers)
|
|
http.all('*/api/*', ({ request }) => {
|
|
console.warn('[MSW] Unhandled API request:', request.method, request.url);
|
|
|
|
return HttpResponse.json(
|
|
{
|
|
message: 'Not implemented in mock',
|
|
code: 'NOT_IMPLEMENTED',
|
|
path: new URL(request.url).pathname,
|
|
method: request.method
|
|
},
|
|
{ status: 501 }
|
|
);
|
|
}),
|
|
];
|
|
|
|
/**
|
|
* Dynamic handler registration helpers
|
|
*/
|
|
export const dynamicHandlers = {
|
|
/**
|
|
* Add a workflow that will be returned by GET requests
|
|
*/
|
|
addWorkflow: (workflow: any) => {
|
|
mockWorkflows.push(workflow);
|
|
},
|
|
|
|
/**
|
|
* Clear all mock workflows
|
|
*/
|
|
clearWorkflows: () => {
|
|
mockWorkflows.length = 0;
|
|
},
|
|
|
|
/**
|
|
* Add an execution that will be returned by GET requests
|
|
*/
|
|
addExecution: (execution: any) => {
|
|
mockExecutions.push(execution);
|
|
},
|
|
|
|
/**
|
|
* Clear all mock executions
|
|
*/
|
|
clearExecutions: () => {
|
|
mockExecutions.length = 0;
|
|
},
|
|
|
|
/**
|
|
* Reset all mock data to initial state
|
|
*/
|
|
resetAll: () => {
|
|
// Reset arrays to initial state (implementation depends on data modules)
|
|
mockWorkflows.length = 0;
|
|
mockExecutions.length = 0;
|
|
mockCredentials.length = 0;
|
|
}
|
|
}; |