refactor: add TypeScript interfaces for test response types

Replace 'as any' type assertions with proper TypeScript interfaces for improved type safety in Phase 8 integration tests.

Changes:
- Created response-types.ts with comprehensive interfaces for all response types
- Updated health-check.test.ts to use HealthCheckResponse interface
- Updated list-tools.test.ts to use ListToolsResponse interface
- Updated diagnostic.test.ts to use DiagnosticResponse interface
- Added null-safety checks for optional fields (data.debug)
- Used non-null assertions (!) for values verified with expect().toBeDefined()
- Removed unnecessary 'as any' casts throughout test files

Benefits:
- Better type safety and IDE autocomplete
- Catches potential type mismatches at compile time
- More maintainable and self-documenting code
- Consistent with code review recommendation

All 19 tests still passing with full type safety.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-10-05 10:45:30 +02:00
parent 69f3a31d41
commit c519cd5060
4 changed files with 241 additions and 35 deletions

View File

@@ -9,6 +9,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import { createMcpContext } from '../utils/mcp-context'; import { createMcpContext } from '../utils/mcp-context';
import { InstanceContext } from '../../../../src/types/instance-context'; import { InstanceContext } from '../../../../src/types/instance-context';
import { handleDiagnostic } from '../../../../src/mcp/handlers-n8n-manager'; import { handleDiagnostic } from '../../../../src/mcp/handlers-n8n-manager';
import { DiagnosticResponse } from '../utils/response-types';
describe('Integration: handleDiagnostic', () => { describe('Integration: handleDiagnostic', () => {
let mcpContext: InstanceContext; let mcpContext: InstanceContext;
@@ -31,7 +32,7 @@ describe('Integration: handleDiagnostic', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as DiagnosticResponse;
// Verify core diagnostic fields // Verify core diagnostic fields
expect(data).toHaveProperty('timestamp'); expect(data).toHaveProperty('timestamp');
@@ -52,7 +53,7 @@ describe('Integration: handleDiagnostic', () => {
mcpContext mcpContext
); );
const data = response.data as any; const data = response.data as DiagnosticResponse;
expect(data.environment).toBeDefined(); expect(data.environment).toBeDefined();
expect(data.environment).toHaveProperty('N8N_API_URL'); expect(data.environment).toHaveProperty('N8N_API_URL');
@@ -72,7 +73,7 @@ describe('Integration: handleDiagnostic', () => {
mcpContext mcpContext
); );
const data = response.data as any; const data = response.data as DiagnosticResponse;
expect(data.apiConfiguration).toBeDefined(); expect(data.apiConfiguration).toBeDefined();
expect(data.apiConfiguration).toHaveProperty('configured'); expect(data.apiConfiguration).toHaveProperty('configured');
@@ -109,7 +110,7 @@ describe('Integration: handleDiagnostic', () => {
mcpContext mcpContext
); );
const data = response.data as any; const data = response.data as DiagnosticResponse;
expect(data.toolsAvailability).toBeDefined(); expect(data.toolsAvailability).toBeDefined();
expect(data.toolsAvailability).toHaveProperty('documentationTools'); expect(data.toolsAvailability).toHaveProperty('documentationTools');
@@ -144,7 +145,7 @@ describe('Integration: handleDiagnostic', () => {
mcpContext mcpContext
); );
const data = response.data as any; const data = response.data as DiagnosticResponse;
expect(data.troubleshooting).toBeDefined(); expect(data.troubleshooting).toBeDefined();
expect(data.troubleshooting).toHaveProperty('steps'); expect(data.troubleshooting).toHaveProperty('steps');
@@ -172,32 +173,33 @@ describe('Integration: handleDiagnostic', () => {
); );
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as DiagnosticResponse;
// Verbose mode should add debug section // Verbose mode should add debug section
expect(data).toHaveProperty('debug'); expect(data).toHaveProperty('debug');
expect(data.debug).toBeDefined(); expect(data.debug).toBeDefined();
// Verify debug information // Verify debug information
expect(data.debug).toBeDefined();
expect(data.debug).toHaveProperty('processEnv'); expect(data.debug).toHaveProperty('processEnv');
expect(data.debug).toHaveProperty('nodeVersion'); expect(data.debug).toHaveProperty('nodeVersion');
expect(data.debug).toHaveProperty('platform'); expect(data.debug).toHaveProperty('platform');
expect(data.debug).toHaveProperty('workingDirectory'); expect(data.debug).toHaveProperty('workingDirectory');
// Process env should list relevant environment variables // Process env should list relevant environment variables
expect(Array.isArray(data.debug.processEnv)).toBe(true); expect(Array.isArray(data.debug?.processEnv)).toBe(true);
// Node version should be a string // Node version should be a string
expect(typeof data.debug.nodeVersion).toBe('string'); expect(typeof data.debug?.nodeVersion).toBe('string');
expect(data.debug.nodeVersion).toMatch(/^v\d+\.\d+\.\d+/); expect(data.debug?.nodeVersion).toMatch(/^v\d+\.\d+\.\d+/);
// Platform should be a string (linux, darwin, win32, etc.) // Platform should be a string (linux, darwin, win32, etc.)
expect(typeof data.debug.platform).toBe('string'); expect(typeof data.debug?.platform).toBe('string');
expect(data.debug.platform.length).toBeGreaterThan(0); expect(data.debug && data.debug.platform.length).toBeGreaterThan(0);
// Working directory should be a path // Working directory should be a path
expect(typeof data.debug.workingDirectory).toBe('string'); expect(typeof data.debug?.workingDirectory).toBe('string');
expect(data.debug.workingDirectory.length).toBeGreaterThan(0); expect(data.debug && data.debug.workingDirectory.length).toBeGreaterThan(0);
}); });
it('should not include debug info when verbose is false', async () => { it('should not include debug info when verbose is false', async () => {
@@ -207,7 +209,7 @@ describe('Integration: handleDiagnostic', () => {
); );
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as DiagnosticResponse;
// Debug section should not be present // Debug section should not be present
expect(data.debug).toBeUndefined(); expect(data.debug).toBeUndefined();
@@ -220,7 +222,7 @@ describe('Integration: handleDiagnostic', () => {
); );
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as DiagnosticResponse;
// Debug section should not be present when verbose not specified // Debug section should not be present when verbose not specified
expect(data.debug).toBeUndefined(); expect(data.debug).toBeUndefined();
@@ -241,7 +243,7 @@ describe('Integration: handleDiagnostic', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as DiagnosticResponse;
// Verify all required fields // Verify all required fields
const requiredFields = [ const requiredFields = [

View File

@@ -9,6 +9,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import { createMcpContext } from '../utils/mcp-context'; import { createMcpContext } from '../utils/mcp-context';
import { InstanceContext } from '../../../../src/types/instance-context'; import { InstanceContext } from '../../../../src/types/instance-context';
import { handleHealthCheck } from '../../../../src/mcp/handlers-n8n-manager'; import { handleHealthCheck } from '../../../../src/mcp/handlers-n8n-manager';
import { HealthCheckResponse } from '../utils/response-types';
describe('Integration: handleHealthCheck', () => { describe('Integration: handleHealthCheck', () => {
let mcpContext: InstanceContext; let mcpContext: InstanceContext;
@@ -28,7 +29,7 @@ describe('Integration: handleHealthCheck', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as HealthCheckResponse;
// Verify required fields // Verify required fields
expect(data).toHaveProperty('status'); expect(data).toHaveProperty('status');
@@ -53,7 +54,7 @@ describe('Integration: handleHealthCheck', () => {
const response = await handleHealthCheck(mcpContext); const response = await handleHealthCheck(mcpContext);
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as HealthCheckResponse;
// Check for feature information // Check for feature information
// Note: Features may vary by n8n instance configuration // Note: Features may vary by n8n instance configuration
@@ -89,7 +90,7 @@ describe('Integration: handleHealthCheck', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as HealthCheckResponse;
// Verify all expected fields are present // Verify all expected fields are present
const expectedFields = ['status', 'apiUrl', 'mcpVersion']; const expectedFields = ['status', 'apiUrl', 'mcpVersion'];

View File

@@ -9,6 +9,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import { createMcpContext } from '../utils/mcp-context'; import { createMcpContext } from '../utils/mcp-context';
import { InstanceContext } from '../../../../src/types/instance-context'; import { InstanceContext } from '../../../../src/types/instance-context';
import { handleListAvailableTools } from '../../../../src/mcp/handlers-n8n-manager'; import { handleListAvailableTools } from '../../../../src/mcp/handlers-n8n-manager';
import { ListToolsResponse } from '../utils/response-types';
describe('Integration: handleListAvailableTools', () => { describe('Integration: handleListAvailableTools', () => {
let mcpContext: InstanceContext; let mcpContext: InstanceContext;
@@ -28,7 +29,7 @@ describe('Integration: handleListAvailableTools', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as ListToolsResponse;
// Verify tools array exists // Verify tools array exists
expect(data).toHaveProperty('tools'); expect(data).toHaveProperty('tools');
@@ -42,14 +43,14 @@ describe('Integration: handleListAvailableTools', () => {
expect(categories).toContain('System'); expect(categories).toContain('System');
// Verify each category has tools // Verify each category has tools
data.tools.forEach((category: any) => { data.tools.forEach(category => {
expect(category).toHaveProperty('category'); expect(category).toHaveProperty('category');
expect(category).toHaveProperty('tools'); expect(category).toHaveProperty('tools');
expect(Array.isArray(category.tools)).toBe(true); expect(Array.isArray(category.tools)).toBe(true);
expect(category.tools.length).toBeGreaterThan(0); expect(category.tools.length).toBeGreaterThan(0);
// Verify each tool has required fields // Verify each tool has required fields
category.tools.forEach((tool: any) => { category.tools.forEach(tool => {
expect(tool).toHaveProperty('name'); expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description'); expect(tool).toHaveProperty('description');
expect(typeof tool.name).toBe('string'); expect(typeof tool.name).toBe('string');
@@ -62,7 +63,7 @@ describe('Integration: handleListAvailableTools', () => {
const response = await handleListAvailableTools(mcpContext); const response = await handleListAvailableTools(mcpContext);
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as ListToolsResponse;
// Verify configuration status // Verify configuration status
expect(data).toHaveProperty('apiConfigured'); expect(data).toHaveProperty('apiConfigured');
@@ -85,7 +86,7 @@ describe('Integration: handleListAvailableTools', () => {
const response = await handleListAvailableTools(mcpContext); const response = await handleListAvailableTools(mcpContext);
expect(response.success).toBe(true); expect(response.success).toBe(true);
const data = response.data as any; const data = response.data as ListToolsResponse;
// Verify limitations are documented // Verify limitations are documented
expect(data).toHaveProperty('limitations'); expect(data).toHaveProperty('limitations');
@@ -93,7 +94,7 @@ describe('Integration: handleListAvailableTools', () => {
expect(data.limitations.length).toBeGreaterThan(0); expect(data.limitations.length).toBeGreaterThan(0);
// Verify limitations are informative strings // Verify limitations are informative strings
data.limitations.forEach((limitation: string) => { data.limitations.forEach(limitation => {
expect(typeof limitation).toBe('string'); expect(typeof limitation).toBe('string');
expect(limitation.length).toBeGreaterThan(0); expect(limitation.length).toBeGreaterThan(0);
}); });
@@ -112,12 +113,12 @@ describe('Integration: handleListAvailableTools', () => {
describe('Workflow Management Tools', () => { describe('Workflow Management Tools', () => {
it('should include all workflow management tools', async () => { it('should include all workflow management tools', async () => {
const response = await handleListAvailableTools(mcpContext); const response = await handleListAvailableTools(mcpContext);
const data = response.data as any; const data = response.data as ListToolsResponse;
const workflowCategory = data.tools.find((cat: any) => cat.category === 'Workflow Management'); const workflowCategory = data.tools.find(cat => cat.category === 'Workflow Management');
expect(workflowCategory).toBeDefined(); expect(workflowCategory).toBeDefined();
const toolNames = workflowCategory.tools.map((t: any) => t.name); const toolNames = workflowCategory!.tools.map(t => t.name);
// Core workflow tools // Core workflow tools
expect(toolNames).toContain('n8n_create_workflow'); expect(toolNames).toContain('n8n_create_workflow');
@@ -142,12 +143,12 @@ describe('Integration: handleListAvailableTools', () => {
describe('Execution Management Tools', () => { describe('Execution Management Tools', () => {
it('should include all execution management tools', async () => { it('should include all execution management tools', async () => {
const response = await handleListAvailableTools(mcpContext); const response = await handleListAvailableTools(mcpContext);
const data = response.data as any; const data = response.data as ListToolsResponse;
const executionCategory = data.tools.find((cat: any) => cat.category === 'Execution Management'); const executionCategory = data.tools.find(cat => cat.category === 'Execution Management');
expect(executionCategory).toBeDefined(); expect(executionCategory).toBeDefined();
const toolNames = executionCategory.tools.map((t: any) => t.name); const toolNames = executionCategory!.tools.map(t => t.name);
expect(toolNames).toContain('n8n_trigger_webhook_workflow'); expect(toolNames).toContain('n8n_trigger_webhook_workflow');
expect(toolNames).toContain('n8n_get_execution'); expect(toolNames).toContain('n8n_get_execution');
@@ -163,12 +164,12 @@ describe('Integration: handleListAvailableTools', () => {
describe('System Tools', () => { describe('System Tools', () => {
it('should include system tools', async () => { it('should include system tools', async () => {
const response = await handleListAvailableTools(mcpContext); const response = await handleListAvailableTools(mcpContext);
const data = response.data as any; const data = response.data as ListToolsResponse;
const systemCategory = data.tools.find((cat: any) => cat.category === 'System'); const systemCategory = data.tools.find(cat => cat.category === 'System');
expect(systemCategory).toBeDefined(); expect(systemCategory).toBeDefined();
const toolNames = systemCategory.tools.map((t: any) => t.name); const toolNames = systemCategory!.tools.map(t => t.name);
expect(toolNames).toContain('n8n_health_check'); expect(toolNames).toContain('n8n_health_check');
expect(toolNames).toContain('n8n_list_available_tools'); expect(toolNames).toContain('n8n_list_available_tools');
@@ -186,7 +187,7 @@ describe('Integration: handleListAvailableTools', () => {
expect(response.success).toBe(true); expect(response.success).toBe(true);
expect(response.data).toBeDefined(); expect(response.data).toBeDefined();
const data = response.data as any; const data = response.data as ListToolsResponse;
// Verify all required fields // Verify all required fields
expect(data).toHaveProperty('tools'); expect(data).toHaveProperty('tools');

View File

@@ -0,0 +1,202 @@
/**
* TypeScript interfaces for n8n API and MCP handler responses
* Used in integration tests to provide type safety
*/
// ======================================================================
// System Tool Response Types
// ======================================================================
export interface HealthCheckResponse {
status: string;
instanceId?: string;
n8nVersion?: string;
features?: Record<string, any>;
apiUrl: string;
mcpVersion: string;
supportedN8nVersion?: string;
versionNote?: string;
[key: string]: any; // Allow dynamic property access for optional field checks
}
export interface ToolDefinition {
name: string;
description: string;
}
export interface ToolCategory {
category: string;
tools: ToolDefinition[];
}
export interface ApiConfiguration {
apiUrl: string;
timeout: number;
maxRetries: number;
}
export interface ListToolsResponse {
tools: ToolCategory[];
apiConfigured: boolean;
configuration?: ApiConfiguration | null;
limitations: string[];
}
export interface ApiStatus {
configured: boolean;
connected: boolean;
error?: string | null;
version?: string | null;
}
export interface ToolsAvailability {
documentationTools: {
count: number;
enabled: boolean;
description: string;
};
managementTools: {
count: number;
enabled: boolean;
description: string;
};
totalAvailable: number;
}
export interface DebugInfo {
processEnv: string[];
nodeVersion: string;
platform: string;
workingDirectory: string;
}
export interface DiagnosticResponse {
timestamp: string;
environment: {
N8N_API_URL: string | null;
N8N_API_KEY: string | null;
NODE_ENV: string;
MCP_MODE: string;
};
apiConfiguration: {
configured: boolean;
status: ApiStatus;
config?: {
baseUrl: string;
timeout: number;
maxRetries: number;
} | null;
};
toolsAvailability: ToolsAvailability;
troubleshooting: {
steps: string[];
documentation: string;
};
debug?: DebugInfo;
[key: string]: any; // Allow dynamic property access for optional field checks
}
// ======================================================================
// Execution Response Types
// ======================================================================
export interface ExecutionData {
id: string;
status?: 'success' | 'error' | 'running' | 'waiting';
mode?: string;
startedAt?: string;
stoppedAt?: string;
workflowId?: string;
data?: any;
}
export interface ListExecutionsResponse {
executions: ExecutionData[];
returned: number;
nextCursor?: string;
hasMore: boolean;
_note?: string;
}
// ======================================================================
// Workflow Response Types
// ======================================================================
export interface WorkflowNode {
id: string;
name: string;
type: string;
typeVersion: number;
position: [number, number];
parameters: Record<string, any>;
credentials?: Record<string, any>;
disabled?: boolean;
}
export interface WorkflowConnections {
[key: string]: any;
}
export interface WorkflowData {
id: string;
name: string;
active: boolean;
nodes: WorkflowNode[];
connections: WorkflowConnections;
settings?: Record<string, any>;
staticData?: Record<string, any>;
tags?: string[];
versionId?: string;
createdAt?: string;
updatedAt?: string;
}
export interface ValidationError {
nodeId?: string;
nodeName?: string;
field?: string;
message: string;
type?: string;
}
export interface ValidationWarning {
nodeId?: string;
nodeName?: string;
message: string;
type?: string;
}
export interface ValidateWorkflowResponse {
valid: boolean;
errors?: ValidationError[];
warnings?: ValidationWarning[];
errorCount?: number;
warningCount?: number;
summary?: string;
}
export interface AutofixChange {
nodeId: string;
nodeName: string;
field: string;
oldValue: any;
newValue: any;
reason: string;
}
export interface AutofixSuggestion {
fixType: string;
nodeId: string;
nodeName: string;
description: string;
confidence: 'high' | 'medium' | 'low';
changes: AutofixChange[];
}
export interface AutofixResponse {
appliedFixes?: number;
suggestions?: AutofixSuggestion[];
workflow?: WorkflowData;
summary?: string;
preview?: boolean;
}