MCP HubMCP Hub
スキル一覧に戻る

n8n-integration-testing-patterns

proffesor-for-testing
更新日 Yesterday
206 閲覧
99
21
99
GitHubで表示
その他n8nintegrationapiauthenticationoauthrate-limitingtesting

について

このスキルは、n8nワークフロー統合のテストパターンを提供し、API契約、認証、レート制限、エラー処理をカバーします。外部サービスに接続するn8nノードを開発または検証する際にご利用ください。統合が堅牢で実世界のシナリオを適切に処理することを保証するのに役立ちます。

クイックインストール

Claude Code

推奨
プラグインコマンド推奨
/plugin add https://github.com/proffesor-for-testing/agentic-qe
Git クローン代替
git clone https://github.com/proffesor-for-testing/agentic-qe.git ~/.claude/skills/n8n-integration-testing-patterns

このコマンドをClaude Codeにコピー&ペーストしてスキルをインストールします

ドキュメント

n8n Integration Testing Patterns

<default_to_action> When testing n8n integrations:

  1. VERIFY connectivity and authentication
  2. TEST all configured operations
  3. VALIDATE API response handling
  4. CHECK rate limit behavior
  5. CONFIRM error handling works

Quick Integration Checklist:

  • Credentials valid and not expired
  • API permissions sufficient for operations
  • Rate limits understood and respected
  • Error responses properly handled
  • Data formats match API expectations

Critical Success Factors:

  • Test in isolation before workflow integration
  • Verify OAuth token refresh works
  • Check API version compatibility
  • Monitor rate limit headers </default_to_action>

Quick Reference Card

Common n8n Integrations

CategoryServicesAuth Type
CommunicationSlack, Teams, DiscordOAuth2, Webhook
Data StorageGoogle Sheets, AirtableOAuth2, API Key
CRMSalesforce, HubSpotOAuth2
Dev ToolsGitHub, Jira, LinearOAuth2, API Key
MarketingMailchimp, SendGridAPI Key

Authentication Types

TypeSetupRefresh
OAuth2User authorization flowAutomatic token refresh
API KeyManual key entryManual rotation
Basic AuthUsername/passwordNo refresh needed
Header AuthCustom headerManual rotation

Connectivity Testing

// Test integration connectivity
async function testIntegrationConnectivity(nodeName: string): Promise<ConnectivityResult> {
  const node = await getNodeConfig(nodeName);

  // Check credential exists
  if (!node.credentials) {
    return { connected: false, error: 'No credentials configured' };
  }

  // Test based on integration type
  switch (getIntegrationType(node.type)) {
    case 'slack':
      return await testSlackConnectivity(node.credentials);
    case 'google-sheets':
      return await testGoogleSheetsConnectivity(node.credentials);
    case 'jira':
      return await testJiraConnectivity(node.credentials);
    case 'github':
      return await testGitHubConnectivity(node.credentials);
    default:
      return await testGenericAPIConnectivity(node);
  }
}

// Slack connectivity test
async function testSlackConnectivity(credentials: any): Promise<ConnectivityResult> {
  try {
    const response = await fetch('https://slack.com/api/auth.test', {
      headers: { 'Authorization': `Bearer ${credentials.accessToken}` }
    });
    const data = await response.json();

    return {
      connected: data.ok,
      workspace: data.team,
      user: data.user,
      scopes: data.response_metadata?.scopes || []
    };
  } catch (error) {
    return { connected: false, error: error.message };
  }
}

// Google Sheets connectivity test
async function testGoogleSheetsConnectivity(credentials: any): Promise<ConnectivityResult> {
  try {
    const response = await fetch('https://www.googleapis.com/drive/v3/about?fields=user', {
      headers: { 'Authorization': `Bearer ${credentials.accessToken}` }
    });

    if (response.status === 401) {
      // Try refresh
      const refreshed = await refreshOAuthToken(credentials);
      if (refreshed) {
        return testGoogleSheetsConnectivity({ ...credentials, accessToken: refreshed });
      }
      return { connected: false, error: 'Token expired, refresh failed' };
    }

    const data = await response.json();
    return { connected: true, user: data.user };
  } catch (error) {
    return { connected: false, error: error.message };
  }
}

API Operation Testing

// Test integration operations
async function testIntegrationOperations(nodeName: string): Promise<OperationResult[]> {
  const node = await getNodeConfig(nodeName);
  const operations = getNodeOperations(node.type);
  const results: OperationResult[] = [];

  for (const operation of operations) {
    const testData = generateTestData(node.type, operation);

    try {
      const startTime = Date.now();
      const response = await executeOperation(node, operation, testData);

      results.push({
        operation,
        success: true,
        responseTime: Date.now() - startTime,
        responseStatus: response.status,
        dataValid: validateResponseData(response.data, operation)
      });
    } catch (error) {
      results.push({
        operation,
        success: false,
        error: error.message,
        errorType: classifyError(error)
      });
    }
  }

  return results;
}

// Generate test data for operations
function generateTestData(nodeType: string, operation: string): any {
  const testDataMap = {
    'slack': {
      'postMessage': {
        channel: 'C123456',
        text: 'Test message from n8n integration test'
      },
      'uploadFile': {
        channels: 'C123456',
        content: 'Test file content',
        filename: 'test.txt'
      }
    },
    'google-sheets': {
      'appendData': {
        spreadsheetId: 'test-spreadsheet-id',
        range: 'Sheet1!A:Z',
        values: [['Test', 'Data', new Date().toISOString()]]
      },
      'readRows': {
        spreadsheetId: 'test-spreadsheet-id',
        range: 'Sheet1!A1:Z10'
      }
    },
    'jira': {
      'createIssue': {
        project: 'TEST',
        issueType: 'Task',
        summary: 'Test issue from n8n',
        description: 'Created by integration test'
      },
      'updateIssue': {
        issueKey: 'TEST-1',
        fields: { summary: 'Updated by n8n test' }
      }
    }
  };

  return testDataMap[nodeType]?.[operation] || {};
}

Authentication Testing

OAuth2 Flow Testing

// Test OAuth2 authentication
async function testOAuth2Authentication(credentials: any): Promise<OAuth2Result> {
  const result: OAuth2Result = {
    tokenValid: false,
    refreshWorking: false,
    scopes: [],
    expiresIn: 0
  };

  // Test current token
  const tokenTest = await testAccessToken(credentials.accessToken);
  result.tokenValid = tokenTest.valid;
  result.scopes = tokenTest.scopes;

  // Check expiration
  if (credentials.expiresAt) {
    result.expiresIn = new Date(credentials.expiresAt).getTime() - Date.now();
    result.expiresSoon = result.expiresIn < 3600000; // Less than 1 hour
  }

  // Test refresh token
  if (credentials.refreshToken) {
    try {
      const newToken = await refreshOAuthToken(credentials);
      result.refreshWorking = !!newToken;
    } catch (error) {
      result.refreshError = error.message;
    }
  }

  return result;
}

// Test required scopes
async function testRequiredScopes(credentials: any, requiredScopes: string[]): Promise<ScopeResult> {
  const currentScopes = await getTokenScopes(credentials.accessToken);
  const missingScopes = requiredScopes.filter(s => !currentScopes.includes(s));

  return {
    hasAllScopes: missingScopes.length === 0,
    currentScopes,
    missingScopes,
    recommendation: missingScopes.length > 0
      ? `Re-authorize with scopes: ${missingScopes.join(', ')}`
      : null
  };
}

API Key Testing

// Test API key validity
async function testAPIKey(integration: string, apiKey: string): Promise<APIKeyResult> {
  const endpoints = {
    'sendgrid': 'https://api.sendgrid.com/v3/user/profile',
    'mailchimp': 'https://us1.api.mailchimp.com/3.0/ping',
    'airtable': 'https://api.airtable.com/v0/meta/whoami'
  };

  const endpoint = endpoints[integration];
  if (!endpoint) {
    return { valid: false, error: 'Unknown integration' };
  }

  try {
    const response = await fetch(endpoint, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    });

    return {
      valid: response.status === 200,
      status: response.status,
      rateLimit: extractRateLimitInfo(response.headers)
    };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Rate Limit Testing

// Test rate limit handling
async function testRateLimits(nodeName: string, requestCount: number): Promise<RateLimitResult> {
  const results: RequestResult[] = [];
  let rateLimitHit = false;
  let retryAfter = 0;

  for (let i = 0; i < requestCount; i++) {
    const startTime = Date.now();
    const response = await makeRequest(nodeName);

    results.push({
      requestNumber: i + 1,
      status: response.status,
      responseTime: Date.now() - startTime,
      rateLimitRemaining: response.headers['x-ratelimit-remaining'],
      rateLimitLimit: response.headers['x-ratelimit-limit']
    });

    if (response.status === 429) {
      rateLimitHit = true;
      retryAfter = parseInt(response.headers['retry-after'] || '60');
      break;
    }

    // Small delay between requests
    await sleep(100);
  }

  return {
    requestsMade: results.length,
    rateLimitHit,
    retryAfter,
    results,
    recommendation: rateLimitHit
      ? `Implement exponential backoff, retry after ${retryAfter}s`
      : 'Rate limit not reached, consider increasing request count for thorough testing'
  };
}

// Extract rate limit info from headers
function extractRateLimitInfo(headers: Headers): RateLimitInfo {
  return {
    limit: headers.get('x-ratelimit-limit'),
    remaining: headers.get('x-ratelimit-remaining'),
    reset: headers.get('x-ratelimit-reset'),
    retryAfter: headers.get('retry-after')
  };
}

Error Handling Testing

// Test error scenarios
async function testErrorScenarios(nodeName: string): Promise<ErrorTestResult[]> {
  const scenarios = [
    { name: 'Invalid credentials', modify: { credentials: null } },
    { name: 'Invalid endpoint', modify: { url: 'https://invalid.example.com' } },
    { name: 'Timeout', modify: { timeout: 1 } },
    { name: 'Invalid data', modify: { data: { invalid: true } } },
    { name: 'Not found', modify: { resourceId: 'nonexistent-123' } },
    { name: 'Permission denied', modify: { scope: 'read-only' } }
  ];

  const results: ErrorTestResult[] = [];

  for (const scenario of scenarios) {
    try {
      const response = await executeWithModification(nodeName, scenario.modify);

      results.push({
        scenario: scenario.name,
        errorHandled: response.status >= 400,
        errorCode: response.status,
        errorMessage: response.data?.error?.message,
        retried: response.metadata?.retryCount > 0
      });
    } catch (error) {
      results.push({
        scenario: scenario.name,
        errorHandled: true,
        exceptionThrown: true,
        errorType: error.constructor.name,
        errorMessage: error.message
      });
    }
  }

  return results;
}

// Classify error types
function classifyError(error: any): string {
  if (error.status === 401 || error.status === 403) return 'authentication';
  if (error.status === 404) return 'not-found';
  if (error.status === 429) return 'rate-limit';
  if (error.status >= 500) return 'server-error';
  if (error.code === 'ETIMEDOUT') return 'timeout';
  if (error.code === 'ECONNREFUSED') return 'connection';
  return 'unknown';
}

Integration-Specific Patterns

Slack Integration

const slackTestPatterns = {
  // Test message posting
  testPostMessage: async (credentials) => {
    return await fetch('https://slack.com/api/chat.postMessage', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${credentials.accessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        channel: 'C123456',
        text: 'Integration test message'
      })
    });
  },

  // Test file upload
  testFileUpload: async (credentials) => {
    const formData = new FormData();
    formData.append('channels', 'C123456');
    formData.append('content', 'Test file content');
    formData.append('filename', 'test.txt');

    return await fetch('https://slack.com/api/files.upload', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${credentials.accessToken}` },
      body: formData
    });
  },

  // Validate required scopes
  requiredScopes: ['chat:write', 'files:write', 'channels:read']
};

Google Sheets Integration

const googleSheetsTestPatterns = {
  // Test read operation
  testReadRows: async (credentials, spreadsheetId) => {
    return await fetch(
      `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/Sheet1!A1:Z10`,
      { headers: { 'Authorization': `Bearer ${credentials.accessToken}` } }
    );
  },

  // Test append operation
  testAppendRow: async (credentials, spreadsheetId, values) => {
    return await fetch(
      `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/Sheet1!A:Z:append?valueInputOption=USER_ENTERED`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${credentials.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ values: [values] })
      }
    );
  },

  // Required scopes
  requiredScopes: ['https://www.googleapis.com/auth/spreadsheets']
};

Test Report Template

# Integration Test Report

## Summary
| Integration | Status | Auth | Operations | Errors |
|-------------|--------|------|------------|--------|
| Slack | PASS | OK | 4/4 | 0 |
| Google Sheets | WARN | Expiring | 3/3 | 0 |
| Jira | FAIL | OK | 2/4 | 2 |

## Authentication Status
- Slack: OAuth2 valid, expires in 28 days
- Google Sheets: OAuth2 expires in 2 hours - REFRESH RECOMMENDED
- Jira: API Key valid

## Rate Limit Status
| Integration | Limit | Used | Remaining |
|-------------|-------|------|-----------|
| Slack | 50/min | 12 | 38 |
| Google Sheets | 100/min | 45 | 55 |
| Jira | 100/min | 8 | 92 |

## Failed Operations
### Jira: Transition Issue
- Error: Invalid transition for current state
- Fix: Check workflow transitions in Jira

## Recommendations
1. Refresh Google Sheets OAuth token before expiration
2. Fix Jira workflow transition logic

Related Skills


Remember

n8n integrates with 400+ services, each with unique authentication, rate limits, and API quirks. Testing requires:

  • Connectivity verification
  • Authentication validation (OAuth refresh, API key expiry)
  • Operation testing with realistic data
  • Rate limit awareness
  • Error handling verification

With Agents: Use n8n-integration-test for comprehensive integration testing. Coordinate with n8n-workflow-executor to test integrations in context.

GitHub リポジトリ

proffesor-for-testing/agentic-qe
パス: .claude/skills/n8n-integration-testing-patterns
agenticqeagenticsfoundationagentsquality-engineering

関連スキル

content-collections

メタ

This skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.

スキルを見る

creating-opencode-plugins

メタ

This skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.

スキルを見る

evaluating-llms-harness

テスト

This Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.

スキルを見る

polymarket

メタ

This skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.

スキルを見る