@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,1084 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chaos Engineering Mode
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* COMPETITIVE MOAT FEATURE - Resilience Testing Through Controlled Failure
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* This engine injects controlled failures during Reality Mode to test how the
|
|
9
|
-
* application handles adverse conditions. No other tool does this at runtime.
|
|
10
|
-
*
|
|
11
|
-
* Chaos Types:
|
|
12
|
-
* - Network failures (timeout, disconnect, slow connection)
|
|
13
|
-
* - API errors (500s, 503s, rate limiting)
|
|
14
|
-
* - Latency injection (slow responses)
|
|
15
|
-
* - Payload corruption (malformed JSON, truncated responses)
|
|
16
|
-
* - Resource exhaustion (memory pressure simulation)
|
|
17
|
-
* - Third-party failures (CDN, auth provider, payment gateway)
|
|
18
|
-
* - Clock skew (time manipulation)
|
|
19
|
-
*
|
|
20
|
-
* This catches:
|
|
21
|
-
* - Missing error boundaries
|
|
22
|
-
* - Poor offline handling
|
|
23
|
-
* - Race conditions under load
|
|
24
|
-
* - Retry storm vulnerabilities
|
|
25
|
-
* - Timeout handling issues
|
|
26
|
-
* - Graceful degradation failures
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
"use strict";
|
|
30
|
-
|
|
31
|
-
const fs = require("fs");
|
|
32
|
-
const path = require("path");
|
|
33
|
-
|
|
34
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
-
// CHAOS SCENARIOS
|
|
36
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
-
|
|
38
|
-
const CHAOS_SCENARIOS = {
|
|
39
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
-
// NETWORK CHAOS
|
|
41
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
-
network: {
|
|
43
|
-
disconnect: {
|
|
44
|
-
name: "Network Disconnect",
|
|
45
|
-
description: "Simulate complete network failure",
|
|
46
|
-
severity: "high",
|
|
47
|
-
duration: 5000,
|
|
48
|
-
apply: async (context) => {
|
|
49
|
-
await context.setOffline(true);
|
|
50
|
-
return { type: "disconnect", started: Date.now() };
|
|
51
|
-
},
|
|
52
|
-
cleanup: async (context) => {
|
|
53
|
-
await context.setOffline(false);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
slowConnection: {
|
|
58
|
-
name: "Slow 3G Connection",
|
|
59
|
-
description: "Simulate slow mobile network",
|
|
60
|
-
severity: "medium",
|
|
61
|
-
config: {
|
|
62
|
-
downloadThroughput: 50 * 1024, // 50KB/s
|
|
63
|
-
uploadThroughput: 20 * 1024, // 20KB/s
|
|
64
|
-
latency: 2000 // 2s latency
|
|
65
|
-
},
|
|
66
|
-
apply: async (context, page) => {
|
|
67
|
-
const cdp = await page.context().newCDPSession(page);
|
|
68
|
-
await cdp.send("Network.emulateNetworkConditions", {
|
|
69
|
-
offline: false,
|
|
70
|
-
downloadThroughput: 50 * 1024,
|
|
71
|
-
uploadThroughput: 20 * 1024,
|
|
72
|
-
latency: 2000
|
|
73
|
-
});
|
|
74
|
-
return { type: "slowConnection", cdp };
|
|
75
|
-
},
|
|
76
|
-
cleanup: async (context, state) => {
|
|
77
|
-
if (state.cdp) {
|
|
78
|
-
await state.cdp.send("Network.emulateNetworkConditions", {
|
|
79
|
-
offline: false,
|
|
80
|
-
downloadThroughput: -1,
|
|
81
|
-
uploadThroughput: -1,
|
|
82
|
-
latency: 0
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
intermittent: {
|
|
89
|
-
name: "Intermittent Connectivity",
|
|
90
|
-
description: "Connection drops randomly",
|
|
91
|
-
severity: "high",
|
|
92
|
-
config: {
|
|
93
|
-
dropChance: 0.3, // 30% chance to drop
|
|
94
|
-
dropDuration: 2000 // 2s drops
|
|
95
|
-
},
|
|
96
|
-
apply: async (context, page, config) => {
|
|
97
|
-
const interval = setInterval(async () => {
|
|
98
|
-
if (Math.random() < config.dropChance) {
|
|
99
|
-
await context.setOffline(true);
|
|
100
|
-
setTimeout(() => context.setOffline(false), config.dropDuration);
|
|
101
|
-
}
|
|
102
|
-
}, 1000);
|
|
103
|
-
return { type: "intermittent", interval };
|
|
104
|
-
},
|
|
105
|
-
cleanup: async (context, state) => {
|
|
106
|
-
clearInterval(state.interval);
|
|
107
|
-
await context.setOffline(false);
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
dnsFailure: {
|
|
112
|
-
name: "DNS Resolution Failure",
|
|
113
|
-
description: "Simulate DNS lookup failures",
|
|
114
|
-
severity: "high",
|
|
115
|
-
apply: async (context, page) => {
|
|
116
|
-
await page.route("**/*", async (route) => {
|
|
117
|
-
const url = route.request().url();
|
|
118
|
-
// Fail external domains
|
|
119
|
-
if (!url.includes("localhost") && !url.includes("127.0.0.1")) {
|
|
120
|
-
await route.abort("namenotresolved");
|
|
121
|
-
} else {
|
|
122
|
-
await route.continue();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
return { type: "dnsFailure" };
|
|
126
|
-
},
|
|
127
|
-
cleanup: async (context, page) => {
|
|
128
|
-
await page.unroute("**/*");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
-
// API CHAOS
|
|
135
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
-
api: {
|
|
137
|
-
serverError: {
|
|
138
|
-
name: "API Server Errors",
|
|
139
|
-
description: "All API calls return 500",
|
|
140
|
-
severity: "high",
|
|
141
|
-
config: {
|
|
142
|
-
pattern: "**/api/**",
|
|
143
|
-
status: 500,
|
|
144
|
-
body: { error: "Internal Server Error", message: "Chaos injection" }
|
|
145
|
-
},
|
|
146
|
-
apply: async (context, page, config) => {
|
|
147
|
-
await page.route(config.pattern, async (route) => {
|
|
148
|
-
await route.fulfill({
|
|
149
|
-
status: config.status,
|
|
150
|
-
contentType: "application/json",
|
|
151
|
-
body: JSON.stringify(config.body)
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
return { type: "serverError", pattern: config.pattern };
|
|
155
|
-
},
|
|
156
|
-
cleanup: async (context, page, state) => {
|
|
157
|
-
await page.unroute(state.pattern);
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
serviceUnavailable: {
|
|
162
|
-
name: "Service Unavailable",
|
|
163
|
-
description: "API returns 503 with retry-after",
|
|
164
|
-
severity: "high",
|
|
165
|
-
config: {
|
|
166
|
-
pattern: "**/api/**",
|
|
167
|
-
retryAfter: 30
|
|
168
|
-
},
|
|
169
|
-
apply: async (context, page, config) => {
|
|
170
|
-
await page.route(config.pattern, async (route) => {
|
|
171
|
-
await route.fulfill({
|
|
172
|
-
status: 503,
|
|
173
|
-
headers: { "Retry-After": String(config.retryAfter) },
|
|
174
|
-
contentType: "application/json",
|
|
175
|
-
body: JSON.stringify({ error: "Service Unavailable" })
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
return { type: "serviceUnavailable", pattern: config.pattern };
|
|
179
|
-
},
|
|
180
|
-
cleanup: async (context, page, state) => {
|
|
181
|
-
await page.unroute(state.pattern);
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
rateLimited: {
|
|
186
|
-
name: "Rate Limited",
|
|
187
|
-
description: "API returns 429 Too Many Requests",
|
|
188
|
-
severity: "medium",
|
|
189
|
-
config: {
|
|
190
|
-
pattern: "**/api/**",
|
|
191
|
-
retryAfter: 60
|
|
192
|
-
},
|
|
193
|
-
apply: async (context, page, config) => {
|
|
194
|
-
await page.route(config.pattern, async (route) => {
|
|
195
|
-
await route.fulfill({
|
|
196
|
-
status: 429,
|
|
197
|
-
headers: {
|
|
198
|
-
"Retry-After": String(config.retryAfter),
|
|
199
|
-
"X-RateLimit-Remaining": "0",
|
|
200
|
-
"X-RateLimit-Reset": String(Date.now() + config.retryAfter * 1000)
|
|
201
|
-
},
|
|
202
|
-
contentType: "application/json",
|
|
203
|
-
body: JSON.stringify({ error: "Too Many Requests" })
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
return { type: "rateLimited", pattern: config.pattern };
|
|
207
|
-
},
|
|
208
|
-
cleanup: async (context, page, state) => {
|
|
209
|
-
await page.unroute(state.pattern);
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
randomErrors: {
|
|
214
|
-
name: "Random API Errors",
|
|
215
|
-
description: "30% of API calls fail randomly",
|
|
216
|
-
severity: "high",
|
|
217
|
-
config: {
|
|
218
|
-
pattern: "**/api/**",
|
|
219
|
-
failureRate: 0.3,
|
|
220
|
-
errorCodes: [500, 502, 503, 504]
|
|
221
|
-
},
|
|
222
|
-
apply: async (context, page, config) => {
|
|
223
|
-
await page.route(config.pattern, async (route) => {
|
|
224
|
-
if (Math.random() < config.failureRate) {
|
|
225
|
-
const status = config.errorCodes[
|
|
226
|
-
Math.floor(Math.random() * config.errorCodes.length)
|
|
227
|
-
];
|
|
228
|
-
await route.fulfill({
|
|
229
|
-
status,
|
|
230
|
-
contentType: "application/json",
|
|
231
|
-
body: JSON.stringify({ error: `Chaos error ${status}` })
|
|
232
|
-
});
|
|
233
|
-
} else {
|
|
234
|
-
await route.continue();
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
return { type: "randomErrors", pattern: config.pattern };
|
|
238
|
-
},
|
|
239
|
-
cleanup: async (context, page, state) => {
|
|
240
|
-
await page.unroute(state.pattern);
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
timeout: {
|
|
245
|
-
name: "API Timeout",
|
|
246
|
-
description: "API calls hang indefinitely",
|
|
247
|
-
severity: "high",
|
|
248
|
-
config: {
|
|
249
|
-
pattern: "**/api/**",
|
|
250
|
-
delay: 30000 // 30 seconds
|
|
251
|
-
},
|
|
252
|
-
apply: async (context, page, config) => {
|
|
253
|
-
await page.route(config.pattern, async (route) => {
|
|
254
|
-
await new Promise(resolve => setTimeout(resolve, config.delay));
|
|
255
|
-
await route.abort("timedout");
|
|
256
|
-
});
|
|
257
|
-
return { type: "timeout", pattern: config.pattern };
|
|
258
|
-
},
|
|
259
|
-
cleanup: async (context, page, state) => {
|
|
260
|
-
await page.unroute(state.pattern);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
266
|
-
// LATENCY CHAOS
|
|
267
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
268
|
-
latency: {
|
|
269
|
-
highLatency: {
|
|
270
|
-
name: "High Latency",
|
|
271
|
-
description: "Add 3-5 second delay to all requests",
|
|
272
|
-
severity: "medium",
|
|
273
|
-
config: {
|
|
274
|
-
pattern: "**/*",
|
|
275
|
-
minDelay: 3000,
|
|
276
|
-
maxDelay: 5000
|
|
277
|
-
},
|
|
278
|
-
apply: async (context, page, config) => {
|
|
279
|
-
await page.route(config.pattern, async (route) => {
|
|
280
|
-
const delay = config.minDelay +
|
|
281
|
-
Math.random() * (config.maxDelay - config.minDelay);
|
|
282
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
283
|
-
await route.continue();
|
|
284
|
-
});
|
|
285
|
-
return { type: "highLatency", pattern: config.pattern };
|
|
286
|
-
},
|
|
287
|
-
cleanup: async (context, page, state) => {
|
|
288
|
-
await page.unroute(state.pattern);
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
spikyLatency: {
|
|
293
|
-
name: "Spiky Latency",
|
|
294
|
-
description: "Occasional latency spikes",
|
|
295
|
-
severity: "medium",
|
|
296
|
-
config: {
|
|
297
|
-
pattern: "**/*",
|
|
298
|
-
spikeChance: 0.2,
|
|
299
|
-
normalDelay: 100,
|
|
300
|
-
spikeDelay: 5000
|
|
301
|
-
},
|
|
302
|
-
apply: async (context, page, config) => {
|
|
303
|
-
await page.route(config.pattern, async (route) => {
|
|
304
|
-
const delay = Math.random() < config.spikeChance
|
|
305
|
-
? config.spikeDelay
|
|
306
|
-
: config.normalDelay;
|
|
307
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
308
|
-
await route.continue();
|
|
309
|
-
});
|
|
310
|
-
return { type: "spikyLatency", pattern: config.pattern };
|
|
311
|
-
},
|
|
312
|
-
cleanup: async (context, page, state) => {
|
|
313
|
-
await page.unroute(state.pattern);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
319
|
-
// PAYLOAD CHAOS
|
|
320
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
321
|
-
payload: {
|
|
322
|
-
malformedJson: {
|
|
323
|
-
name: "Malformed JSON",
|
|
324
|
-
description: "Return invalid JSON responses",
|
|
325
|
-
severity: "high",
|
|
326
|
-
config: {
|
|
327
|
-
pattern: "**/api/**"
|
|
328
|
-
},
|
|
329
|
-
apply: async (context, page, config) => {
|
|
330
|
-
await page.route(config.pattern, async (route) => {
|
|
331
|
-
await route.fulfill({
|
|
332
|
-
status: 200,
|
|
333
|
-
contentType: "application/json",
|
|
334
|
-
body: '{"broken": true, "missing_quote: "value"}' // Invalid JSON
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
return { type: "malformedJson", pattern: config.pattern };
|
|
338
|
-
},
|
|
339
|
-
cleanup: async (context, page, state) => {
|
|
340
|
-
await page.unroute(state.pattern);
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
|
|
344
|
-
truncatedResponse: {
|
|
345
|
-
name: "Truncated Response",
|
|
346
|
-
description: "Cut responses in half",
|
|
347
|
-
severity: "high",
|
|
348
|
-
config: {
|
|
349
|
-
pattern: "**/api/**",
|
|
350
|
-
truncatePercent: 0.5
|
|
351
|
-
},
|
|
352
|
-
apply: async (context, page, config) => {
|
|
353
|
-
await page.route(config.pattern, async (route) => {
|
|
354
|
-
const response = await route.fetch();
|
|
355
|
-
const body = await response.text();
|
|
356
|
-
const truncated = body.substring(
|
|
357
|
-
0,
|
|
358
|
-
Math.floor(body.length * config.truncatePercent)
|
|
359
|
-
);
|
|
360
|
-
await route.fulfill({
|
|
361
|
-
status: response.status(),
|
|
362
|
-
headers: response.headers(),
|
|
363
|
-
body: truncated
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
return { type: "truncatedResponse", pattern: config.pattern };
|
|
367
|
-
},
|
|
368
|
-
cleanup: async (context, page, state) => {
|
|
369
|
-
await page.unroute(state.pattern);
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
|
|
373
|
-
emptyResponse: {
|
|
374
|
-
name: "Empty Response",
|
|
375
|
-
description: "Return empty bodies",
|
|
376
|
-
severity: "medium",
|
|
377
|
-
config: {
|
|
378
|
-
pattern: "**/api/**"
|
|
379
|
-
},
|
|
380
|
-
apply: async (context, page, config) => {
|
|
381
|
-
await page.route(config.pattern, async (route) => {
|
|
382
|
-
await route.fulfill({
|
|
383
|
-
status: 200,
|
|
384
|
-
contentType: "application/json",
|
|
385
|
-
body: ""
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
return { type: "emptyResponse", pattern: config.pattern };
|
|
389
|
-
},
|
|
390
|
-
cleanup: async (context, page, state) => {
|
|
391
|
-
await page.unroute(state.pattern);
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
|
|
395
|
-
wrongContentType: {
|
|
396
|
-
name: "Wrong Content-Type",
|
|
397
|
-
description: "Return HTML instead of JSON",
|
|
398
|
-
severity: "medium",
|
|
399
|
-
config: {
|
|
400
|
-
pattern: "**/api/**"
|
|
401
|
-
},
|
|
402
|
-
apply: async (context, page, config) => {
|
|
403
|
-
await page.route(config.pattern, async (route) => {
|
|
404
|
-
await route.fulfill({
|
|
405
|
-
status: 200,
|
|
406
|
-
contentType: "text/html",
|
|
407
|
-
body: "<html><body>Unexpected HTML</body></html>"
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
return { type: "wrongContentType", pattern: config.pattern };
|
|
411
|
-
},
|
|
412
|
-
cleanup: async (context, page, state) => {
|
|
413
|
-
await page.unroute(state.pattern);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
|
|
418
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
419
|
-
// THIRD-PARTY CHAOS
|
|
420
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
421
|
-
thirdParty: {
|
|
422
|
-
cdnFailure: {
|
|
423
|
-
name: "CDN Failure",
|
|
424
|
-
description: "CDN/static assets unavailable",
|
|
425
|
-
severity: "high",
|
|
426
|
-
config: {
|
|
427
|
-
patterns: [
|
|
428
|
-
"**/*.cloudflare.com/**",
|
|
429
|
-
"**/*.cloudfront.net/**",
|
|
430
|
-
"**/*.akamaized.net/**",
|
|
431
|
-
"**/cdn.**/**",
|
|
432
|
-
"**/static.**/**"
|
|
433
|
-
]
|
|
434
|
-
},
|
|
435
|
-
apply: async (context, page, config) => {
|
|
436
|
-
for (const pattern of config.patterns) {
|
|
437
|
-
await page.route(pattern, route => route.abort("failed"));
|
|
438
|
-
}
|
|
439
|
-
return { type: "cdnFailure", patterns: config.patterns };
|
|
440
|
-
},
|
|
441
|
-
cleanup: async (context, page, state) => {
|
|
442
|
-
for (const pattern of state.patterns) {
|
|
443
|
-
await page.unroute(pattern);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
authProviderDown: {
|
|
449
|
-
name: "Auth Provider Down",
|
|
450
|
-
description: "OAuth/Auth0/Firebase auth fails",
|
|
451
|
-
severity: "critical",
|
|
452
|
-
config: {
|
|
453
|
-
patterns: [
|
|
454
|
-
"**/*.auth0.com/**",
|
|
455
|
-
"**/accounts.google.com/**",
|
|
456
|
-
"**/login.microsoftonline.com/**",
|
|
457
|
-
"**/*.firebaseauth.com/**",
|
|
458
|
-
"**/clerk.**/**"
|
|
459
|
-
]
|
|
460
|
-
},
|
|
461
|
-
apply: async (context, page, config) => {
|
|
462
|
-
for (const pattern of config.patterns) {
|
|
463
|
-
await page.route(pattern, async (route) => {
|
|
464
|
-
await route.fulfill({
|
|
465
|
-
status: 503,
|
|
466
|
-
body: "Auth service unavailable"
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
return { type: "authProviderDown", patterns: config.patterns };
|
|
471
|
-
},
|
|
472
|
-
cleanup: async (context, page, state) => {
|
|
473
|
-
for (const pattern of state.patterns) {
|
|
474
|
-
await page.unroute(pattern);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
paymentGatewayDown: {
|
|
480
|
-
name: "Payment Gateway Down",
|
|
481
|
-
description: "Stripe/PayPal unavailable",
|
|
482
|
-
severity: "critical",
|
|
483
|
-
config: {
|
|
484
|
-
patterns: [
|
|
485
|
-
"**/api.stripe.com/**",
|
|
486
|
-
"**/js.stripe.com/**",
|
|
487
|
-
"**/api.paypal.com/**",
|
|
488
|
-
"**/checkout.stripe.com/**"
|
|
489
|
-
]
|
|
490
|
-
},
|
|
491
|
-
apply: async (context, page, config) => {
|
|
492
|
-
for (const pattern of config.patterns) {
|
|
493
|
-
await page.route(pattern, async (route) => {
|
|
494
|
-
await route.fulfill({
|
|
495
|
-
status: 503,
|
|
496
|
-
contentType: "application/json",
|
|
497
|
-
body: JSON.stringify({
|
|
498
|
-
error: { message: "Payment service unavailable" }
|
|
499
|
-
})
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
return { type: "paymentGatewayDown", patterns: config.patterns };
|
|
504
|
-
},
|
|
505
|
-
cleanup: async (context, page, state) => {
|
|
506
|
-
for (const pattern of state.patterns) {
|
|
507
|
-
await page.unroute(pattern);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
|
|
512
|
-
analyticsBlocked: {
|
|
513
|
-
name: "Analytics Blocked",
|
|
514
|
-
description: "Simulate ad blocker blocking analytics",
|
|
515
|
-
severity: "low",
|
|
516
|
-
config: {
|
|
517
|
-
patterns: [
|
|
518
|
-
"**/google-analytics.com/**",
|
|
519
|
-
"**/googletagmanager.com/**",
|
|
520
|
-
"**/segment.io/**",
|
|
521
|
-
"**/mixpanel.com/**",
|
|
522
|
-
"**/amplitude.com/**",
|
|
523
|
-
"**/hotjar.com/**"
|
|
524
|
-
]
|
|
525
|
-
},
|
|
526
|
-
apply: async (context, page, config) => {
|
|
527
|
-
for (const pattern of config.patterns) {
|
|
528
|
-
await page.route(pattern, route => route.abort("blockedbyclient"));
|
|
529
|
-
}
|
|
530
|
-
return { type: "analyticsBlocked", patterns: config.patterns };
|
|
531
|
-
},
|
|
532
|
-
cleanup: async (context, page, state) => {
|
|
533
|
-
for (const pattern of state.patterns) {
|
|
534
|
-
await page.unroute(pattern);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
|
-
|
|
540
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
541
|
-
// RESOURCE CHAOS
|
|
542
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
543
|
-
resource: {
|
|
544
|
-
memoryPressure: {
|
|
545
|
-
name: "Memory Pressure",
|
|
546
|
-
description: "Simulate low memory conditions",
|
|
547
|
-
severity: "high",
|
|
548
|
-
apply: async (context, page) => {
|
|
549
|
-
// Inject script to consume memory
|
|
550
|
-
await page.evaluate(() => {
|
|
551
|
-
window.__chaosMemory = [];
|
|
552
|
-
for (let i = 0; i < 100; i++) {
|
|
553
|
-
window.__chaosMemory.push(new Array(1000000).fill("x"));
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
return { type: "memoryPressure" };
|
|
557
|
-
},
|
|
558
|
-
cleanup: async (context, page) => {
|
|
559
|
-
await page.evaluate(() => {
|
|
560
|
-
if (window.__chaosMemory) {
|
|
561
|
-
window.__chaosMemory = null;
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
},
|
|
566
|
-
|
|
567
|
-
cpuThrottle: {
|
|
568
|
-
name: "CPU Throttling",
|
|
569
|
-
description: "Simulate slow CPU",
|
|
570
|
-
severity: "medium",
|
|
571
|
-
config: {
|
|
572
|
-
rate: 4 // 4x slowdown
|
|
573
|
-
},
|
|
574
|
-
apply: async (context, page, config) => {
|
|
575
|
-
const cdp = await page.context().newCDPSession(page);
|
|
576
|
-
await cdp.send("Emulation.setCPUThrottlingRate", { rate: config.rate });
|
|
577
|
-
return { type: "cpuThrottle", cdp };
|
|
578
|
-
},
|
|
579
|
-
cleanup: async (context, page, state) => {
|
|
580
|
-
if (state.cdp) {
|
|
581
|
-
await state.cdp.send("Emulation.setCPUThrottlingRate", { rate: 1 });
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
|
|
586
|
-
localStorage: {
|
|
587
|
-
name: "LocalStorage Full",
|
|
588
|
-
description: "Fill localStorage to quota",
|
|
589
|
-
severity: "medium",
|
|
590
|
-
apply: async (context, page) => {
|
|
591
|
-
await page.evaluate(() => {
|
|
592
|
-
try {
|
|
593
|
-
const data = "x".repeat(5 * 1024 * 1024); // 5MB
|
|
594
|
-
localStorage.setItem("__chaos_fill", data);
|
|
595
|
-
} catch {
|
|
596
|
-
// Expected - quota exceeded
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
return { type: "localStorage" };
|
|
600
|
-
},
|
|
601
|
-
cleanup: async (context, page) => {
|
|
602
|
-
await page.evaluate(() => {
|
|
603
|
-
localStorage.removeItem("__chaos_fill");
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
611
|
-
// CHAOS ENGINE CLASS
|
|
612
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
613
|
-
|
|
614
|
-
class ChaosEngine {
|
|
615
|
-
constructor(options = {}) {
|
|
616
|
-
this.projectRoot = options.projectRoot || process.cwd();
|
|
617
|
-
this.scenarios = { ...CHAOS_SCENARIOS };
|
|
618
|
-
this.activeScenarios = new Map();
|
|
619
|
-
this.results = [];
|
|
620
|
-
this.page = null;
|
|
621
|
-
this.context = null;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Set the Playwright page and context
|
|
626
|
-
*/
|
|
627
|
-
setPage(page) {
|
|
628
|
-
this.page = page;
|
|
629
|
-
this.context = page.context();
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Apply a chaos scenario
|
|
634
|
-
*/
|
|
635
|
-
async applyScenario(category, scenarioName, customConfig = {}) {
|
|
636
|
-
if (!this.page) {
|
|
637
|
-
throw new Error("Page not set. Call setPage() first.");
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const categoryScenarios = this.scenarios[category];
|
|
641
|
-
if (!categoryScenarios) {
|
|
642
|
-
throw new Error(`Unknown chaos category: ${category}`);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const scenario = categoryScenarios[scenarioName];
|
|
646
|
-
if (!scenario) {
|
|
647
|
-
throw new Error(`Unknown chaos scenario: ${category}.${scenarioName}`);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const config = { ...scenario.config, ...customConfig };
|
|
651
|
-
const id = `${category}.${scenarioName}`;
|
|
652
|
-
|
|
653
|
-
try {
|
|
654
|
-
const state = await scenario.apply(this.context, this.page, config);
|
|
655
|
-
|
|
656
|
-
this.activeScenarios.set(id, {
|
|
657
|
-
scenario,
|
|
658
|
-
state,
|
|
659
|
-
config,
|
|
660
|
-
startTime: Date.now()
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
return {
|
|
664
|
-
success: true,
|
|
665
|
-
id,
|
|
666
|
-
name: scenario.name,
|
|
667
|
-
description: scenario.description
|
|
668
|
-
};
|
|
669
|
-
} catch (error) {
|
|
670
|
-
return {
|
|
671
|
-
success: false,
|
|
672
|
-
id,
|
|
673
|
-
error: error.message
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Remove a chaos scenario
|
|
680
|
-
*/
|
|
681
|
-
async removeScenario(id) {
|
|
682
|
-
const active = this.activeScenarios.get(id);
|
|
683
|
-
if (!active) {
|
|
684
|
-
return { success: false, error: "Scenario not active" };
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
try {
|
|
688
|
-
await active.scenario.cleanup(this.context, this.page, active.state);
|
|
689
|
-
this.activeScenarios.delete(id);
|
|
690
|
-
|
|
691
|
-
return {
|
|
692
|
-
success: true,
|
|
693
|
-
id,
|
|
694
|
-
duration: Date.now() - active.startTime
|
|
695
|
-
};
|
|
696
|
-
} catch (error) {
|
|
697
|
-
return {
|
|
698
|
-
success: false,
|
|
699
|
-
id,
|
|
700
|
-
error: error.message
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Remove all active scenarios
|
|
707
|
-
*/
|
|
708
|
-
async cleanup() {
|
|
709
|
-
const results = [];
|
|
710
|
-
|
|
711
|
-
for (const id of this.activeScenarios.keys()) {
|
|
712
|
-
const result = await this.removeScenario(id);
|
|
713
|
-
results.push(result);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
return results;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* Run chaos test suite
|
|
721
|
-
*/
|
|
722
|
-
async runChaosSuite(page, url, options = {}) {
|
|
723
|
-
this.setPage(page);
|
|
724
|
-
this.results = [];
|
|
725
|
-
|
|
726
|
-
const {
|
|
727
|
-
categories = Object.keys(this.scenarios),
|
|
728
|
-
duration = 5000,
|
|
729
|
-
captureScreenshots = true
|
|
730
|
-
} = options;
|
|
731
|
-
|
|
732
|
-
// Navigate to URL first
|
|
733
|
-
await page.goto(url, { waitUntil: "networkidle" });
|
|
734
|
-
const baselineScreenshot = captureScreenshots
|
|
735
|
-
? await page.screenshot()
|
|
736
|
-
: null;
|
|
737
|
-
|
|
738
|
-
// Run each chaos scenario
|
|
739
|
-
for (const category of categories) {
|
|
740
|
-
const categoryScenarios = this.scenarios[category];
|
|
741
|
-
|
|
742
|
-
for (const [name, scenario] of Object.entries(categoryScenarios)) {
|
|
743
|
-
const result = await this.runSingleChaosTest(
|
|
744
|
-
category,
|
|
745
|
-
name,
|
|
746
|
-
scenario,
|
|
747
|
-
url,
|
|
748
|
-
duration,
|
|
749
|
-
captureScreenshots
|
|
750
|
-
);
|
|
751
|
-
this.results.push(result);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return {
|
|
756
|
-
url,
|
|
757
|
-
totalTests: this.results.length,
|
|
758
|
-
passed: this.results.filter(r => r.passed).length,
|
|
759
|
-
failed: this.results.filter(r => !r.passed).length,
|
|
760
|
-
results: this.results,
|
|
761
|
-
baselineScreenshot
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* Run a single chaos test
|
|
767
|
-
*/
|
|
768
|
-
async runSingleChaosTest(category, name, scenario, url, duration, captureScreenshots) {
|
|
769
|
-
const id = `${category}.${name}`;
|
|
770
|
-
const result = {
|
|
771
|
-
id,
|
|
772
|
-
name: scenario.name,
|
|
773
|
-
description: scenario.description,
|
|
774
|
-
severity: scenario.severity,
|
|
775
|
-
category,
|
|
776
|
-
passed: true,
|
|
777
|
-
errors: [],
|
|
778
|
-
warnings: [],
|
|
779
|
-
duration: 0
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
const startTime = Date.now();
|
|
783
|
-
|
|
784
|
-
try {
|
|
785
|
-
// Apply chaos
|
|
786
|
-
await this.applyScenario(category, name);
|
|
787
|
-
|
|
788
|
-
// Wait and collect errors
|
|
789
|
-
const consoleErrors = [];
|
|
790
|
-
const networkErrors = [];
|
|
791
|
-
|
|
792
|
-
this.page.on("console", msg => {
|
|
793
|
-
if (msg.type() === "error") {
|
|
794
|
-
consoleErrors.push(msg.text());
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
this.page.on("pageerror", err => {
|
|
799
|
-
consoleErrors.push(err.message);
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
this.page.on("requestfailed", req => {
|
|
803
|
-
networkErrors.push({
|
|
804
|
-
url: req.url(),
|
|
805
|
-
failure: req.failure()?.errorText
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
// Reload page under chaos
|
|
810
|
-
try {
|
|
811
|
-
await this.page.reload({ timeout: duration * 2 });
|
|
812
|
-
} catch (reloadError) {
|
|
813
|
-
result.warnings.push(`Page reload failed: ${reloadError.message}`);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// Wait for chaos duration
|
|
817
|
-
await this.page.waitForTimeout(duration);
|
|
818
|
-
|
|
819
|
-
// Take screenshot under chaos
|
|
820
|
-
if (captureScreenshots) {
|
|
821
|
-
try {
|
|
822
|
-
result.chaosScreenshot = await this.page.screenshot();
|
|
823
|
-
} catch {
|
|
824
|
-
result.warnings.push("Failed to capture chaos screenshot");
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// Check for JS crashes
|
|
829
|
-
const hasJSCrash = await this.page.evaluate(() => {
|
|
830
|
-
return document.body.innerHTML.includes("error") ||
|
|
831
|
-
document.body.innerHTML.includes("crash") ||
|
|
832
|
-
document.querySelector("[class*='error']") !== null;
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
// Evaluate resilience
|
|
836
|
-
if (consoleErrors.length > 0) {
|
|
837
|
-
result.warnings.push(`${consoleErrors.length} console errors during chaos`);
|
|
838
|
-
result.consoleErrors = consoleErrors.slice(0, 5);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (hasJSCrash) {
|
|
842
|
-
result.errors.push("Possible JS crash detected");
|
|
843
|
-
result.passed = false;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// Check for error boundary or graceful degradation
|
|
847
|
-
const hasErrorBoundary = await this.page.evaluate(() => {
|
|
848
|
-
return document.querySelector("[data-error-boundary]") !== null ||
|
|
849
|
-
document.querySelector("[class*='fallback']") !== null ||
|
|
850
|
-
document.body.innerText.toLowerCase().includes("something went wrong");
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
if (!hasErrorBoundary && consoleErrors.length > 3) {
|
|
854
|
-
result.warnings.push("No error boundary detected despite errors");
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// Check for loading states
|
|
858
|
-
const hasLoadingState = await this.page.evaluate(() => {
|
|
859
|
-
return document.querySelector("[class*='loading']") !== null ||
|
|
860
|
-
document.querySelector("[class*='skeleton']") !== null ||
|
|
861
|
-
document.querySelector("[aria-busy='true']") !== null;
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
if (hasLoadingState) {
|
|
865
|
-
result.warnings.push("App stuck in loading state");
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// Remove chaos
|
|
869
|
-
await this.removeScenario(id);
|
|
870
|
-
|
|
871
|
-
// Verify recovery
|
|
872
|
-
try {
|
|
873
|
-
await this.page.reload({ timeout: 10000 });
|
|
874
|
-
const recovered = await this.page.evaluate(() => {
|
|
875
|
-
return !document.body.innerHTML.includes("error") &&
|
|
876
|
-
document.body.innerHTML.length > 100;
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
if (!recovered) {
|
|
880
|
-
result.errors.push("App did not recover after chaos removed");
|
|
881
|
-
result.passed = false;
|
|
882
|
-
}
|
|
883
|
-
} catch {
|
|
884
|
-
result.errors.push("Page failed to reload after chaos");
|
|
885
|
-
result.passed = false;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
} catch (error) {
|
|
889
|
-
result.errors.push(error.message);
|
|
890
|
-
result.passed = false;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
result.duration = Date.now() - startTime;
|
|
894
|
-
|
|
895
|
-
// Final pass/fail based on severity
|
|
896
|
-
if (result.errors.length > 0) {
|
|
897
|
-
result.passed = false;
|
|
898
|
-
} else if (result.warnings.length > 5 && scenario.severity === "high") {
|
|
899
|
-
result.passed = false;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Get available scenarios
|
|
907
|
-
*/
|
|
908
|
-
getAvailableScenarios() {
|
|
909
|
-
const available = [];
|
|
910
|
-
|
|
911
|
-
for (const [category, scenarios] of Object.entries(this.scenarios)) {
|
|
912
|
-
for (const [name, scenario] of Object.entries(scenarios)) {
|
|
913
|
-
available.push({
|
|
914
|
-
id: `${category}.${name}`,
|
|
915
|
-
category,
|
|
916
|
-
name: scenario.name,
|
|
917
|
-
description: scenario.description,
|
|
918
|
-
severity: scenario.severity
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
return available;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Get active scenarios
|
|
928
|
-
*/
|
|
929
|
-
getActiveScenarios() {
|
|
930
|
-
const active = [];
|
|
931
|
-
|
|
932
|
-
for (const [id, data] of this.activeScenarios) {
|
|
933
|
-
active.push({
|
|
934
|
-
id,
|
|
935
|
-
name: data.scenario.name,
|
|
936
|
-
startTime: data.startTime,
|
|
937
|
-
duration: Date.now() - data.startTime
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
return active;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Generate chaos test report
|
|
946
|
-
*/
|
|
947
|
-
generateReport() {
|
|
948
|
-
const passed = this.results.filter(r => r.passed);
|
|
949
|
-
const failed = this.results.filter(r => !r.passed);
|
|
950
|
-
|
|
951
|
-
const bySeverity = {
|
|
952
|
-
critical: this.results.filter(r => r.severity === "critical"),
|
|
953
|
-
high: this.results.filter(r => r.severity === "high"),
|
|
954
|
-
medium: this.results.filter(r => r.severity === "medium"),
|
|
955
|
-
low: this.results.filter(r => r.severity === "low")
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
const byCategory = {};
|
|
959
|
-
for (const result of this.results) {
|
|
960
|
-
if (!byCategory[result.category]) {
|
|
961
|
-
byCategory[result.category] = { passed: 0, failed: 0 };
|
|
962
|
-
}
|
|
963
|
-
byCategory[result.category][result.passed ? "passed" : "failed"]++;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
return {
|
|
967
|
-
timestamp: new Date().toISOString(),
|
|
968
|
-
summary: {
|
|
969
|
-
total: this.results.length,
|
|
970
|
-
passed: passed.length,
|
|
971
|
-
failed: failed.length,
|
|
972
|
-
passRate: this.results.length > 0
|
|
973
|
-
? ((passed.length / this.results.length) * 100).toFixed(1) + "%"
|
|
974
|
-
: "N/A"
|
|
975
|
-
},
|
|
976
|
-
bySeverity: {
|
|
977
|
-
critical: {
|
|
978
|
-
total: bySeverity.critical.length,
|
|
979
|
-
failed: bySeverity.critical.filter(r => !r.passed).length
|
|
980
|
-
},
|
|
981
|
-
high: {
|
|
982
|
-
total: bySeverity.high.length,
|
|
983
|
-
failed: bySeverity.high.filter(r => !r.passed).length
|
|
984
|
-
},
|
|
985
|
-
medium: {
|
|
986
|
-
total: bySeverity.medium.length,
|
|
987
|
-
failed: bySeverity.medium.filter(r => !r.passed).length
|
|
988
|
-
},
|
|
989
|
-
low: {
|
|
990
|
-
total: bySeverity.low.length,
|
|
991
|
-
failed: bySeverity.low.filter(r => !r.passed).length
|
|
992
|
-
}
|
|
993
|
-
},
|
|
994
|
-
byCategory,
|
|
995
|
-
failures: failed.map(r => ({
|
|
996
|
-
id: r.id,
|
|
997
|
-
name: r.name,
|
|
998
|
-
severity: r.severity,
|
|
999
|
-
errors: r.errors,
|
|
1000
|
-
warnings: r.warnings
|
|
1001
|
-
})),
|
|
1002
|
-
recommendations: this.generateRecommendations(failed)
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Generate recommendations from failures
|
|
1008
|
-
*/
|
|
1009
|
-
generateRecommendations(failures) {
|
|
1010
|
-
const recommendations = [];
|
|
1011
|
-
|
|
1012
|
-
const hasNetworkFailures = failures.some(f => f.category === "network");
|
|
1013
|
-
const hasApiFailures = failures.some(f => f.category === "api");
|
|
1014
|
-
const hasPayloadFailures = failures.some(f => f.category === "payload");
|
|
1015
|
-
const hasThirdPartyFailures = failures.some(f => f.category === "thirdParty");
|
|
1016
|
-
|
|
1017
|
-
if (hasNetworkFailures) {
|
|
1018
|
-
recommendations.push({
|
|
1019
|
-
category: "network",
|
|
1020
|
-
priority: "high",
|
|
1021
|
-
message: "Improve offline handling",
|
|
1022
|
-
suggestions: [
|
|
1023
|
-
"Implement service workers for offline support",
|
|
1024
|
-
"Add network status indicators",
|
|
1025
|
-
"Queue failed requests for retry",
|
|
1026
|
-
"Cache critical data locally"
|
|
1027
|
-
]
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
if (hasApiFailures) {
|
|
1032
|
-
recommendations.push({
|
|
1033
|
-
category: "api",
|
|
1034
|
-
priority: "high",
|
|
1035
|
-
message: "Improve API error handling",
|
|
1036
|
-
suggestions: [
|
|
1037
|
-
"Add error boundaries around API-dependent components",
|
|
1038
|
-
"Implement exponential backoff for retries",
|
|
1039
|
-
"Show user-friendly error messages",
|
|
1040
|
-
"Add circuit breakers for failing endpoints"
|
|
1041
|
-
]
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
if (hasPayloadFailures) {
|
|
1046
|
-
recommendations.push({
|
|
1047
|
-
category: "payload",
|
|
1048
|
-
priority: "high",
|
|
1049
|
-
message: "Improve response validation",
|
|
1050
|
-
suggestions: [
|
|
1051
|
-
"Validate API responses with schema libraries",
|
|
1052
|
-
"Handle malformed JSON gracefully",
|
|
1053
|
-
"Add fallback defaults for missing fields",
|
|
1054
|
-
"Log parsing errors for debugging"
|
|
1055
|
-
]
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
if (hasThirdPartyFailures) {
|
|
1060
|
-
recommendations.push({
|
|
1061
|
-
category: "thirdParty",
|
|
1062
|
-
priority: "medium",
|
|
1063
|
-
message: "Improve third-party resilience",
|
|
1064
|
-
suggestions: [
|
|
1065
|
-
"Implement fallbacks for critical third-party services",
|
|
1066
|
-
"Use async loading for non-critical services",
|
|
1067
|
-
"Add health checks for dependencies",
|
|
1068
|
-
"Consider self-hosted alternatives for critical paths"
|
|
1069
|
-
]
|
|
1070
|
-
});
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
return recommendations;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1078
|
-
// EXPORTS
|
|
1079
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1080
|
-
|
|
1081
|
-
module.exports = {
|
|
1082
|
-
ChaosEngine,
|
|
1083
|
-
CHAOS_SCENARIOS
|
|
1084
|
-
};
|