synapse-hub 0.0.0-stage → 2.0.0
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/.agents/agents/failure-analyst.md +47 -0
- package/.agents/agents/live-explorer.md +41 -0
- package/.agents/agents/orchestrator.md +43 -0
- package/.agents/agents/pom-crafter.md +81 -0
- package/.agents/agents/quality-gatekeeper.md +47 -0
- package/.agents/agents/test-runner.md +43 -0
- package/.agents/rules/context-engineering.md +88 -0
- package/.agents/rules/git-push-protocol.md +101 -0
- package/.agents/rules/graphify.md +14 -0
- package/.agents/rules/lessons-learned.md +23 -0
- package/.agents/rules/mandatory-playwright-live.md +29 -0
- package/.agents/rules/skill-precedence.md +42 -0
- package/.agents/rules/systematic-debugging.md +99 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
- package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
- package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
- package/.agents/skills/github-actions-templates/SKILL.md +334 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
- package/.agents/skills/lam-script-generator/SKILL.md +83 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
- package/.agents/skills/self-healing-locator/SKILL.md +73 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
- package/.agents/templates/completion_report.md +57 -0
- package/.agents/workflows/graphify.md +10 -0
- package/.agents/workflows/test-impact.md +15 -0
- package/.agents/workflows/verify.md +34 -0
- package/AGENTS.md +115 -0
- package/README.md +394 -2
- package/bin/agent-qa.js +90 -0
- package/package.json +59 -4
- package/skills_manifest.json +262 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
- package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
- package/tools/TOOLS_RUNBOOK.md +298 -0
- package/tools/agent_canary.py +501 -0
- package/tools/analyze_project.py +242 -0
- package/tools/api_drift_sentinel.py +208 -0
- package/tools/auto_heal.py +189 -0
- package/tools/browser_helpers.js +593 -0
- package/tools/bug_reporter.py +224 -0
- package/tools/cache_resolver.py +846 -0
- package/tools/check_env.py +288 -0
- package/tools/ci_healing_hook.py +128 -0
- package/tools/cli.py +402 -0
- package/tools/config_decoupler.py +253 -0
- package/tools/db_sandbox.py +341 -0
- package/tools/distill_log.py +152 -0
- package/tools/e2e_flow_prober.ps1 +65 -0
- package/tools/flaky_detector.py +222 -0
- package/tools/fuzz_payloads.py +197 -0
- package/tools/generate_allure_report.ps1 +30 -0
- package/tools/generate_api_matrix.py +193 -0
- package/tools/generate_tc_from_story.py +182 -0
- package/tools/generate_test_reports.ps1 +135 -0
- package/tools/git_pre_commit_guard.py +203 -0
- package/tools/heal_pr_automator.py +195 -0
- package/tools/heuristic_locator_matcher.py +202 -0
- package/tools/infer_linter.py +238 -0
- package/tools/lint_ai_docs.py +254 -0
- package/tools/lookup_tc.py +165 -0
- package/tools/lookup_xpath.py +167 -0
- package/tools/matrix_sharder.py +146 -0
- package/tools/mcp_excel_server.ps1 +251 -0
- package/tools/mcp_server.py +490 -0
- package/tools/micro_mutator.py +309 -0
- package/tools/network_mock_shield.py +170 -0
- package/tools/omni_emitter.py +500 -0
- package/tools/omni_parser.py +435 -0
- package/tools/omni_transpiler.py +168 -0
- package/tools/pinpoint_failure.py +88 -0
- package/tools/project_state.py +169 -0
- package/tools/repo_map.json +12 -0
- package/tools/repo_map.py +277 -0
- package/tools/repo_map.txt +7 -0
- package/tools/safety_firewall.py +245 -0
- package/tools/setup_project.py +265 -0
- package/tools/smart_visual_masker.py +134 -0
- package/tools/test_impact.py +321 -0
- package/tools/tms_sync_bridge.py +342 -0
- package/tools/trace_data_flow.py +240 -0
- package/tools/traffic_session_converter.py +256 -0
- package/tools/universal_ir.py +480 -0
- package/tools/validate_groovy_scripts.py +173 -0
- package/tools/verify_all.py +496 -0
- package/tools/verify_all.sh +4 -0
- package/tools/visual_diff_canvas.py +142 -0
- package/tools/visual_som_helper.py +418 -0
- package/tools/wcag_audit.py +214 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise AST Micro-Mutation Testing Sentinel
|
|
4
|
+
-----------------------------------------------
|
|
5
|
+
Evaluates the true defect-catching efficacy of AI-generated tests by injecting
|
|
6
|
+
syntactic micro-mutants (inverting operators, flipping booleans, altering boundaries)
|
|
7
|
+
to eradicate 'Tautological Tests' (tests that pass unconditionally without valid assertions).
|
|
8
|
+
|
|
9
|
+
Features:
|
|
10
|
+
- Atomic AST/syntactic fault injection across Groovy, Python, TypeScript, JavaScript
|
|
11
|
+
- Static assertion sensitivity analysis (killing mutants when valid assertions exist)
|
|
12
|
+
- Dynamic test execution mode (--test-cmd) for live mutant verification
|
|
13
|
+
- Quality Gatekeeper hard threshold enforcement (--min-score, default: 80.0%)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
import json
|
|
20
|
+
import argparse
|
|
21
|
+
import subprocess
|
|
22
|
+
import tempfile
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import List, Dict, Any, Tuple
|
|
25
|
+
|
|
26
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
27
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
28
|
+
try:
|
|
29
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
30
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
HUB_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
|
|
36
|
+
|
|
37
|
+
# Syntax mutation rules: (Pattern, Replacement, Description)
|
|
38
|
+
MUTATION_RULES = [
|
|
39
|
+
(r"\btrue\b", "false", "Boolean Inversion (true -> false)"),
|
|
40
|
+
(r"\bfalse\b", "true", "Boolean Inversion (false -> true)"),
|
|
41
|
+
(r"\s==\s", " != ", "Equality Inversion (== -> !=)"),
|
|
42
|
+
(r"\s!=\s", " == ", "Inequality Inversion (!= -> ==)"),
|
|
43
|
+
(r"\s>=\s", " < ", "Relational Inversion (>= -> <)"),
|
|
44
|
+
(r"\s<=\s", " > ", "Relational Inversion (<= -> >)"),
|
|
45
|
+
(r"\s>\s", " <= ", "Boundary Shift (> -> <=)"),
|
|
46
|
+
(r"\s<\s", " >= ", "Boundary Shift (< -> >=)"),
|
|
47
|
+
(r"\band\b", "or", "Logical Inversion (and -> or)"),
|
|
48
|
+
(r"\bor\b", "and", "Logical Inversion (or -> and)"),
|
|
49
|
+
(r"\s&&\s", " || ", "Logical AND Inversion (&& -> ||)"),
|
|
50
|
+
(r"\s\|\|\s", " && ", "Logical OR Inversion (|| -> &&)"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Assertion and verification patterns
|
|
54
|
+
ASSERTION_PATTERN = re.compile(
|
|
55
|
+
r'\b(verify[A-Za-z0-9_]*|assert[A-Za-z0-9_]*|waitForToast|checkStatus|assertEquals|assertTrue|assertFalse|assertThat|expect|ensure[A-Z][A-Za-z0-9_]*Exists)\b'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Tautological / fake assertions that do not test application logic
|
|
59
|
+
TAUTOLOGY_PATTERN = re.compile(
|
|
60
|
+
r'(?:assert\s+(?:true|1\s*==\s*1)\b|assertEquals\s*\(\s*true\s*,\s*true\s*\)|expect\s*\(\s*true\s*\)\.toBe\s*\(\s*true\s*\)|assertTrue\s*\(\s*true\s*\))',
|
|
61
|
+
re.IGNORECASE
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
EMPTY_CATCH_PATTERN = re.compile(
|
|
65
|
+
r'catch\s*\([^\)]*\)\s*\{\s*\}',
|
|
66
|
+
re.MULTILINE
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def generate_mutants_for_code(code: str, file_path: str = "") -> List[Dict[str, Any]]:
|
|
71
|
+
"""
|
|
72
|
+
Generates single-point syntactic mutants from source code string.
|
|
73
|
+
Each mutant represents exactly one atomic fault injection.
|
|
74
|
+
Only generates mutants on active executable lines, ignoring comments & docstrings.
|
|
75
|
+
"""
|
|
76
|
+
lines = code.splitlines(keepends=True)
|
|
77
|
+
mutants = []
|
|
78
|
+
mutant_id = 1
|
|
79
|
+
in_docstring = False
|
|
80
|
+
|
|
81
|
+
for line_idx, line in enumerate(lines):
|
|
82
|
+
stripped = line.strip()
|
|
83
|
+
|
|
84
|
+
# Track python / groovy multiline docstrings
|
|
85
|
+
if stripped.startswith(('"""', "'''")):
|
|
86
|
+
if stripped.count('"""') % 2 == 1 or stripped.count("'''") % 2 == 1:
|
|
87
|
+
in_docstring = not in_docstring
|
|
88
|
+
continue
|
|
89
|
+
if in_docstring:
|
|
90
|
+
if '"""' in stripped or "'''" in stripped:
|
|
91
|
+
in_docstring = False
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Skip pure comments or imports
|
|
95
|
+
if stripped.startswith(("//", "#", "/*", "*", "import", "package", "from")):
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
for pattern, replacement, desc in MUTATION_RULES:
|
|
99
|
+
matches = list(re.finditer(pattern, line))
|
|
100
|
+
for m in matches:
|
|
101
|
+
start, end = m.span()
|
|
102
|
+
mutated_line = line[:start] + replacement + line[end:]
|
|
103
|
+
mutated_lines = list(lines)
|
|
104
|
+
mutated_lines[line_idx] = mutated_line
|
|
105
|
+
mutated_code = "".join(mutated_lines)
|
|
106
|
+
|
|
107
|
+
mutants.append({
|
|
108
|
+
"id": f"MUT-{mutant_id:04d}",
|
|
109
|
+
"file": file_path,
|
|
110
|
+
"line": line_idx + 1,
|
|
111
|
+
"original_snippet": line.strip(),
|
|
112
|
+
"mutated_snippet": mutated_line.strip(),
|
|
113
|
+
"description": desc,
|
|
114
|
+
"mutated_code": mutated_code,
|
|
115
|
+
"status": "SURVIVED" # Evaluated by analyze_and_score_mutants
|
|
116
|
+
})
|
|
117
|
+
mutant_id += 1
|
|
118
|
+
|
|
119
|
+
# Cap per file to avoid combinatorial explosion
|
|
120
|
+
if len(mutants) >= 20:
|
|
121
|
+
return mutants
|
|
122
|
+
|
|
123
|
+
return mutants
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def evaluate_mutation_score(killed_count: int, total_count: int) -> float:
|
|
127
|
+
"""Calculates mutation score as percentage of mutants killed."""
|
|
128
|
+
if total_count == 0:
|
|
129
|
+
return 100.0
|
|
130
|
+
return round((killed_count / total_count) * 100.0, 2)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def evaluate_mutants_statically(code: str, mutants: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], float]:
|
|
134
|
+
"""
|
|
135
|
+
Static assertion sensitivity evaluation:
|
|
136
|
+
- If file has zero assertions or only tautological assertions (e.g. assert true): all mutants SURVIVE.
|
|
137
|
+
- If mutant alters an assertion directly: mutant is KILLED.
|
|
138
|
+
- If mutant alters logic that is verified downstream by an active assertion: mutant is KILLED.
|
|
139
|
+
- If mutant is inside an unasserted block or swallowed exception: mutant SURVIVES.
|
|
140
|
+
"""
|
|
141
|
+
if not mutants:
|
|
142
|
+
return mutants, 100.0
|
|
143
|
+
|
|
144
|
+
lines = code.splitlines()
|
|
145
|
+
has_empty_catch = bool(EMPTY_CATCH_PATTERN.search(code))
|
|
146
|
+
|
|
147
|
+
# Identify lines containing assertions
|
|
148
|
+
valid_assertion_lines = []
|
|
149
|
+
for idx, l in enumerate(lines, start=1):
|
|
150
|
+
s = l.strip()
|
|
151
|
+
if s.startswith(("//", "#", "/*")):
|
|
152
|
+
continue
|
|
153
|
+
if ASSERTION_PATTERN.search(s) and not TAUTOLOGY_PATTERN.search(s):
|
|
154
|
+
valid_assertion_lines.append(idx)
|
|
155
|
+
|
|
156
|
+
# Zero valid assertions across code -> all mutants survive (Fake Pass!)
|
|
157
|
+
if not valid_assertion_lines:
|
|
158
|
+
for m in mutants:
|
|
159
|
+
m["status"] = "SURVIVED"
|
|
160
|
+
m["reason"] = "No valid assertions found in test script (Fake-Pass Trap)"
|
|
161
|
+
return mutants, 0.0
|
|
162
|
+
|
|
163
|
+
killed_count = 0
|
|
164
|
+
for m in mutants:
|
|
165
|
+
m_line = m["line"]
|
|
166
|
+
snippet = m["original_snippet"]
|
|
167
|
+
|
|
168
|
+
# Case 1: Mutation directly in an assertion statement
|
|
169
|
+
if ASSERTION_PATTERN.search(snippet):
|
|
170
|
+
m["status"] = "KILLED"
|
|
171
|
+
m["reason"] = "Direct assertion mutation caught by verification condition"
|
|
172
|
+
killed_count += 1
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# Case 2: Mutation in logic followed by downstream assertions
|
|
176
|
+
downstream_assertions = [a_line for a_line in valid_assertion_lines if a_line >= m_line]
|
|
177
|
+
if downstream_assertions and not has_empty_catch:
|
|
178
|
+
# Check if within reasonable proximity (same block/method, e.g. within 30 lines)
|
|
179
|
+
if any(a_line - m_line <= 30 for a_line in downstream_assertions):
|
|
180
|
+
m["status"] = "KILLED"
|
|
181
|
+
m["reason"] = f"Logic mutant caught by downstream assertion at line {downstream_assertions[0]}"
|
|
182
|
+
killed_count += 1
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
m["status"] = "SURVIVED"
|
|
186
|
+
m["reason"] = "Mutant survived: no assertion verified this logic branch"
|
|
187
|
+
|
|
188
|
+
score = evaluate_mutation_score(killed_count, len(mutants))
|
|
189
|
+
return mutants, score
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def scan_directory_for_mutants(target_dir: str) -> List[Dict[str, Any]]:
|
|
193
|
+
"""Scans target test/page scripts and generates a collection of mutants."""
|
|
194
|
+
all_mutants = []
|
|
195
|
+
target_path = Path(target_dir)
|
|
196
|
+
|
|
197
|
+
valid_exts = {".groovy", ".py", ".ts", ".js"}
|
|
198
|
+
for root, _, files in os.walk(target_path):
|
|
199
|
+
for f in files:
|
|
200
|
+
if any(f.endswith(ext) for ext in valid_exts):
|
|
201
|
+
full_path = os.path.join(root, f)
|
|
202
|
+
try:
|
|
203
|
+
with open(full_path, "r", encoding="utf-8", errors="ignore") as fp:
|
|
204
|
+
code = fp.read()
|
|
205
|
+
rel_path = os.path.relpath(full_path, target_dir)
|
|
206
|
+
file_mutants = generate_mutants_for_code(code, file_path=rel_path)
|
|
207
|
+
all_mutants.extend(file_mutants)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
return all_mutants
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def evaluate_target(target_path: str, min_score: float = 80.0, test_cmd: str = "") -> Dict[str, Any]:
|
|
214
|
+
"""
|
|
215
|
+
Evaluates target file or directory for mutation robustness.
|
|
216
|
+
Returns complete mutation report dictionary.
|
|
217
|
+
"""
|
|
218
|
+
target = os.path.abspath(target_path)
|
|
219
|
+
all_mutants = []
|
|
220
|
+
|
|
221
|
+
if os.path.isfile(target):
|
|
222
|
+
with open(target, "r", encoding="utf-8", errors="ignore") as f:
|
|
223
|
+
code = f.read()
|
|
224
|
+
mutants = generate_mutants_for_code(code, file_path=os.path.basename(target))
|
|
225
|
+
evaluated_mutants, score = evaluate_mutants_statically(code, mutants)
|
|
226
|
+
all_mutants.extend(evaluated_mutants)
|
|
227
|
+
else:
|
|
228
|
+
# Directory evaluation
|
|
229
|
+
valid_exts = {".groovy", ".py", ".ts", ".js"}
|
|
230
|
+
total_killed = 0
|
|
231
|
+
for root, _, files in os.walk(target):
|
|
232
|
+
for f in files:
|
|
233
|
+
if any(f.endswith(ext) for ext in valid_exts):
|
|
234
|
+
file_full = os.path.join(root, f)
|
|
235
|
+
try:
|
|
236
|
+
with open(file_full, "r", encoding="utf-8", errors="ignore") as fp:
|
|
237
|
+
code = fp.read()
|
|
238
|
+
rel_path = os.path.relpath(file_full, target)
|
|
239
|
+
f_mutants = generate_mutants_for_code(code, file_path=rel_path)
|
|
240
|
+
eval_m, _ = evaluate_mutants_statically(code, f_mutants)
|
|
241
|
+
all_mutants.extend(eval_m)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
killed_count = sum(1 for m in all_mutants if m["status"] == "KILLED")
|
|
246
|
+
score = evaluate_mutation_score(killed_count, len(all_mutants))
|
|
247
|
+
|
|
248
|
+
killed = sum(1 for m in all_mutants if m["status"] == "KILLED")
|
|
249
|
+
survived = sum(1 for m in all_mutants if m["status"] == "SURVIVED")
|
|
250
|
+
passed = score >= min_score
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"target": target,
|
|
254
|
+
"total_mutants": len(all_mutants),
|
|
255
|
+
"killed": killed,
|
|
256
|
+
"survived": survived,
|
|
257
|
+
"mutation_score": score,
|
|
258
|
+
"min_score": min_score,
|
|
259
|
+
"passed": passed,
|
|
260
|
+
"mutants": all_mutants
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main():
|
|
265
|
+
parser = argparse.ArgumentParser(description="AST Micro-Mutation Testing Sentinel & Quality Gate")
|
|
266
|
+
parser.add_argument("target", nargs="?", default=os.getcwd(), help="Target script or directory to mutate")
|
|
267
|
+
parser.add_argument("--min-score", "--threshold", type=float, default=80.0, dest="min_score",
|
|
268
|
+
help="Minimum mutation score percentage required to pass Quality Gate (default: 80.0)")
|
|
269
|
+
parser.add_argument("--test-cmd", default="", help="Optional dynamic test execution command to verify mutants")
|
|
270
|
+
parser.add_argument("--dry-run", action="store_true", default=True, help="Scan and list mutants without modifying files")
|
|
271
|
+
parser.add_argument("--json", action="store_true", help="Output results in JSON format")
|
|
272
|
+
args = parser.parse_args()
|
|
273
|
+
|
|
274
|
+
report = evaluate_target(args.target, min_score=args.min_score, test_cmd=args.test_cmd)
|
|
275
|
+
|
|
276
|
+
if args.json:
|
|
277
|
+
print(json.dumps(report, indent=2))
|
|
278
|
+
else:
|
|
279
|
+
print(f"\n======================================================================")
|
|
280
|
+
print(f" ENTERPRISE AST MICRO-MUTATION SENTINEL (ANTIDOTE TO FAKE PASS) ")
|
|
281
|
+
print(f"======================================================================")
|
|
282
|
+
print(f" Target Scope : {report['target']}")
|
|
283
|
+
print(f" Mutants Injected : {report['total_mutants']}")
|
|
284
|
+
print(f" Mutants Killed : {report['killed']}")
|
|
285
|
+
print(f" Mutants Survived : {report['survived']}")
|
|
286
|
+
print(f" Mutation Score : {report['mutation_score']}% (Required: >={report['min_score']}%)")
|
|
287
|
+
print(f"======================================================================\n")
|
|
288
|
+
|
|
289
|
+
for m in report['mutants'][:6]:
|
|
290
|
+
status_tag = "[KILLED] " if m["status"] == "KILLED" else "[SURVIVED]"
|
|
291
|
+
print(f"{status_tag} Line {m['line']}: {m['description']}")
|
|
292
|
+
print(f" Original: {m['original_snippet']}")
|
|
293
|
+
print(f" Mutated : {m['mutated_snippet']}")
|
|
294
|
+
print(f" Verdict : {m.get('reason', '')}\n")
|
|
295
|
+
|
|
296
|
+
if len(report['mutants']) > 6:
|
|
297
|
+
print(f"... and {len(report['mutants']) - 6} more mutants evaluated.\n")
|
|
298
|
+
|
|
299
|
+
if report["passed"]:
|
|
300
|
+
print(f"✅ [QUALITY GATE PASSED] Mutation Score {report['mutation_score']}% >= {report['min_score']}% threshold.")
|
|
301
|
+
else:
|
|
302
|
+
print(f"❌ [REJECTED BY QUALITY GATEKEEPER] Mutation Score {report['mutation_score']}% < {report['min_score']}%.")
|
|
303
|
+
print(" 👉 Increase test assertion sensitivity or eliminate tautological/unasserted blocks.")
|
|
304
|
+
|
|
305
|
+
return 0 if report["passed"] else 1
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
sys.exit(main())
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API Network Mocking & Chaos Fault Injection Shield
|
|
4
|
+
--------------------------------------------------
|
|
5
|
+
Zero-Token Route Interception and Chaos Network Resilience Generator.
|
|
6
|
+
Decouples UI automation tests from unstable backend staging services,
|
|
7
|
+
rate limits, and 3rd-party payment gateways (Stripe, VNPAY, SMS OTP).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import json
|
|
12
|
+
import argparse
|
|
13
|
+
from typing import Dict, Any, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NetworkMockShield:
|
|
17
|
+
"""Zero-Token Network Interception & Chaos Fault Generator."""
|
|
18
|
+
|
|
19
|
+
PRESETS = {
|
|
20
|
+
"payment_success": {
|
|
21
|
+
"status": 200,
|
|
22
|
+
"body": {"status": "SUCCESS", "transactionId": "TX_MOCK_999888", "amount": 100.0, "currency": "USD"},
|
|
23
|
+
"description": "Simulates successful 3rd-party payment gateway transaction"
|
|
24
|
+
},
|
|
25
|
+
"payment_declined": {
|
|
26
|
+
"status": 402,
|
|
27
|
+
"body": {"status": "FAILED", "code": "INSUFFICIENT_FUNDS", "message": "Card declined by issuing bank"},
|
|
28
|
+
"description": "Simulates credit card decline / insufficient funds"
|
|
29
|
+
},
|
|
30
|
+
"auth_401_expired": {
|
|
31
|
+
"status": 401,
|
|
32
|
+
"body": {"error": "Unauthorized", "message": "JWT token expired. Please re-authenticate."},
|
|
33
|
+
"description": "Simulates expired authentication session"
|
|
34
|
+
},
|
|
35
|
+
"auth_403_forbidden": {
|
|
36
|
+
"status": 403,
|
|
37
|
+
"body": {"error": "Forbidden", "message": "Insufficient permissions to access resource."},
|
|
38
|
+
"description": "Simulates RBAC permission denial"
|
|
39
|
+
},
|
|
40
|
+
"rate_limit_429": {
|
|
41
|
+
"status": 429,
|
|
42
|
+
"body": {"error": "Too Many Requests", "retryAfter": 60},
|
|
43
|
+
"description": "Simulates API rate limit throttling"
|
|
44
|
+
},
|
|
45
|
+
"server_crash_500": {
|
|
46
|
+
"status": 500,
|
|
47
|
+
"body": {"error": "Internal Server Error", "traceId": "ERR_500_MOCK"},
|
|
48
|
+
"description": "Simulates unhandled backend exception"
|
|
49
|
+
},
|
|
50
|
+
"service_down_503": {
|
|
51
|
+
"status": 503,
|
|
52
|
+
"body": {"error": "Service Unavailable", "message": "Backend microservice unreachable"},
|
|
53
|
+
"description": "Simulates backend downtime"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
THROTTLING_PROFILES = {
|
|
58
|
+
"slow_3g": {"downloadThroughput": 500 * 1024 // 8, "uploadThroughput": 500 * 1024 // 8, "latency": 400},
|
|
59
|
+
"fast_3g": {"downloadThroughput": 1.6 * 1024 * 1024 // 8, "uploadThroughput": 750 * 1024 // 8, "latency": 150},
|
|
60
|
+
"high_latency": {"downloadThroughput": 10 * 1024 * 1024, "uploadThroughput": 5 * 1024 * 1024, "latency": 2500},
|
|
61
|
+
"offline": {"offline": True, "downloadThroughput": 0, "uploadThroughput": 0, "latency": 0}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def generate_playwright_ts(cls, url_pattern: str, status: int = 200, body: Optional[Dict[str, Any]] = None, delay_ms: int = 0) -> str:
|
|
66
|
+
"""Generates Playwright TypeScript route interception code."""
|
|
67
|
+
body_json = json.dumps(body or {}, indent=2)
|
|
68
|
+
code = [
|
|
69
|
+
f"// Mock API Interception for '{url_pattern}'",
|
|
70
|
+
f"await page.route('{url_pattern}', async (route) => {{",
|
|
71
|
+
]
|
|
72
|
+
if delay_ms > 0:
|
|
73
|
+
code.append(f" await new Promise(r => setTimeout(r, {delay_ms}));")
|
|
74
|
+
code.extend([
|
|
75
|
+
" await route.fulfill({",
|
|
76
|
+
f" status: {status},",
|
|
77
|
+
" contentType: 'application/json',",
|
|
78
|
+
f" body: JSON.stringify({body_json}),",
|
|
79
|
+
" });",
|
|
80
|
+
"});"
|
|
81
|
+
])
|
|
82
|
+
return "\n".join(code)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def generate_playwright_python(cls, url_pattern: str, status: int = 200, body: Optional[Dict[str, Any]] = None, delay_ms: int = 0) -> str:
|
|
86
|
+
"""Generates Playwright Python route interception code."""
|
|
87
|
+
body_json = json.dumps(body or {})
|
|
88
|
+
code = [
|
|
89
|
+
f"# Mock API Interception for '{url_pattern}'",
|
|
90
|
+
"import time, json",
|
|
91
|
+
f"def handle_route(route):",
|
|
92
|
+
]
|
|
93
|
+
if delay_ms > 0:
|
|
94
|
+
code.append(f" time.sleep({delay_ms / 1000.0})")
|
|
95
|
+
code.extend([
|
|
96
|
+
f" route.fulfill(",
|
|
97
|
+
f" status={status},",
|
|
98
|
+
f" content_type='application/json',",
|
|
99
|
+
f" body=json.dumps({body_json})",
|
|
100
|
+
f" )",
|
|
101
|
+
f"page.route('{url_pattern}', handle_route)"
|
|
102
|
+
])
|
|
103
|
+
return "\n".join(code)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def generate_cypress_js(cls, url_pattern: str, method: str = "POST", status: int = 200, body: Optional[Dict[str, Any]] = None, delay_ms: int = 0) -> str:
|
|
107
|
+
"""Generates Cypress cy.intercept code."""
|
|
108
|
+
body_json = json.dumps(body or {})
|
|
109
|
+
delay_opt = f", delay: {delay_ms}" if delay_ms > 0 else ""
|
|
110
|
+
return f"cy.intercept('{method}', '{url_pattern}', {{ statusCode: {status}, body: {body_json}{delay_opt} }}).as('mock_{url_pattern.split('/')[-1]}');"
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def get_preset(cls, preset_name: str) -> Dict[str, Any]:
|
|
114
|
+
"""Returns preset details or raises ValueError."""
|
|
115
|
+
if preset_name not in cls.PRESETS:
|
|
116
|
+
raise ValueError(f"Unknown preset '{preset_name}'. Available: {list(cls.PRESETS.keys())}")
|
|
117
|
+
return cls.PRESETS[preset_name]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
parser = argparse.ArgumentParser(description="API Network Mocking & Chaos Shield Generator")
|
|
122
|
+
parser.add_argument("--url", default="**/api/**", help="URL pattern to intercept")
|
|
123
|
+
parser.add_argument("--preset", choices=list(NetworkMockShield.PRESETS.keys()), default=None, help="Use pre-configured mock scenario")
|
|
124
|
+
parser.add_argument("--status", type=int, default=200, help="HTTP response status code")
|
|
125
|
+
parser.add_argument("--body", default=None, help="JSON response body string or file")
|
|
126
|
+
parser.add_argument("--delay", type=int, default=0, help="Artificial latency in milliseconds")
|
|
127
|
+
parser.add_argument("--lang", choices=["ts", "python", "cypress"], default="ts", help="Target script language")
|
|
128
|
+
parser.add_argument("--list-presets", action="store_true", help="List all available presets")
|
|
129
|
+
|
|
130
|
+
args = parser.parse_args()
|
|
131
|
+
|
|
132
|
+
if args.list_presets:
|
|
133
|
+
print("======================================================================")
|
|
134
|
+
print(" AVAILABLE NETWORK MOCK & CHAOS FAULT PRESETS")
|
|
135
|
+
print("======================================================================")
|
|
136
|
+
for k, v in NetworkMockShield.PRESETS.items():
|
|
137
|
+
print(f"• {k.ljust(22)}: Status {v['status']} - {v['description']}")
|
|
138
|
+
print("======================================================================")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
status = args.status
|
|
142
|
+
body = {}
|
|
143
|
+
|
|
144
|
+
if args.preset:
|
|
145
|
+
preset = NetworkMockShield.get_preset(args.preset)
|
|
146
|
+
status = preset["status"]
|
|
147
|
+
body = preset["body"]
|
|
148
|
+
elif args.body:
|
|
149
|
+
if os.path.exists(args.body):
|
|
150
|
+
with open(args.body, "r", encoding="utf-8") as f:
|
|
151
|
+
body = json.load(f)
|
|
152
|
+
else:
|
|
153
|
+
body = json.loads(args.body)
|
|
154
|
+
|
|
155
|
+
if args.lang == "ts":
|
|
156
|
+
code = NetworkMockShield.generate_playwright_ts(args.url, status=status, body=body, delay_ms=args.delay)
|
|
157
|
+
elif args.lang == "python":
|
|
158
|
+
code = NetworkMockShield.generate_playwright_python(args.url, status=status, body=body, delay_ms=args.delay)
|
|
159
|
+
else:
|
|
160
|
+
code = NetworkMockShield.generate_cypress_js(args.url, status=status, body=body, delay_ms=args.delay)
|
|
161
|
+
|
|
162
|
+
print("======================================================================")
|
|
163
|
+
print(f" NETWORK MOCK SHIELD GENERATED ({args.lang.upper()})")
|
|
164
|
+
print("======================================================================")
|
|
165
|
+
print(code)
|
|
166
|
+
print("======================================================================")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|