mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-21 09:53:08 +00:00
feat: implement integration testing foundation (Phase 1)
Complete implementation of Phase 1 foundation for n8n API integration tests. Establishes core utilities, fixtures, and infrastructure for testing all 17 n8n API handlers against real n8n instance. Changes: - Add integration test environment configuration to .env.example - Create comprehensive test utilities infrastructure: * credentials.ts: Environment-aware credential management (local .env vs CI secrets) * n8n-client.ts: Singleton API client wrapper with health checks * test-context.ts: Resource tracking and automatic cleanup * cleanup-helpers.ts: Multi-level cleanup strategies (orphaned, age-based, tag-based) * fixtures.ts: 6 pre-built workflow templates (webhook, HTTP, multi-node, error handling, AI, expressions) * factories.ts: Dynamic node/workflow builders with 15+ factory functions * webhook-workflows.ts: Webhook workflow configs and setup instructions - Add npm scripts: * test:integration:n8n: Run n8n API integration tests * test:cleanup:orphans: Clean up orphaned test resources - Create cleanup script for CI/manual use Documentation: - Add comprehensive integration testing plan (550 lines) - Add Phase 1 completion summary with lessons learned Key Features: - Automatic credential detection (CI vs local) - Multi-level cleanup (test, suite, CI, orphan) - 6 workflow fixtures covering common scenarios - 15+ factory functions for dynamic test data - Support for 4 HTTP methods (GET, POST, PUT, DELETE) via pre-activated webhook workflows - TypeScript-first with full type safety - Comprehensive error handling with helpful messages Total: ~1,520 lines of production-ready code + 650 lines of documentation Ready for Phase 2: Workflow creation tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
276
tests/integration/n8n-api/utils/cleanup-helpers.ts
Normal file
276
tests/integration/n8n-api/utils/cleanup-helpers.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Cleanup Helpers for Integration Tests
|
||||
*
|
||||
* Provides multi-level cleanup strategies for test resources:
|
||||
* - Orphaned workflows (from failed test runs)
|
||||
* - Old executions (older than 24 hours)
|
||||
* - Bulk cleanup by tag or name prefix
|
||||
*/
|
||||
|
||||
import { getTestN8nClient } from './n8n-client';
|
||||
import { getN8nCredentials } from './credentials';
|
||||
import { Logger } from '../../../../src/utils/logger';
|
||||
|
||||
const logger = new Logger({ prefix: '[Cleanup]' });
|
||||
|
||||
/**
|
||||
* Clean up orphaned test workflows
|
||||
*
|
||||
* Finds and deletes all workflows tagged with the test tag or
|
||||
* prefixed with the test name prefix. Run this periodically in CI
|
||||
* to clean up failed test runs.
|
||||
*
|
||||
* @returns Array of deleted workflow IDs
|
||||
*/
|
||||
export async function cleanupOrphanedWorkflows(): Promise<string[]> {
|
||||
const creds = getN8nCredentials();
|
||||
const client = getTestN8nClient();
|
||||
const deleted: string[] = [];
|
||||
|
||||
logger.info('Searching for orphaned test workflows...');
|
||||
|
||||
let allWorkflows: any[] = [];
|
||||
let cursor: string | undefined;
|
||||
let pageCount = 0;
|
||||
|
||||
// Fetch all workflows with pagination
|
||||
try {
|
||||
do {
|
||||
pageCount++;
|
||||
logger.debug(`Fetching workflows page ${pageCount}...`);
|
||||
|
||||
const response = await client.listWorkflows({
|
||||
cursor,
|
||||
limit: 100,
|
||||
excludePinnedData: true
|
||||
});
|
||||
|
||||
allWorkflows.push(...response.data);
|
||||
cursor = response.nextCursor || undefined;
|
||||
} while (cursor);
|
||||
|
||||
logger.info(`Found ${allWorkflows.length} total workflows across ${pageCount} page(s)`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch workflows:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Find test workflows
|
||||
const testWorkflows = allWorkflows.filter(w =>
|
||||
w.tags?.includes(creds.cleanup.tag) ||
|
||||
w.name?.startsWith(creds.cleanup.namePrefix)
|
||||
);
|
||||
|
||||
logger.info(`Found ${testWorkflows.length} orphaned test workflow(s)`);
|
||||
|
||||
if (testWorkflows.length === 0) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
// Delete them
|
||||
for (const workflow of testWorkflows) {
|
||||
try {
|
||||
await client.deleteWorkflow(workflow.id);
|
||||
deleted.push(workflow.id);
|
||||
logger.debug(`Deleted orphaned workflow: ${workflow.name} (${workflow.id})`);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete workflow ${workflow.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Successfully deleted ${deleted.length} orphaned workflow(s)`);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old executions
|
||||
*
|
||||
* Deletes executions older than the specified age.
|
||||
*
|
||||
* @param maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
||||
* @returns Array of deleted execution IDs
|
||||
*/
|
||||
export async function cleanupOldExecutions(
|
||||
maxAgeMs: number = 24 * 60 * 60 * 1000
|
||||
): Promise<string[]> {
|
||||
const client = getTestN8nClient();
|
||||
const deleted: string[] = [];
|
||||
|
||||
logger.info(`Searching for executions older than ${maxAgeMs}ms...`);
|
||||
|
||||
let allExecutions: any[] = [];
|
||||
let cursor: string | undefined;
|
||||
let pageCount = 0;
|
||||
|
||||
// Fetch all executions
|
||||
try {
|
||||
do {
|
||||
pageCount++;
|
||||
logger.debug(`Fetching executions page ${pageCount}...`);
|
||||
|
||||
const response = await client.listExecutions({
|
||||
cursor,
|
||||
limit: 100,
|
||||
includeData: false
|
||||
});
|
||||
|
||||
allExecutions.push(...response.data);
|
||||
cursor = response.nextCursor || undefined;
|
||||
} while (cursor);
|
||||
|
||||
logger.info(`Found ${allExecutions.length} total executions across ${pageCount} page(s)`);
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch executions:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const cutoffTime = Date.now() - maxAgeMs;
|
||||
const oldExecutions = allExecutions.filter(e => {
|
||||
const executionTime = new Date(e.startedAt).getTime();
|
||||
return executionTime < cutoffTime;
|
||||
});
|
||||
|
||||
logger.info(`Found ${oldExecutions.length} old execution(s)`);
|
||||
|
||||
if (oldExecutions.length === 0) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
for (const execution of oldExecutions) {
|
||||
try {
|
||||
await client.deleteExecution(execution.id);
|
||||
deleted.push(execution.id);
|
||||
logger.debug(`Deleted old execution: ${execution.id}`);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete execution ${execution.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Successfully deleted ${deleted.length} old execution(s)`);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all test resources
|
||||
*
|
||||
* Combines cleanupOrphanedWorkflows and cleanupOldExecutions.
|
||||
* Use this as a comprehensive cleanup in CI.
|
||||
*
|
||||
* @returns Object with counts of deleted resources
|
||||
*/
|
||||
export async function cleanupAllTestResources(): Promise<{
|
||||
workflows: number;
|
||||
executions: number;
|
||||
}> {
|
||||
logger.info('Starting comprehensive test resource cleanup...');
|
||||
|
||||
const [workflowIds, executionIds] = await Promise.all([
|
||||
cleanupOrphanedWorkflows(),
|
||||
cleanupOldExecutions()
|
||||
]);
|
||||
|
||||
logger.info(
|
||||
`Cleanup complete: ${workflowIds.length} workflows, ${executionIds.length} executions`
|
||||
);
|
||||
|
||||
return {
|
||||
workflows: workflowIds.length,
|
||||
executions: executionIds.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workflows by tag
|
||||
*
|
||||
* Deletes all workflows with the specified tag.
|
||||
*
|
||||
* @param tag - Tag to match
|
||||
* @returns Array of deleted workflow IDs
|
||||
*/
|
||||
export async function cleanupWorkflowsByTag(tag: string): Promise<string[]> {
|
||||
const client = getTestN8nClient();
|
||||
const deleted: string[] = [];
|
||||
|
||||
logger.info(`Searching for workflows with tag: ${tag}`);
|
||||
|
||||
try {
|
||||
const response = await client.listWorkflows({
|
||||
tags: tag ? [tag] : undefined,
|
||||
limit: 100,
|
||||
excludePinnedData: true
|
||||
});
|
||||
|
||||
const workflows = response.data;
|
||||
logger.info(`Found ${workflows.length} workflow(s) with tag: ${tag}`);
|
||||
|
||||
for (const workflow of workflows) {
|
||||
if (!workflow.id) continue;
|
||||
|
||||
try {
|
||||
await client.deleteWorkflow(workflow.id);
|
||||
deleted.push(workflow.id);
|
||||
logger.debug(`Deleted workflow: ${workflow.name} (${workflow.id})`);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete workflow ${workflow.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Successfully deleted ${deleted.length} workflow(s)`);
|
||||
return deleted;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to cleanup workflows by tag: ${tag}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete executions for a specific workflow
|
||||
*
|
||||
* @param workflowId - Workflow ID
|
||||
* @returns Array of deleted execution IDs
|
||||
*/
|
||||
export async function cleanupExecutionsByWorkflow(
|
||||
workflowId: string
|
||||
): Promise<string[]> {
|
||||
const client = getTestN8nClient();
|
||||
const deleted: string[] = [];
|
||||
|
||||
logger.info(`Searching for executions of workflow: ${workflowId}`);
|
||||
|
||||
let cursor: string | undefined;
|
||||
let totalCount = 0;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response = await client.listExecutions({
|
||||
workflowId,
|
||||
cursor,
|
||||
limit: 100,
|
||||
includeData: false
|
||||
});
|
||||
|
||||
const executions = response.data;
|
||||
totalCount += executions.length;
|
||||
|
||||
for (const execution of executions) {
|
||||
try {
|
||||
await client.deleteExecution(execution.id);
|
||||
deleted.push(execution.id);
|
||||
logger.debug(`Deleted execution: ${execution.id}`);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete execution ${execution.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
cursor = response.nextCursor || undefined;
|
||||
} while (cursor);
|
||||
|
||||
logger.info(
|
||||
`Successfully deleted ${deleted.length}/${totalCount} execution(s) for workflow ${workflowId}`
|
||||
);
|
||||
return deleted;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to cleanup executions for workflow: ${workflowId}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user