feat: API 응답 검증 전역 설정 추가

This commit is contained in:
light
2026-01-31 10:05:32 +09:00
parent bd41ff1c12
commit a9438bf60c

180
_global-api-config.json Normal file
View File

@@ -0,0 +1,180 @@
{
"$schema": "E2E Global API Monitoring Configuration",
"version": "1.0.0",
"description": "모든 E2E 테스트 시나리오에 적용되는 API 요청/응답 검증 설정",
"lastUpdated": "2026-01-31",
"apiMonitoring": {
"enabled": true,
"captureAllRequests": true,
"baseUrl": "https://dev.codebridge-x.com",
"apiPathPrefix": "/api/",
"captureConfig": {
"includePatterns": [
"/api/v1/**",
"/api/**"
],
"excludePatterns": [
"/_next/**",
"/static/**",
"*.js",
"*.css",
"*.png",
"*.jpg",
"*.svg",
"*.woff",
"*.woff2"
],
"captureRequestBody": true,
"captureResponseBody": true,
"maxBodySize": 10240
}
},
"validation": {
"defaultRules": {
"statusCodes": {
"success": [200, 201, 204],
"clientError": [400, 401, 403, 404],
"serverError": [500, 502, 503]
},
"responseTime": {
"warning": 2000,
"error": 5000
},
"contentType": {
"json": "application/json",
"pdf": "application/pdf",
"excel": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
},
"methodRules": {
"GET": {
"expectedStatus": [200],
"maxResponseTime": 3000
},
"POST": {
"expectedStatus": [200, 201],
"maxResponseTime": 5000,
"requireResponseBody": true
},
"PUT": {
"expectedStatus": [200],
"maxResponseTime": 5000
},
"DELETE": {
"expectedStatus": [200, 204],
"maxResponseTime": 3000
}
}
},
"scripts": {
"setupApiMonitoring": "(function() { window.__API_LOGS__ = []; window.__API_ERRORS__ = []; const originalFetch = window.fetch; window.fetch = async function(...args) { const startTime = Date.now(); const url = typeof args[0] === 'string' ? args[0] : args[0].url; const method = args[1]?.method || 'GET'; try { const response = await originalFetch.apply(this, args); const endTime = Date.now(); const logEntry = { url, method, status: response.status, duration: endTime - startTime, timestamp: new Date().toISOString(), ok: response.ok }; window.__API_LOGS__.push(logEntry); if (!response.ok) { window.__API_ERRORS__.push(logEntry); } return response; } catch (error) { const errorEntry = { url, method, error: error.message, timestamp: new Date().toISOString() }; window.__API_ERRORS__.push(errorEntry); throw error; } }; return 'API monitoring initialized'; })()",
"getApiLogs": "(function() { return JSON.stringify(window.__API_LOGS__ || []); })()",
"getApiErrors": "(function() { return JSON.stringify(window.__API_ERRORS__ || []); })()",
"clearApiLogs": "(function() { window.__API_LOGS__ = []; window.__API_ERRORS__ = []; return 'API logs cleared'; })()",
"getApiSummary": "(function() { const logs = window.__API_LOGS__ || []; const errors = window.__API_ERRORS__ || []; const summary = { total: logs.length, success: logs.filter(l => l.ok).length, failed: errors.length, avgResponseTime: logs.length > 0 ? Math.round(logs.reduce((sum, l) => sum + (l.duration || 0), 0) / logs.length) : 0, slowRequests: logs.filter(l => l.duration > 2000).length, byMethod: {}, byStatus: {} }; logs.forEach(l => { summary.byMethod[l.method] = (summary.byMethod[l.method] || 0) + 1; summary.byStatus[l.status] = (summary.byStatus[l.status] || 0) + 1; }); return JSON.stringify(summary); })()",
"waitForApiCall": "(async function(urlPattern, timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { const logs = window.__API_LOGS__ || []; const found = logs.find(l => l.url.includes(urlPattern)); if (found) return JSON.stringify(found); await new Promise(r => setTimeout(r, 100)); } return JSON.stringify({ error: 'timeout', pattern: urlPattern }); })",
"validateApiCall": "(function(urlPattern, expectedStatus) { const logs = window.__API_LOGS__ || []; const matching = logs.filter(l => l.url.includes(urlPattern)); if (matching.length === 0) return JSON.stringify({ valid: false, error: 'API call not found', pattern: urlPattern }); const latest = matching[matching.length - 1]; const statusMatch = Array.isArray(expectedStatus) ? expectedStatus.includes(latest.status) : latest.status === expectedStatus; return JSON.stringify({ valid: statusMatch, call: latest, expected: expectedStatus }); })"
},
"commonApiEndpoints": {
"auth": {
"login": { "method": "POST", "path": "/api/v1/auth/login", "expectedStatus": 200 },
"logout": { "method": "POST", "path": "/api/v1/auth/logout", "expectedStatus": 200 },
"refresh": { "method": "POST", "path": "/api/v1/auth/refresh", "expectedStatus": 200 }
},
"clients": {
"list": { "method": "GET", "path": "/api/v1/clients", "expectedStatus": 200 },
"detail": { "method": "GET", "path": "/api/v1/clients/{id}", "expectedStatus": 200 },
"create": { "method": "POST", "path": "/api/v1/clients", "expectedStatus": [200, 201] },
"update": { "method": "PUT", "path": "/api/v1/clients/{id}", "expectedStatus": 200 },
"delete": { "method": "DELETE", "path": "/api/v1/clients/{id}", "expectedStatus": [200, 204] }
},
"approvals": {
"inbox": { "method": "GET", "path": "/api/v1/approvals/inbox", "expectedStatus": 200 },
"detail": { "method": "GET", "path": "/api/v1/approvals/{id}", "expectedStatus": 200 },
"approve": { "method": "POST", "path": "/api/v1/approvals/{id}/approve", "expectedStatus": 200 },
"reject": { "method": "POST", "path": "/api/v1/approvals/{id}/reject", "expectedStatus": 200 }
}
},
"reportTemplate": {
"apiSection": {
"title": "API 호출 검증 결과",
"fields": [
"총 API 호출 수",
"성공 호출",
"실패 호출",
"평균 응답 시간",
"느린 요청 (>2초)"
]
},
"errorSection": {
"title": "API 오류 상세",
"fields": [
"URL",
"Method",
"Status",
"응답 시간",
"오류 메시지"
]
}
},
"actionHooks": {
"beforeScenario": {
"description": "시나리오 시작 전 API 모니터링 초기화",
"actions": [
"setupApiMonitoring",
"clearApiLogs"
]
},
"afterStep": {
"description": "각 스텝 후 API 호출 확인",
"actions": [
"checkForApiErrors"
]
},
"afterScenario": {
"description": "시나리오 종료 후 API 요약 생성",
"actions": [
"getApiSummary",
"generateApiReport"
]
}
},
"errorHandling": {
"on500Error": {
"action": "captureAndReport",
"screenshot": true,
"logApiDetails": true,
"continueTest": false
},
"on401Error": {
"action": "reAuthenticate",
"maxRetries": 1,
"continueTest": true
},
"on404Error": {
"action": "logAndContinue",
"screenshot": true,
"continueTest": true
},
"onTimeout": {
"action": "retry",
"maxRetries": 2,
"continueTest": true
}
}
}