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:
270
tests/integration/setup/msw-test-server.ts
Normal file
270
tests/integration/setup/msw-test-server.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { HttpResponse, http } from 'msw';
|
||||
import type { RequestHandler } from 'msw';
|
||||
import { handlers as defaultHandlers } from '../../mocks/n8n-api/handlers';
|
||||
|
||||
/**
|
||||
* MSW server instance for integration tests
|
||||
* This is separate from the global MSW setup to allow for more control
|
||||
* in integration tests that may need specific handler configurations
|
||||
*/
|
||||
export const integrationTestServer = setupServer(...defaultHandlers);
|
||||
|
||||
/**
|
||||
* Enhanced server controls for integration tests
|
||||
*/
|
||||
export const mswTestServer = {
|
||||
/**
|
||||
* Start the server with specific options
|
||||
*/
|
||||
start: (options?: {
|
||||
onUnhandledRequest?: 'error' | 'warn' | 'bypass';
|
||||
quiet?: boolean;
|
||||
}) => {
|
||||
integrationTestServer.listen({
|
||||
onUnhandledRequest: options?.onUnhandledRequest || 'warn',
|
||||
});
|
||||
|
||||
if (!options?.quiet && process.env.MSW_DEBUG === 'true') {
|
||||
integrationTestServer.events.on('request:start', ({ request }) => {
|
||||
console.log('[Integration MSW] %s %s', request.method, request.url);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
*/
|
||||
stop: () => {
|
||||
integrationTestServer.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset handlers to defaults
|
||||
*/
|
||||
reset: () => {
|
||||
integrationTestServer.resetHandlers();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add handlers for a specific test
|
||||
*/
|
||||
use: (...handlers: RequestHandler[]) => {
|
||||
integrationTestServer.use(...handlers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace all handlers (useful for isolated test scenarios)
|
||||
*/
|
||||
replaceAll: (...handlers: RequestHandler[]) => {
|
||||
integrationTestServer.resetHandlers(...handlers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Wait for a specific number of requests to be made
|
||||
*/
|
||||
waitForRequests: (count: number, timeout = 5000): Promise<Request[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requests: Request[] = [];
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(new Error(`Timeout waiting for ${count} requests. Got ${requests.length}`));
|
||||
}, timeout);
|
||||
|
||||
integrationTestServer.events.on('request:match', ({ request }) => {
|
||||
requests.push(request);
|
||||
if (requests.length === count) {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(requests);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify no unhandled requests were made
|
||||
*/
|
||||
verifyNoUnhandledRequests: (): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let hasUnhandled = false;
|
||||
|
||||
integrationTestServer.events.on('request:unhandled', ({ request }) => {
|
||||
hasUnhandled = true;
|
||||
reject(new Error(`Unhandled request: ${request.method} ${request.url}`));
|
||||
});
|
||||
|
||||
// Give a small delay to allow any pending requests
|
||||
setTimeout(() => {
|
||||
if (!hasUnhandled) {
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a scoped server for a specific test
|
||||
* Automatically starts and stops the server
|
||||
*/
|
||||
withScope: async <T>(
|
||||
handlers: RequestHandler[],
|
||||
testFn: () => Promise<T>
|
||||
): Promise<T> => {
|
||||
// Save current handlers
|
||||
const currentHandlers = [...defaultHandlers];
|
||||
|
||||
try {
|
||||
// Replace with scoped handlers
|
||||
integrationTestServer.resetHandlers(...handlers);
|
||||
|
||||
// Run the test
|
||||
return await testFn();
|
||||
} finally {
|
||||
// Restore original handlers
|
||||
integrationTestServer.resetHandlers(...currentHandlers);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Integration test utilities for n8n API mocking
|
||||
*/
|
||||
export const n8nApiMock = {
|
||||
/**
|
||||
* Mock a successful workflow creation
|
||||
*/
|
||||
mockWorkflowCreate: (response?: any) => {
|
||||
return http.post('*/api/v1/workflows', async ({ request }) => {
|
||||
const body = await request.json();
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
id: 'test-workflow-id',
|
||||
...body,
|
||||
...response,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
}, { status: 201 });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mock a workflow validation endpoint
|
||||
*/
|
||||
mockWorkflowValidate: (validationResult: { valid: boolean; errors?: any[] }) => {
|
||||
return http.post('*/api/v1/workflows/validate', async () => {
|
||||
return HttpResponse.json(validationResult);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mock webhook execution
|
||||
*/
|
||||
mockWebhookExecution: (webhookPath: string, response: any) => {
|
||||
return http.all(`*/webhook/${webhookPath}`, async ({ request }) => {
|
||||
const body = request.body ? await request.json() : undefined;
|
||||
|
||||
// Simulate webhook processing
|
||||
return HttpResponse.json({
|
||||
...response,
|
||||
webhookReceived: {
|
||||
path: webhookPath,
|
||||
method: request.method,
|
||||
body,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mock API error responses
|
||||
*/
|
||||
mockError: (endpoint: string, error: { status: number; message: string; code?: string }) => {
|
||||
return http.all(endpoint, () => {
|
||||
return HttpResponse.json(
|
||||
{
|
||||
message: error.message,
|
||||
code: error.code || 'ERROR',
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
{ status: error.status }
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mock rate limiting
|
||||
*/
|
||||
mockRateLimit: (endpoint: string) => {
|
||||
let requestCount = 0;
|
||||
const limit = 5;
|
||||
|
||||
return http.all(endpoint, () => {
|
||||
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 });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test data builders for integration tests
|
||||
*/
|
||||
export const testDataBuilders = {
|
||||
/**
|
||||
* Build a workflow for testing
|
||||
*/
|
||||
workflow: (overrides?: any) => ({
|
||||
name: 'Integration Test Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
name: 'Start',
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {},
|
||||
settings: {},
|
||||
active: false,
|
||||
...overrides
|
||||
}),
|
||||
|
||||
/**
|
||||
* Build an execution result
|
||||
*/
|
||||
execution: (workflowId: string, overrides?: any) => ({
|
||||
id: `exec_${Date.now()}`,
|
||||
workflowId,
|
||||
status: 'success',
|
||||
mode: 'manual',
|
||||
startedAt: new Date().toISOString(),
|
||||
stoppedAt: new Date().toISOString(),
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {}
|
||||
}
|
||||
},
|
||||
...overrides
|
||||
})
|
||||
};
|
||||
Reference in New Issue
Block a user