agentic-qe 2.5.6 → 2.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/n8n/n8n-base-agent.md +376 -0
- package/.claude/agents/n8n/n8n-bdd-scenario-tester.md +613 -0
- package/.claude/agents/n8n/n8n-chaos-tester.md +654 -0
- package/.claude/agents/n8n/n8n-ci-orchestrator.md +850 -0
- package/.claude/agents/n8n/n8n-compliance-validator.md +685 -0
- package/.claude/agents/n8n/n8n-expression-validator.md +560 -0
- package/.claude/agents/n8n/n8n-integration-test.md +602 -0
- package/.claude/agents/n8n/n8n-monitoring-validator.md +589 -0
- package/.claude/agents/n8n/n8n-node-validator.md +455 -0
- package/.claude/agents/n8n/n8n-performance-tester.md +630 -0
- package/.claude/agents/n8n/n8n-security-auditor.md +786 -0
- package/.claude/agents/n8n/n8n-trigger-test.md +500 -0
- package/.claude/agents/n8n/n8n-unit-tester.md +633 -0
- package/.claude/agents/n8n/n8n-version-comparator.md +567 -0
- package/.claude/agents/n8n/n8n-workflow-executor.md +392 -0
- package/.claude/skills/n8n-expression-testing/SKILL.md +434 -0
- package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
- package/.claude/skills/n8n-security-testing/SKILL.md +599 -0
- package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
- package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
- package/CHANGELOG.md +41 -0
- package/README.md +7 -4
- package/dist/agents/n8n/N8nAPIClient.d.ts +121 -0
- package/dist/agents/n8n/N8nAPIClient.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAPIClient.js +367 -0
- package/dist/agents/n8n/N8nAPIClient.js.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts +120 -0
- package/dist/agents/n8n/N8nAuditPersistence.d.ts.map +1 -0
- package/dist/agents/n8n/N8nAuditPersistence.js +473 -0
- package/dist/agents/n8n/N8nAuditPersistence.js.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts +159 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js +697 -0
- package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts +126 -0
- package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nBaseAgent.js +446 -0
- package/dist/agents/n8n/N8nBaseAgent.js.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts +164 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js +610 -0
- package/dist/agents/n8n/N8nCIOrchestratorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts +205 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js +729 -0
- package/dist/agents/n8n/N8nChaosTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts +228 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js +986 -0
- package/dist/agents/n8n/N8nComplianceValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts +213 -0
- package/dist/agents/n8n/N8nContractTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js +989 -0
- package/dist/agents/n8n/N8nContractTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts +99 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js +632 -0
- package/dist/agents/n8n/N8nExpressionValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts +238 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js +956 -0
- package/dist/agents/n8n/N8nFailureModeTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts +242 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js +992 -0
- package/dist/agents/n8n/N8nIdempotencyTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts +104 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js +653 -0
- package/dist/agents/n8n/N8nIntegrationTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts +210 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js +669 -0
- package/dist/agents/n8n/N8nMonitoringValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts +142 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js +1090 -0
- package/dist/agents/n8n/N8nNodeValidatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts +198 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js +653 -0
- package/dist/agents/n8n/N8nPerformanceTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts +245 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js +952 -0
- package/dist/agents/n8n/N8nReplayabilityTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts +325 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js +1187 -0
- package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts +91 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js +825 -0
- package/dist/agents/n8n/N8nSecurityAuditorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts +131 -0
- package/dist/agents/n8n/N8nTestHarness.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTestHarness.js +456 -0
- package/dist/agents/n8n/N8nTestHarness.js.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts +119 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js +652 -0
- package/dist/agents/n8n/N8nTriggerTestAgent.js.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts +130 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js +522 -0
- package/dist/agents/n8n/N8nUnitTesterAgent.js.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts +201 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js +645 -0
- package/dist/agents/n8n/N8nVersionComparatorAgent.js.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts +120 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts.map +1 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js +347 -0
- package/dist/agents/n8n/N8nWorkflowExecutorAgent.js.map +1 -0
- package/dist/agents/n8n/index.d.ts +119 -0
- package/dist/agents/n8n/index.d.ts.map +1 -0
- package/dist/agents/n8n/index.js +298 -0
- package/dist/agents/n8n/index.js.map +1 -0
- package/dist/agents/n8n/types.d.ts +486 -0
- package/dist/agents/n8n/types.d.ts.map +1 -0
- package/dist/agents/n8n/types.js +8 -0
- package/dist/agents/n8n/types.js.map +1 -0
- package/dist/cli/init/agents.d.ts.map +1 -1
- package/dist/cli/init/agents.js +29 -0
- package/dist/cli/init/agents.js.map +1 -1
- package/dist/cli/init/skills.d.ts.map +1 -1
- package/dist/cli/init/skills.js +7 -1
- package/dist/cli/init/skills.js.map +1 -1
- package/dist/core/memory/HNSWVectorMemory.js +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/docs/reference/agents.md +91 -2
- package/docs/reference/skills.md +97 -2
- package/package.json +2 -2
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: n8n-integration-testing-patterns
|
|
3
|
+
description: "API contract testing, authentication flows, rate limit handling, and error scenario coverage for n8n integrations with external services. Use when testing n8n node integrations."
|
|
4
|
+
category: n8n-testing
|
|
5
|
+
priority: high
|
|
6
|
+
tokenEstimate: 1100
|
|
7
|
+
agents: [n8n-integration-test]
|
|
8
|
+
implementation_status: production
|
|
9
|
+
optimization_version: 1.0
|
|
10
|
+
last_optimized: 2025-12-15
|
|
11
|
+
dependencies: []
|
|
12
|
+
quick_reference_card: true
|
|
13
|
+
tags: [n8n, integration, api, authentication, oauth, rate-limiting, testing]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# n8n Integration Testing Patterns
|
|
17
|
+
|
|
18
|
+
<default_to_action>
|
|
19
|
+
When testing n8n integrations:
|
|
20
|
+
1. VERIFY connectivity and authentication
|
|
21
|
+
2. TEST all configured operations
|
|
22
|
+
3. VALIDATE API response handling
|
|
23
|
+
4. CHECK rate limit behavior
|
|
24
|
+
5. CONFIRM error handling works
|
|
25
|
+
|
|
26
|
+
**Quick Integration Checklist:**
|
|
27
|
+
- Credentials valid and not expired
|
|
28
|
+
- API permissions sufficient for operations
|
|
29
|
+
- Rate limits understood and respected
|
|
30
|
+
- Error responses properly handled
|
|
31
|
+
- Data formats match API expectations
|
|
32
|
+
|
|
33
|
+
**Critical Success Factors:**
|
|
34
|
+
- Test in isolation before workflow integration
|
|
35
|
+
- Verify OAuth token refresh works
|
|
36
|
+
- Check API version compatibility
|
|
37
|
+
- Monitor rate limit headers
|
|
38
|
+
</default_to_action>
|
|
39
|
+
|
|
40
|
+
## Quick Reference Card
|
|
41
|
+
|
|
42
|
+
### Common n8n Integrations
|
|
43
|
+
|
|
44
|
+
| Category | Services | Auth Type |
|
|
45
|
+
|----------|----------|-----------|
|
|
46
|
+
| **Communication** | Slack, Teams, Discord | OAuth2, Webhook |
|
|
47
|
+
| **Data Storage** | Google Sheets, Airtable | OAuth2, API Key |
|
|
48
|
+
| **CRM** | Salesforce, HubSpot | OAuth2 |
|
|
49
|
+
| **Dev Tools** | GitHub, Jira, Linear | OAuth2, API Key |
|
|
50
|
+
| **Marketing** | Mailchimp, SendGrid | API Key |
|
|
51
|
+
|
|
52
|
+
### Authentication Types
|
|
53
|
+
|
|
54
|
+
| Type | Setup | Refresh |
|
|
55
|
+
|------|-------|---------|
|
|
56
|
+
| **OAuth2** | User authorization flow | Automatic token refresh |
|
|
57
|
+
| **API Key** | Manual key entry | Manual rotation |
|
|
58
|
+
| **Basic Auth** | Username/password | No refresh needed |
|
|
59
|
+
| **Header Auth** | Custom header | Manual rotation |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Connectivity Testing
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Test integration connectivity
|
|
67
|
+
async function testIntegrationConnectivity(nodeName: string): Promise<ConnectivityResult> {
|
|
68
|
+
const node = await getNodeConfig(nodeName);
|
|
69
|
+
|
|
70
|
+
// Check credential exists
|
|
71
|
+
if (!node.credentials) {
|
|
72
|
+
return { connected: false, error: 'No credentials configured' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Test based on integration type
|
|
76
|
+
switch (getIntegrationType(node.type)) {
|
|
77
|
+
case 'slack':
|
|
78
|
+
return await testSlackConnectivity(node.credentials);
|
|
79
|
+
case 'google-sheets':
|
|
80
|
+
return await testGoogleSheetsConnectivity(node.credentials);
|
|
81
|
+
case 'jira':
|
|
82
|
+
return await testJiraConnectivity(node.credentials);
|
|
83
|
+
case 'github':
|
|
84
|
+
return await testGitHubConnectivity(node.credentials);
|
|
85
|
+
default:
|
|
86
|
+
return await testGenericAPIConnectivity(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Slack connectivity test
|
|
91
|
+
async function testSlackConnectivity(credentials: any): Promise<ConnectivityResult> {
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch('https://slack.com/api/auth.test', {
|
|
94
|
+
headers: { 'Authorization': `Bearer ${credentials.accessToken}` }
|
|
95
|
+
});
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
connected: data.ok,
|
|
100
|
+
workspace: data.team,
|
|
101
|
+
user: data.user,
|
|
102
|
+
scopes: data.response_metadata?.scopes || []
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return { connected: false, error: error.message };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Google Sheets connectivity test
|
|
110
|
+
async function testGoogleSheetsConnectivity(credentials: any): Promise<ConnectivityResult> {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch('https://www.googleapis.com/drive/v3/about?fields=user', {
|
|
113
|
+
headers: { 'Authorization': `Bearer ${credentials.accessToken}` }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (response.status === 401) {
|
|
117
|
+
// Try refresh
|
|
118
|
+
const refreshed = await refreshOAuthToken(credentials);
|
|
119
|
+
if (refreshed) {
|
|
120
|
+
return testGoogleSheetsConnectivity({ ...credentials, accessToken: refreshed });
|
|
121
|
+
}
|
|
122
|
+
return { connected: false, error: 'Token expired, refresh failed' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
return { connected: true, user: data.user };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return { connected: false, error: error.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## API Operation Testing
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Test integration operations
|
|
139
|
+
async function testIntegrationOperations(nodeName: string): Promise<OperationResult[]> {
|
|
140
|
+
const node = await getNodeConfig(nodeName);
|
|
141
|
+
const operations = getNodeOperations(node.type);
|
|
142
|
+
const results: OperationResult[] = [];
|
|
143
|
+
|
|
144
|
+
for (const operation of operations) {
|
|
145
|
+
const testData = generateTestData(node.type, operation);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
const response = await executeOperation(node, operation, testData);
|
|
150
|
+
|
|
151
|
+
results.push({
|
|
152
|
+
operation,
|
|
153
|
+
success: true,
|
|
154
|
+
responseTime: Date.now() - startTime,
|
|
155
|
+
responseStatus: response.status,
|
|
156
|
+
dataValid: validateResponseData(response.data, operation)
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
results.push({
|
|
160
|
+
operation,
|
|
161
|
+
success: false,
|
|
162
|
+
error: error.message,
|
|
163
|
+
errorType: classifyError(error)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return results;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Generate test data for operations
|
|
172
|
+
function generateTestData(nodeType: string, operation: string): any {
|
|
173
|
+
const testDataMap = {
|
|
174
|
+
'slack': {
|
|
175
|
+
'postMessage': {
|
|
176
|
+
channel: 'C123456',
|
|
177
|
+
text: 'Test message from n8n integration test'
|
|
178
|
+
},
|
|
179
|
+
'uploadFile': {
|
|
180
|
+
channels: 'C123456',
|
|
181
|
+
content: 'Test file content',
|
|
182
|
+
filename: 'test.txt'
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
'google-sheets': {
|
|
186
|
+
'appendData': {
|
|
187
|
+
spreadsheetId: 'test-spreadsheet-id',
|
|
188
|
+
range: 'Sheet1!A:Z',
|
|
189
|
+
values: [['Test', 'Data', new Date().toISOString()]]
|
|
190
|
+
},
|
|
191
|
+
'readRows': {
|
|
192
|
+
spreadsheetId: 'test-spreadsheet-id',
|
|
193
|
+
range: 'Sheet1!A1:Z10'
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
'jira': {
|
|
197
|
+
'createIssue': {
|
|
198
|
+
project: 'TEST',
|
|
199
|
+
issueType: 'Task',
|
|
200
|
+
summary: 'Test issue from n8n',
|
|
201
|
+
description: 'Created by integration test'
|
|
202
|
+
},
|
|
203
|
+
'updateIssue': {
|
|
204
|
+
issueKey: 'TEST-1',
|
|
205
|
+
fields: { summary: 'Updated by n8n test' }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return testDataMap[nodeType]?.[operation] || {};
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Authentication Testing
|
|
217
|
+
|
|
218
|
+
### OAuth2 Flow Testing
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// Test OAuth2 authentication
|
|
222
|
+
async function testOAuth2Authentication(credentials: any): Promise<OAuth2Result> {
|
|
223
|
+
const result: OAuth2Result = {
|
|
224
|
+
tokenValid: false,
|
|
225
|
+
refreshWorking: false,
|
|
226
|
+
scopes: [],
|
|
227
|
+
expiresIn: 0
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Test current token
|
|
231
|
+
const tokenTest = await testAccessToken(credentials.accessToken);
|
|
232
|
+
result.tokenValid = tokenTest.valid;
|
|
233
|
+
result.scopes = tokenTest.scopes;
|
|
234
|
+
|
|
235
|
+
// Check expiration
|
|
236
|
+
if (credentials.expiresAt) {
|
|
237
|
+
result.expiresIn = new Date(credentials.expiresAt).getTime() - Date.now();
|
|
238
|
+
result.expiresSoon = result.expiresIn < 3600000; // Less than 1 hour
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Test refresh token
|
|
242
|
+
if (credentials.refreshToken) {
|
|
243
|
+
try {
|
|
244
|
+
const newToken = await refreshOAuthToken(credentials);
|
|
245
|
+
result.refreshWorking = !!newToken;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
result.refreshError = error.message;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Test required scopes
|
|
255
|
+
async function testRequiredScopes(credentials: any, requiredScopes: string[]): Promise<ScopeResult> {
|
|
256
|
+
const currentScopes = await getTokenScopes(credentials.accessToken);
|
|
257
|
+
const missingScopes = requiredScopes.filter(s => !currentScopes.includes(s));
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
hasAllScopes: missingScopes.length === 0,
|
|
261
|
+
currentScopes,
|
|
262
|
+
missingScopes,
|
|
263
|
+
recommendation: missingScopes.length > 0
|
|
264
|
+
? `Re-authorize with scopes: ${missingScopes.join(', ')}`
|
|
265
|
+
: null
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### API Key Testing
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// Test API key validity
|
|
274
|
+
async function testAPIKey(integration: string, apiKey: string): Promise<APIKeyResult> {
|
|
275
|
+
const endpoints = {
|
|
276
|
+
'sendgrid': 'https://api.sendgrid.com/v3/user/profile',
|
|
277
|
+
'mailchimp': 'https://us1.api.mailchimp.com/3.0/ping',
|
|
278
|
+
'airtable': 'https://api.airtable.com/v0/meta/whoami'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const endpoint = endpoints[integration];
|
|
282
|
+
if (!endpoint) {
|
|
283
|
+
return { valid: false, error: 'Unknown integration' };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch(endpoint, {
|
|
288
|
+
headers: { 'Authorization': `Bearer ${apiKey}` }
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
valid: response.status === 200,
|
|
293
|
+
status: response.status,
|
|
294
|
+
rateLimit: extractRateLimitInfo(response.headers)
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return { valid: false, error: error.message };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Rate Limit Testing
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// Test rate limit handling
|
|
308
|
+
async function testRateLimits(nodeName: string, requestCount: number): Promise<RateLimitResult> {
|
|
309
|
+
const results: RequestResult[] = [];
|
|
310
|
+
let rateLimitHit = false;
|
|
311
|
+
let retryAfter = 0;
|
|
312
|
+
|
|
313
|
+
for (let i = 0; i < requestCount; i++) {
|
|
314
|
+
const startTime = Date.now();
|
|
315
|
+
const response = await makeRequest(nodeName);
|
|
316
|
+
|
|
317
|
+
results.push({
|
|
318
|
+
requestNumber: i + 1,
|
|
319
|
+
status: response.status,
|
|
320
|
+
responseTime: Date.now() - startTime,
|
|
321
|
+
rateLimitRemaining: response.headers['x-ratelimit-remaining'],
|
|
322
|
+
rateLimitLimit: response.headers['x-ratelimit-limit']
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (response.status === 429) {
|
|
326
|
+
rateLimitHit = true;
|
|
327
|
+
retryAfter = parseInt(response.headers['retry-after'] || '60');
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Small delay between requests
|
|
332
|
+
await sleep(100);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
requestsMade: results.length,
|
|
337
|
+
rateLimitHit,
|
|
338
|
+
retryAfter,
|
|
339
|
+
results,
|
|
340
|
+
recommendation: rateLimitHit
|
|
341
|
+
? `Implement exponential backoff, retry after ${retryAfter}s`
|
|
342
|
+
: 'Rate limit not reached, consider increasing request count for thorough testing'
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Extract rate limit info from headers
|
|
347
|
+
function extractRateLimitInfo(headers: Headers): RateLimitInfo {
|
|
348
|
+
return {
|
|
349
|
+
limit: headers.get('x-ratelimit-limit'),
|
|
350
|
+
remaining: headers.get('x-ratelimit-remaining'),
|
|
351
|
+
reset: headers.get('x-ratelimit-reset'),
|
|
352
|
+
retryAfter: headers.get('retry-after')
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Error Handling Testing
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Test error scenarios
|
|
363
|
+
async function testErrorScenarios(nodeName: string): Promise<ErrorTestResult[]> {
|
|
364
|
+
const scenarios = [
|
|
365
|
+
{ name: 'Invalid credentials', modify: { credentials: null } },
|
|
366
|
+
{ name: 'Invalid endpoint', modify: { url: 'https://invalid.example.com' } },
|
|
367
|
+
{ name: 'Timeout', modify: { timeout: 1 } },
|
|
368
|
+
{ name: 'Invalid data', modify: { data: { invalid: true } } },
|
|
369
|
+
{ name: 'Not found', modify: { resourceId: 'nonexistent-123' } },
|
|
370
|
+
{ name: 'Permission denied', modify: { scope: 'read-only' } }
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
const results: ErrorTestResult[] = [];
|
|
374
|
+
|
|
375
|
+
for (const scenario of scenarios) {
|
|
376
|
+
try {
|
|
377
|
+
const response = await executeWithModification(nodeName, scenario.modify);
|
|
378
|
+
|
|
379
|
+
results.push({
|
|
380
|
+
scenario: scenario.name,
|
|
381
|
+
errorHandled: response.status >= 400,
|
|
382
|
+
errorCode: response.status,
|
|
383
|
+
errorMessage: response.data?.error?.message,
|
|
384
|
+
retried: response.metadata?.retryCount > 0
|
|
385
|
+
});
|
|
386
|
+
} catch (error) {
|
|
387
|
+
results.push({
|
|
388
|
+
scenario: scenario.name,
|
|
389
|
+
errorHandled: true,
|
|
390
|
+
exceptionThrown: true,
|
|
391
|
+
errorType: error.constructor.name,
|
|
392
|
+
errorMessage: error.message
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return results;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Classify error types
|
|
401
|
+
function classifyError(error: any): string {
|
|
402
|
+
if (error.status === 401 || error.status === 403) return 'authentication';
|
|
403
|
+
if (error.status === 404) return 'not-found';
|
|
404
|
+
if (error.status === 429) return 'rate-limit';
|
|
405
|
+
if (error.status >= 500) return 'server-error';
|
|
406
|
+
if (error.code === 'ETIMEDOUT') return 'timeout';
|
|
407
|
+
if (error.code === 'ECONNREFUSED') return 'connection';
|
|
408
|
+
return 'unknown';
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Integration-Specific Patterns
|
|
415
|
+
|
|
416
|
+
### Slack Integration
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const slackTestPatterns = {
|
|
420
|
+
// Test message posting
|
|
421
|
+
testPostMessage: async (credentials) => {
|
|
422
|
+
return await fetch('https://slack.com/api/chat.postMessage', {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: {
|
|
425
|
+
'Authorization': `Bearer ${credentials.accessToken}`,
|
|
426
|
+
'Content-Type': 'application/json'
|
|
427
|
+
},
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
channel: 'C123456',
|
|
430
|
+
text: 'Integration test message'
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
// Test file upload
|
|
436
|
+
testFileUpload: async (credentials) => {
|
|
437
|
+
const formData = new FormData();
|
|
438
|
+
formData.append('channels', 'C123456');
|
|
439
|
+
formData.append('content', 'Test file content');
|
|
440
|
+
formData.append('filename', 'test.txt');
|
|
441
|
+
|
|
442
|
+
return await fetch('https://slack.com/api/files.upload', {
|
|
443
|
+
method: 'POST',
|
|
444
|
+
headers: { 'Authorization': `Bearer ${credentials.accessToken}` },
|
|
445
|
+
body: formData
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
// Validate required scopes
|
|
450
|
+
requiredScopes: ['chat:write', 'files:write', 'channels:read']
|
|
451
|
+
};
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Google Sheets Integration
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
const googleSheetsTestPatterns = {
|
|
458
|
+
// Test read operation
|
|
459
|
+
testReadRows: async (credentials, spreadsheetId) => {
|
|
460
|
+
return await fetch(
|
|
461
|
+
`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/Sheet1!A1:Z10`,
|
|
462
|
+
{ headers: { 'Authorization': `Bearer ${credentials.accessToken}` } }
|
|
463
|
+
);
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
// Test append operation
|
|
467
|
+
testAppendRow: async (credentials, spreadsheetId, values) => {
|
|
468
|
+
return await fetch(
|
|
469
|
+
`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/Sheet1!A:Z:append?valueInputOption=USER_ENTERED`,
|
|
470
|
+
{
|
|
471
|
+
method: 'POST',
|
|
472
|
+
headers: {
|
|
473
|
+
'Authorization': `Bearer ${credentials.accessToken}`,
|
|
474
|
+
'Content-Type': 'application/json'
|
|
475
|
+
},
|
|
476
|
+
body: JSON.stringify({ values: [values] })
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
// Required scopes
|
|
482
|
+
requiredScopes: ['https://www.googleapis.com/auth/spreadsheets']
|
|
483
|
+
};
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Test Report Template
|
|
489
|
+
|
|
490
|
+
```markdown
|
|
491
|
+
# Integration Test Report
|
|
492
|
+
|
|
493
|
+
## Summary
|
|
494
|
+
| Integration | Status | Auth | Operations | Errors |
|
|
495
|
+
|-------------|--------|------|------------|--------|
|
|
496
|
+
| Slack | PASS | OK | 4/4 | 0 |
|
|
497
|
+
| Google Sheets | WARN | Expiring | 3/3 | 0 |
|
|
498
|
+
| Jira | FAIL | OK | 2/4 | 2 |
|
|
499
|
+
|
|
500
|
+
## Authentication Status
|
|
501
|
+
- Slack: OAuth2 valid, expires in 28 days
|
|
502
|
+
- Google Sheets: OAuth2 expires in 2 hours - REFRESH RECOMMENDED
|
|
503
|
+
- Jira: API Key valid
|
|
504
|
+
|
|
505
|
+
## Rate Limit Status
|
|
506
|
+
| Integration | Limit | Used | Remaining |
|
|
507
|
+
|-------------|-------|------|-----------|
|
|
508
|
+
| Slack | 50/min | 12 | 38 |
|
|
509
|
+
| Google Sheets | 100/min | 45 | 55 |
|
|
510
|
+
| Jira | 100/min | 8 | 92 |
|
|
511
|
+
|
|
512
|
+
## Failed Operations
|
|
513
|
+
### Jira: Transition Issue
|
|
514
|
+
- Error: Invalid transition for current state
|
|
515
|
+
- Fix: Check workflow transitions in Jira
|
|
516
|
+
|
|
517
|
+
## Recommendations
|
|
518
|
+
1. Refresh Google Sheets OAuth token before expiration
|
|
519
|
+
2. Fix Jira workflow transition logic
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Related Skills
|
|
525
|
+
- [n8n-workflow-testing-fundamentals](../n8n-workflow-testing-fundamentals/)
|
|
526
|
+
- [n8n-security-testing](../n8n-security-testing/)
|
|
527
|
+
- [api-testing-patterns](../api-testing-patterns/)
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## Remember
|
|
532
|
+
|
|
533
|
+
**n8n integrates with 400+ services**, each with unique authentication, rate limits, and API quirks. Testing requires:
|
|
534
|
+
- Connectivity verification
|
|
535
|
+
- Authentication validation (OAuth refresh, API key expiry)
|
|
536
|
+
- Operation testing with realistic data
|
|
537
|
+
- Rate limit awareness
|
|
538
|
+
- Error handling verification
|
|
539
|
+
|
|
540
|
+
**With Agents:** Use n8n-integration-test for comprehensive integration testing. Coordinate with n8n-workflow-executor to test integrations in context.
|