mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
feat: implement Phase 7 integration tests for execution management
Implement comprehensive integration tests for 4 execution management handlers: - handleTriggerWebhookWorkflow (20 tests): GET/POST/PUT/DELETE methods, headers, error handling - handleGetExecution (16 tests): 4 retrieval modes (preview/summary/filtered/full), filtering, legacy compatibility - handleListExecutions (13 tests): status filtering, pagination with cursor, data inclusion - handleDeleteExecution (5 tests): successful deletion with verification, error handling All 54 tests passing against real n8n instance. Coverage: - All HTTP methods (GET, POST, PUT, DELETE) - All execution retrieval modes with filtering options - Pagination with cursor handling - Execution creation and cleanup verification - Comprehensive error handling scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
263
tests/integration/n8n-api/executions/list-executions.test.ts
Normal file
263
tests/integration/n8n-api/executions/list-executions.test.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Integration Tests: handleListExecutions
|
||||
*
|
||||
* Tests execution listing against a real n8n instance.
|
||||
* Covers filtering, pagination, and various list parameters.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { createMcpContext } from '../utils/mcp-context';
|
||||
import { InstanceContext } from '../../../../src/types/instance-context';
|
||||
import { handleListExecutions } from '../../../../src/mcp/handlers-n8n-manager';
|
||||
|
||||
describe('Integration: handleListExecutions', () => {
|
||||
let mcpContext: InstanceContext;
|
||||
|
||||
beforeEach(() => {
|
||||
mcpContext = createMcpContext();
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// No Filters
|
||||
// ======================================================================
|
||||
|
||||
describe('No Filters', () => {
|
||||
it('should list all executions without filters', async () => {
|
||||
const response = await handleListExecutions({}, mcpContext);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.data).toBeDefined();
|
||||
|
||||
const data = response.data as any;
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
expect(data).toHaveProperty('returned');
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// Filter by Status
|
||||
// ======================================================================
|
||||
|
||||
describe('Filter by Status', () => {
|
||||
it('should filter executions by success status', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ status: 'success' },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
// All returned executions should have success status
|
||||
if (data.executions.length > 0) {
|
||||
data.executions.forEach((exec: any) => {
|
||||
expect(exec.status).toBe('success');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should filter executions by error status', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ status: 'error' },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
// All returned executions should have error status
|
||||
if (data.executions.length > 0) {
|
||||
data.executions.forEach((exec: any) => {
|
||||
expect(exec.status).toBe('error');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should filter executions by waiting status', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ status: 'waiting' },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// Pagination
|
||||
// ======================================================================
|
||||
|
||||
describe('Pagination', () => {
|
||||
it('should return first page with limit', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 10 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
expect(data.executions.length).toBeLessThanOrEqual(10);
|
||||
});
|
||||
|
||||
it('should handle pagination with cursor', async () => {
|
||||
// Get first page
|
||||
const firstPage = await handleListExecutions(
|
||||
{ limit: 5 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(firstPage.success).toBe(true);
|
||||
const firstData = firstPage.data as any;
|
||||
|
||||
// If there's a next cursor, get second page
|
||||
if (firstData.nextCursor) {
|
||||
const secondPage = await handleListExecutions(
|
||||
{ limit: 5, cursor: firstData.nextCursor },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(secondPage.success).toBe(true);
|
||||
const secondData = secondPage.data as any;
|
||||
|
||||
// Second page should have different executions
|
||||
const firstIds = new Set(firstData.executions.map((e: any) => e.id));
|
||||
const secondIds = secondData.executions.map((e: any) => e.id);
|
||||
|
||||
secondIds.forEach((id: string) => {
|
||||
expect(firstIds.has(id)).toBe(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should respect limit=1', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 1 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(data.executions.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('should respect limit=50', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 50 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(data.executions.length).toBeLessThanOrEqual(50);
|
||||
});
|
||||
|
||||
it('should respect limit=100 (max)', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 100 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(data.executions.length).toBeLessThanOrEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// Include Execution Data
|
||||
// ======================================================================
|
||||
|
||||
describe('Include Execution Data', () => {
|
||||
it('should exclude execution data by default', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 5 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
// By default, should not include full execution data
|
||||
});
|
||||
|
||||
it('should include execution data when requested', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 5, includeData: true },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// Empty Results
|
||||
// ======================================================================
|
||||
|
||||
describe('Empty Results', () => {
|
||||
it('should return empty array when no executions match filters', async () => {
|
||||
// Use a very restrictive workflowId that likely doesn't exist
|
||||
const response = await handleListExecutions(
|
||||
{ workflowId: '99999999' },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
// May or may not be empty depending on actual data
|
||||
});
|
||||
});
|
||||
|
||||
// ======================================================================
|
||||
// Response Format Verification
|
||||
// ======================================================================
|
||||
|
||||
describe('Response Format', () => {
|
||||
it('should return complete list response structure', async () => {
|
||||
const response = await handleListExecutions(
|
||||
{ limit: 10 },
|
||||
mcpContext
|
||||
);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
const data = response.data as any;
|
||||
|
||||
// Verify required fields
|
||||
expect(data).toHaveProperty('executions');
|
||||
expect(Array.isArray(data.executions)).toBe(true);
|
||||
expect(data).toHaveProperty('returned');
|
||||
expect(data).toHaveProperty('hasMore');
|
||||
|
||||
// Verify pagination fields when present
|
||||
if (data.nextCursor) {
|
||||
expect(typeof data.nextCursor).toBe('string');
|
||||
}
|
||||
|
||||
// Verify execution structure if any executions returned
|
||||
if (data.executions.length > 0) {
|
||||
const execution = data.executions[0];
|
||||
expect(execution).toHaveProperty('id');
|
||||
|
||||
if (execution.status) {
|
||||
expect(['success', 'error', 'running', 'waiting']).toContain(execution.status);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user