test: add Phase 4 database integration tests (partial)
- 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
This commit is contained in:
287
tests/mocks/n8n-api/handlers.ts
Normal file
287
tests/mocks/n8n-api/handlers.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user