synapse-hub 0.0.0-stage → 2.0.1

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.
Files changed (122) hide show
  1. package/.agents/agents/failure-analyst.md +47 -0
  2. package/.agents/agents/live-explorer.md +41 -0
  3. package/.agents/agents/orchestrator.md +43 -0
  4. package/.agents/agents/pom-crafter.md +81 -0
  5. package/.agents/agents/quality-gatekeeper.md +47 -0
  6. package/.agents/agents/test-runner.md +43 -0
  7. package/.agents/rules/context-engineering.md +88 -0
  8. package/.agents/rules/git-push-protocol.md +101 -0
  9. package/.agents/rules/graphify.md +14 -0
  10. package/.agents/rules/lessons-learned.md +23 -0
  11. package/.agents/rules/mandatory-playwright-live.md +29 -0
  12. package/.agents/rules/skill-precedence.md +42 -0
  13. package/.agents/rules/systematic-debugging.md +99 -0
  14. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
  15. package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
  16. package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
  17. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
  18. package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
  19. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
  20. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
  21. package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
  22. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
  23. package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
  24. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
  25. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
  26. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
  27. package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
  28. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
  29. package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
  30. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
  31. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
  32. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
  33. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
  34. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
  35. package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
  36. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
  37. package/.agents/skills/github-actions-templates/SKILL.md +334 -0
  38. package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
  39. package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
  40. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
  41. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
  42. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
  43. package/.agents/skills/lam-script-generator/SKILL.md +83 -0
  44. package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
  45. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
  46. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
  47. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
  48. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
  49. package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
  50. package/.agents/skills/self-healing-locator/SKILL.md +73 -0
  51. package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
  52. package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
  53. package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
  54. package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
  55. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
  56. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
  57. package/.agents/templates/completion_report.md +57 -0
  58. package/.agents/workflows/graphify.md +10 -0
  59. package/.agents/workflows/test-impact.md +15 -0
  60. package/.agents/workflows/verify.md +34 -0
  61. package/AGENTS.md +115 -0
  62. package/README.md +537 -2
  63. package/bin/agent-qa.js +90 -0
  64. package/package.json +59 -4
  65. package/skills_manifest.json +262 -0
  66. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
  67. package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
  68. package/tools/TOOLS_RUNBOOK.md +298 -0
  69. package/tools/agent_canary.py +501 -0
  70. package/tools/analyze_project.py +242 -0
  71. package/tools/api_drift_sentinel.py +208 -0
  72. package/tools/auto_heal.py +189 -0
  73. package/tools/browser_helpers.js +593 -0
  74. package/tools/bug_reporter.py +224 -0
  75. package/tools/cache_resolver.py +846 -0
  76. package/tools/check_env.py +288 -0
  77. package/tools/ci_healing_hook.py +128 -0
  78. package/tools/cli.py +402 -0
  79. package/tools/config_decoupler.py +253 -0
  80. package/tools/db_sandbox.py +341 -0
  81. package/tools/distill_log.py +152 -0
  82. package/tools/e2e_flow_prober.ps1 +65 -0
  83. package/tools/flaky_detector.py +222 -0
  84. package/tools/fuzz_payloads.py +197 -0
  85. package/tools/generate_allure_report.ps1 +30 -0
  86. package/tools/generate_api_matrix.py +193 -0
  87. package/tools/generate_tc_from_story.py +182 -0
  88. package/tools/generate_test_reports.ps1 +135 -0
  89. package/tools/git_pre_commit_guard.py +203 -0
  90. package/tools/heal_pr_automator.py +195 -0
  91. package/tools/heuristic_locator_matcher.py +202 -0
  92. package/tools/infer_linter.py +238 -0
  93. package/tools/lint_ai_docs.py +254 -0
  94. package/tools/lookup_tc.py +165 -0
  95. package/tools/lookup_xpath.py +167 -0
  96. package/tools/matrix_sharder.py +146 -0
  97. package/tools/mcp_excel_server.ps1 +251 -0
  98. package/tools/mcp_server.py +490 -0
  99. package/tools/micro_mutator.py +309 -0
  100. package/tools/network_mock_shield.py +170 -0
  101. package/tools/omni_emitter.py +500 -0
  102. package/tools/omni_parser.py +435 -0
  103. package/tools/omni_transpiler.py +168 -0
  104. package/tools/pinpoint_failure.py +88 -0
  105. package/tools/project_state.py +169 -0
  106. package/tools/repo_map.json +12 -0
  107. package/tools/repo_map.py +277 -0
  108. package/tools/repo_map.txt +7 -0
  109. package/tools/safety_firewall.py +245 -0
  110. package/tools/setup_project.py +265 -0
  111. package/tools/smart_visual_masker.py +134 -0
  112. package/tools/test_impact.py +321 -0
  113. package/tools/tms_sync_bridge.py +342 -0
  114. package/tools/trace_data_flow.py +240 -0
  115. package/tools/traffic_session_converter.py +256 -0
  116. package/tools/universal_ir.py +480 -0
  117. package/tools/validate_groovy_scripts.py +173 -0
  118. package/tools/verify_all.py +496 -0
  119. package/tools/verify_all.sh +4 -0
  120. package/tools/visual_diff_canvas.py +142 -0
  121. package/tools/visual_som_helper.py +425 -0
  122. package/tools/wcag_audit.py +214 -0
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Flaky Test Detector & Quarantine Sentinel
4
+ ---------------------------------------------------
5
+ Executes a target test scenario N times (default: 5) or parses historical JUnit XML
6
+ reports to calculate the Flakiness Index, detect state flips, and recommend quarantine isolation.
7
+
8
+ Usage:
9
+ python tools/flaky_detector.py --repeat 5 --cmd "python -m unittest tests/test_tools.py"
10
+ python tools/flaky_detector.py --reports "reports/**/junit*.xml"
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import glob
16
+ import json
17
+ import time
18
+ import argparse
19
+ import subprocess
20
+ import xml.etree.ElementTree as ET
21
+ from datetime import datetime
22
+
23
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
24
+ if hasattr(sys.stdout, 'reconfigure'):
25
+ try:
26
+ sys.stdout.reconfigure(encoding='utf-8')
27
+ sys.stderr.reconfigure(encoding='utf-8')
28
+ except Exception:
29
+ pass
30
+
31
+ REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
32
+ REPORTS_DIR = os.path.join(REPO_ROOT, "reports")
33
+
34
+ GREEN = "\033[92m"
35
+ RED = "\033[91m"
36
+ YELLOW = "\033[93m"
37
+ CYAN = "\033[96m"
38
+ BOLD = "\033[1m"
39
+ RESET = "\033[0m"
40
+
41
+
42
+ def run_command(cmd, timeout=300):
43
+ """Executes a shell command and returns (returncode, stdout, stderr, elapsed_ms)."""
44
+ start = time.time()
45
+ try:
46
+ res = subprocess.run(
47
+ cmd,
48
+ shell=True,
49
+ stdout=subprocess.PIPE,
50
+ stderr=subprocess.PIPE,
51
+ text=True,
52
+ timeout=timeout
53
+ )
54
+ elapsed_ms = round((time.time() - start) * 1000, 2)
55
+ return res.returncode, res.stdout, res.stderr, elapsed_ms
56
+ except subprocess.TimeoutExpired:
57
+ elapsed_ms = round((time.time() - start) * 1000, 2)
58
+ return -1, "", "Execution timed out", elapsed_ms
59
+
60
+
61
+ def calculate_flakiness(runs):
62
+ """
63
+ Given a list of boolean statuses [True, False, True, ...],
64
+ calculates pass rate, flip count, and flakiness tier.
65
+ """
66
+ total = len(runs)
67
+ if total == 0:
68
+ return {
69
+ "total": 0,
70
+ "passes": 0,
71
+ "failures": 0,
72
+ "pass_rate": 0.0,
73
+ "flips": 0,
74
+ "status": "NO_DATA",
75
+ "quarantine": False
76
+ }
77
+
78
+ passes = sum(1 for r in runs if r)
79
+ failures = total - passes
80
+ pass_rate = round((passes / total) * 100, 1)
81
+
82
+ # Count transitions (Pass -> Fail or Fail -> Pass)
83
+ flips = 0
84
+ for i in range(len(runs) - 1):
85
+ if runs[i] != runs[i + 1]:
86
+ flips += 1
87
+
88
+ if pass_rate == 100.0:
89
+ status = "STABLE"
90
+ quarantine = False
91
+ elif pass_rate == 0.0:
92
+ status = "DETERMINISTIC_FAIL"
93
+ quarantine = False
94
+ elif pass_rate >= 80.0:
95
+ status = "MILD_FLAKE"
96
+ quarantine = (flips >= 2)
97
+ else:
98
+ status = "SEVERE_FLAKE"
99
+ quarantine = True
100
+
101
+ return {
102
+ "total": total,
103
+ "passes": passes,
104
+ "failures": failures,
105
+ "pass_rate": pass_rate,
106
+ "flips": flips,
107
+ "status": status,
108
+ "quarantine": quarantine
109
+ }
110
+
111
+
112
+ def parse_junit_reports(glob_pattern):
113
+ """Parses JUnit XML report files and aggregates pass/fail counts by test case."""
114
+ test_results = {}
115
+ files = glob.glob(glob_pattern, recursive=True)
116
+ if not files:
117
+ print(f"⚠️ No JUnit XML reports found matching: {glob_pattern}")
118
+ return test_results
119
+
120
+ for f in files:
121
+ try:
122
+ tree = ET.parse(f)
123
+ root = tree.getroot()
124
+ for tc in root.iter("testcase"):
125
+ name = tc.attrib.get("name", "unknown")
126
+ classname = tc.attrib.get("classname", "")
127
+ full_id = f"{classname}#{name}" if classname else name
128
+ has_failure = tc.find("failure") is not None or tc.find("error") is not None
129
+ is_passed = not has_failure
130
+ test_results.setdefault(full_id, []).append(is_passed)
131
+ except Exception as e:
132
+ print(f"⚠️ Could not parse {f}: {e}")
133
+
134
+ return test_results
135
+
136
+
137
+ def benchmark_live_command(cmd, iterations=5):
138
+ """Executes a live test command N times to observe flakiness in real time."""
139
+ print(f"\n{BOLD}{CYAN}======================================================================{RESET}")
140
+ print(f"{BOLD}{CYAN} ENTERPRISE FLAKY TEST SENTINEL (Multi-Run Verification) {RESET}")
141
+ print(f"{BOLD}{CYAN}======================================================================{RESET}")
142
+ print(f" Target Command : {cmd}")
143
+ print(f" Repetition Count : {iterations} consecutive iterations")
144
+ print(f" Timestamp : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
145
+
146
+ runs = []
147
+ latencies = []
148
+ for i in range(1, iterations + 1):
149
+ sys.stdout.write(f" [Run {i}/{iterations}] Executing... ")
150
+ sys.stdout.flush()
151
+ code, stdout, stderr, ms = run_command(cmd)
152
+ is_pass = (code == 0)
153
+ runs.append(is_pass)
154
+ latencies.append(ms)
155
+ status_str = f"{GREEN}PASS{RESET}" if is_pass else f"{RED}FAIL (Exit {code}){RESET}"
156
+ print(f"{status_str} ({ms} ms)")
157
+
158
+ stats = calculate_flakiness(runs)
159
+ stats["latencies_ms"] = latencies
160
+ stats["avg_latency_ms"] = round(sum(latencies) / max(1, len(latencies)), 2)
161
+ stats["command"] = cmd
162
+
163
+ # Print Scorecard
164
+ print(f"\n{BOLD}======================================================================{RESET}")
165
+ print(f"{BOLD} FLAKINESS DIAGNOSTIC SCORECARD {RESET}")
166
+ print(f"{BOLD}======================================================================{RESET}")
167
+ print(f" Pass Rate : {stats['pass_rate']}% ({stats['passes']}/{stats['total']} passes)")
168
+ print(f" Status Flips : {stats['flips']} transitions")
169
+ print(f" Average Runtime : {stats['avg_latency_ms']} ms")
170
+ print(f" Stability Status : {stats['status']}")
171
+
172
+ if stats["quarantine"]:
173
+ print(f" Recommendation : {RED}{BOLD}[QUARANTINE REQUIRED]{RESET} Tag with @quarantine immediately!")
174
+ else:
175
+ print(f" Recommendation : {GREEN}[SUITE APPROVED]{RESET} Test is deterministic.")
176
+ print(f"======================================================================\n")
177
+
178
+ # Persist Report
179
+ os.makedirs(REPORTS_DIR, exist_ok=True)
180
+ report_file = os.path.join(REPORTS_DIR, "quarantine_board.json")
181
+ with open(report_file, "w", encoding="utf-8") as fp:
182
+ json.dump(stats, fp, indent=2)
183
+ print(f"Detailed telemetry saved to: {report_file}")
184
+
185
+ return stats
186
+
187
+
188
+ def main():
189
+ parser = argparse.ArgumentParser(description="Enterprise Flaky Test Detector & Quarantine Sentinel")
190
+ parser.add_argument("--cmd", "-c", default=None, help="Test command to execute repeatedly")
191
+ parser.add_argument("--repeat", "-r", type=int, default=5, help="Number of repetitions (default: 5)")
192
+ parser.add_argument("--reports", default=None, help="Glob pattern for JUnit XML reports to analyze")
193
+ args = parser.parse_args()
194
+
195
+ if args.cmd:
196
+ stats = benchmark_live_command(args.cmd, iterations=args.repeat)
197
+ sys.exit(1 if stats["quarantine"] else 0)
198
+ elif args.reports:
199
+ results = parse_junit_reports(args.reports)
200
+ quarantine_candidates = []
201
+ for test_id, runs in results.items():
202
+ st = calculate_flakiness(runs)
203
+ st["test_id"] = test_id
204
+ if st["quarantine"]:
205
+ quarantine_candidates.append(st)
206
+
207
+ print(f"\nAnalyzed {len(results)} distinct test scenarios from reports.")
208
+ print(f"Quarantine Candidates Detected: {len(quarantine_candidates)}")
209
+ for qc in quarantine_candidates:
210
+ print(f" - {RED}{qc['test_id']}{RESET}: Pass rate {qc['pass_rate']}%, Flips: {qc['flips']}")
211
+
212
+ report_file = os.path.join(REPORTS_DIR, "quarantine_board.json")
213
+ with open(report_file, "w", encoding="utf-8") as fp:
214
+ json.dump(quarantine_candidates, fp, indent=2)
215
+ sys.exit(1 if quarantine_candidates else 0)
216
+ else:
217
+ parser.print_help()
218
+ sys.exit(0)
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Edge-Case Fuzzing & Input Sanitization Payload Catalog
4
+ ------------------------------------------------------------------
5
+ Supplies standardized boundary, resilience, and sanitization payloads
6
+ for transforming functional automated test suites into robust input
7
+ sanitization and vulnerability boundary validators.
8
+
9
+ Categories Covered:
10
+ 1. SQLi Harmless Polyglots (no data destruction, tests parser escaping)
11
+ 2. XSS & Script Injection Polyglots
12
+ 3. Unicode BiDi / RTL Trojan Source & Zero-Width characters
13
+ 4. Multi-byte Emoji Surrogate Pairs & ZWJ Sequences
14
+ 5. Extreme Length Strings (10^4 buffer stress testing)
15
+ 6. Null Byte & Control Character Injections
16
+ 7. Numeric Precision & IEEE-754 Boundary Limits
17
+ """
18
+
19
+ import sys
20
+ import json
21
+ import argparse
22
+ from typing import Dict, Any, List, Optional
23
+
24
+ # Enforce UTF-8 stdout encoding for Windows console compatibility
25
+ if hasattr(sys.stdout, 'reconfigure'):
26
+ try:
27
+ sys.stdout.reconfigure(encoding='utf-8')
28
+ sys.stderr.reconfigure(encoding='utf-8')
29
+ except Exception:
30
+ pass
31
+
32
+
33
+ FUZZ_PAYLOAD_CATALOG: Dict[str, List[Dict[str, Any]]] = {
34
+ "sqli_polyglots": [
35
+ {"name": "Standard Tautology", "payload": "' OR '1'='1' -- ", "expected_status": 400},
36
+ {"name": "Inline Polyglot Comment", "payload": "SLEEP(0)/*'/*\"/*--", "expected_status": 400},
37
+ {"name": "Union Select Null Probe", "payload": "1' UNION SELECT NULL, NULL, NULL -- ", "expected_status": 400},
38
+ {"name": "Batched Query Delay Probe", "payload": "'; WAITFOR DELAY '0:0:0'--", "expected_status": 400},
39
+ {"name": "Admin Comment Bypass", "payload": "admin' -- ", "expected_status": 400}
40
+ ],
41
+ "xss_polyglots": [
42
+ {"name": "Standard Script Tag", "payload": "<script>alert(1)</script>", "expected_status": 400},
43
+ {"name": "Image Tag OnError", "payload": "<img src=x onerror=console.warn('qa_xss')>", "expected_status": 400},
44
+ {"name": "Universal Polyglot Vector", "payload": "javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/\"/+/onmouseover=1+(alert)(1)//'>", "expected_status": 400},
45
+ {"name": "SVG FromCharCode Injection", "payload": "\"><svg/onload=alert(String.fromCharCode(88,83,83))>", "expected_status": 400}
46
+ ],
47
+ "unicode_bidi_rtl": [
48
+ {"name": "Right-to-Left Override (Trojan Source)", "payload": "\u202E\u0041\u0042\u0043_AUTO_TX", "expected_status": 400},
49
+ {"name": "Zero-Width Space & Joiner Injection", "payload": "AUTO\u200B\u200C\u200D_DATA", "expected_status": 400},
50
+ {"name": "Byte Order Mark (BOM) In-String", "payload": "\uFEFFAUTO_USER", "expected_status": 400},
51
+ {"name": "Mixed BiDi Arabic/Latin", "payload": "USER_مرحبا_بالعالم_2026", "expected_status": 200}
52
+ ],
53
+ "emoji_surrogates": [
54
+ {"name": "Complex ZWJ Family Sequence", "payload": "👨‍👩‍👧‍👦_QA_RECORD", "expected_status": 200},
55
+ {"name": "Rainbow Flag Multi-Code", "payload": "🏳️‍🌈_FLAG_TAG", "expected_status": 200},
56
+ {"name": "Unpaired Surrogate Pair Boundary", "payload": "\uD83D\uDE00\uD83D", "expected_status": 400},
57
+ {"name": "4-Byte High Plane UTF-8", "payload": "🔥🚀⚡🧪✨", "expected_status": 200}
58
+ ],
59
+ "extreme_length": [
60
+ {"name": "10^4 (10,000 Chars) Buffer Stress", "payload": "A" * 10000, "expected_status": 400},
61
+ {"name": "1,024 Chars Text Block", "payload": "X" * 1024, "expected_status": 400},
62
+ {"name": "Repetitive Delimiter Stress", "payload": "{\"test\": [" + ",".join(["1"] * 500) + "]}", "expected_status": 400}
63
+ ],
64
+ "null_byte_control": [
65
+ {"name": "Null Byte Extension Truncation", "payload": "document\x00.pdf", "expected_status": 400},
66
+ {"name": "URL Encoded Null Byte", "payload": "test%00.png", "expected_status": 400},
67
+ {"name": "Carriage Return Linefeed Header Split", "payload": "test\r\nSet-Cookie: QA_LEAK=1", "expected_status": 400}
68
+ ],
69
+ "numeric_boundaries": [
70
+ {"name": "32-bit Integer Max Overflow", "payload": 2147483648, "expected_status": 400},
71
+ {"name": "32-bit Integer Min Underflow", "payload": -2147483649, "expected_status": 400},
72
+ {"name": "64-bit BigInt Max Boundary", "payload": 9223372036854775807, "expected_status": 200},
73
+ {"name": "JavaScript MAX_SAFE_INTEGER + 1", "payload": 9007199254740992, "expected_status": 400},
74
+ {"name": "IEEE-754 Extreme Float Overflow", "payload": 1e308, "expected_status": 400},
75
+ {"name": "Negative Zero Float", "payload": -0.0, "expected_status": 200}
76
+ ]
77
+ }
78
+
79
+
80
+ def get_fuzz_payloads(category: Optional[str] = None) -> Dict[str, List[Dict[str, Any]]]:
81
+ """Returns payloads filtered by category or the entire catalog."""
82
+ if category and category.lower() != "all":
83
+ cat_key = category.lower()
84
+ if cat_key in FUZZ_PAYLOAD_CATALOG:
85
+ return {cat_key: FUZZ_PAYLOAD_CATALOG[cat_key]}
86
+ # Fuzzy match
87
+ for k in FUZZ_PAYLOAD_CATALOG:
88
+ if cat_key in k:
89
+ return {k: FUZZ_PAYLOAD_CATALOG[k]}
90
+ return FUZZ_PAYLOAD_CATALOG
91
+
92
+
93
+ def get_field_fuzz_scenarios(field_name: str, field_type: str = "string") -> List[Dict[str, Any]]:
94
+ """
95
+ Generates actionable test scenarios for a given field name and data type.
96
+ """
97
+ scenarios = []
98
+ if field_type == "string":
99
+ # 1. SQLi Polyglot
100
+ scenarios.append({
101
+ "scenario": f"{field_name} SQLi Polyglot Sanitization",
102
+ "category": "SECURITY_SANITY",
103
+ "value": FUZZ_PAYLOAD_CATALOG["sqli_polyglots"][0]["payload"],
104
+ "expected": 400
105
+ })
106
+ # 2. XSS Probe
107
+ scenarios.append({
108
+ "scenario": f"{field_name} XSS Universal Polyglot",
109
+ "category": "SECURITY_SANITY",
110
+ "value": FUZZ_PAYLOAD_CATALOG["xss_polyglots"][1]["payload"],
111
+ "expected": 400
112
+ })
113
+ # 3. BiDi RTL Trojan Source
114
+ scenarios.append({
115
+ "scenario": f"{field_name} Unicode BiDi RTL Override",
116
+ "category": "RESILIENCE_UNICODE",
117
+ "value": FUZZ_PAYLOAD_CATALOG["unicode_bidi_rtl"][0]["payload"],
118
+ "expected": 400
119
+ })
120
+ # 4. Multi-byte Emoji Surrogate
121
+ scenarios.append({
122
+ "scenario": f"{field_name} Emoji Surrogate & ZWJ Sequence",
123
+ "category": "RESILIENCE_UNICODE",
124
+ "value": FUZZ_PAYLOAD_CATALOG["emoji_surrogates"][0]["payload"],
125
+ "expected": 200
126
+ })
127
+ # 5. Extreme Length 10^4
128
+ scenarios.append({
129
+ "scenario": f"{field_name} 10^4 Chars Buffer Stress",
130
+ "category": "RESILIENCE_OVERFLOW",
131
+ "value": "A" * 10000,
132
+ "expected": 400
133
+ })
134
+ # 6. Null Byte Injection
135
+ scenarios.append({
136
+ "scenario": f"{field_name} Null Byte Injection",
137
+ "category": "SECURITY_SANITY",
138
+ "value": "item\x00_test",
139
+ "expected": 400
140
+ })
141
+
142
+ elif field_type in ("integer", "number"):
143
+ for item in FUZZ_PAYLOAD_CATALOG["numeric_boundaries"]:
144
+ scenarios.append({
145
+ "scenario": f"{field_name} {item['name']}",
146
+ "category": "RESILIENCE_NUMERIC",
147
+ "value": item["payload"],
148
+ "expected": item["expected_status"]
149
+ })
150
+
151
+ return scenarios
152
+
153
+
154
+ def main():
155
+ parser = argparse.ArgumentParser(description="Edge-Case Fuzzing & Input Sanitization Payload Generator")
156
+ parser.add_argument("--category", default="all", help="Payload category (sqli, xss, bidi, emoji, length, null, numeric, or all)")
157
+ parser.add_argument("--field", default="", help="Optional field name to generate targeted boundary suite")
158
+ parser.add_argument("--type", default="string", choices=["string", "integer", "number"], help="Field data type")
159
+ parser.add_argument("--json", action="store_true", help="Output results in JSON format")
160
+ args = parser.parse_args()
161
+
162
+ if args.field:
163
+ scenarios = get_field_fuzz_scenarios(args.field, field_type=args.type)
164
+ if args.json:
165
+ print(json.dumps(scenarios, indent=2, ensure_ascii=False))
166
+ else:
167
+ print(f"\n======================================================================")
168
+ print(f" EDGE-CASE FUZZING SCENARIOS FOR FIELD: '{args.field}' ({args.type.upper()})")
169
+ print(f"======================================================================")
170
+ for idx, sc in enumerate(scenarios, start=1):
171
+ val_disp = repr(sc['value']) if len(str(sc['value'])) < 60 else repr(str(sc['value'])[:50] + "...")
172
+ print(f" [{idx:02d}] {sc['scenario']:<45} | Expected: HTTP {sc['expected']} | Value: {val_disp}")
173
+ print()
174
+ return 0
175
+
176
+ catalog = get_fuzz_payloads(args.category)
177
+ if args.json:
178
+ print(json.dumps(catalog, indent=2, ensure_ascii=False))
179
+ else:
180
+ print(f"\n======================================================================")
181
+ print(f" ENTERPRISE EDGE-CASE FUZZING & SANITIZATION CATALOG ")
182
+ print(f"======================================================================")
183
+ total_payloads = sum(len(items) for items in catalog.values())
184
+ print(f"Categories: {len(catalog)} | Total Payloads: {total_payloads}\n")
185
+
186
+ for cat, items in catalog.items():
187
+ print(f"📁 [{cat.upper()}] ({len(items)} vectors):")
188
+ for it in items:
189
+ p_disp = repr(it['payload']) if len(str(it['payload'])) < 60 else repr(str(it['payload'])[:50] + "...")
190
+ print(f" • {it['name']:<35} -> HTTP {it['expected_status']} | {p_disp}")
191
+ print()
192
+
193
+ return 0
194
+
195
+
196
+ if __name__ == "__main__":
197
+ sys.exit(main())
@@ -0,0 +1,30 @@
1
+ Write-Host "==========================================================" -ForegroundColor Cyan
2
+ Write-Host " ENTERPRISE AUTOMATION - ALLURE REPORT GENERATOR" -ForegroundColor Cyan
3
+ Write-Host "==========================================================" -ForegroundColor Cyan
4
+
5
+ $projectDir = Split-Path -Parent $PSScriptRoot
6
+ $resultsDir = Join-Path $projectDir "allure-results"
7
+ $reportDir = Join-Path $projectDir "allure-report"
8
+
9
+ if (-not (Test-Path $resultsDir)) {
10
+ Write-Host "[!] Không tìm thấy thư mục: $resultsDir" -ForegroundColor Yellow
11
+ Write-Host "[!] Hãy thực thi Test Suite trước để sinh dữ liệu kết quả." -ForegroundColor Yellow
12
+ exit 0
13
+ }
14
+
15
+ # Kiểm tra Allure CLI
16
+ $allureCmd = Get-Command allure -ErrorAction SilentlyContinue
17
+
18
+ if ($allureCmd) {
19
+ Write-Host "[+] Đang xuất báo cáo Allure HTML..." -ForegroundColor Green
20
+ allure generate $resultsDir -o $reportDir --clean
21
+ Write-Host "[+] Xuất báo cáo hoàn tất tại: $reportDir" -ForegroundColor Green
22
+ Write-Host "[+] Đang mở báo cáo trên trình duyệt..." -ForegroundColor Cyan
23
+ allure open $reportDir
24
+ } else {
25
+ Write-Host "[!] Không tìm thấy công cụ Allure CLI trong hệ thống." -ForegroundColor Yellow
26
+ Write-Host "[*] Hướng dẫn cài đặt nhanh:" -ForegroundColor White
27
+ Write-Host " Cách 1: npm install -g allure-commandline" -ForegroundColor Gray
28
+ Write-Host " Cách 2: scoop install allure" -ForegroundColor Gray
29
+ Write-Host " Cách 3: choco install allure-commandline" -ForegroundColor Gray
30
+ }
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ tools/generate_api_matrix.py — OpenAPI / Swagger to E2E Testing Matrix Generator
5
+ Generates Positive, Boundary Value, Negative, and Edge-Case Fuzzing scenarios from API schemas.
6
+ Compatible with Windows, macOS, and Linux.
7
+ """
8
+
9
+ import sys
10
+ import os
11
+ import json
12
+ import argparse
13
+
14
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
15
+ if SCRIPT_DIR not in sys.path:
16
+ sys.path.insert(0, SCRIPT_DIR)
17
+
18
+ try:
19
+ from fuzz_payloads import get_field_fuzz_scenarios
20
+ except ImportError:
21
+ get_field_fuzz_scenarios = None
22
+
23
+
24
+ def generate_field_boundaries(field_name, field_schema):
25
+ f_type = field_schema.get("type", "string")
26
+ boundaries = []
27
+
28
+ if f_type == "string":
29
+ min_l = field_schema.get("minLength", 1)
30
+ max_l = field_schema.get("maxLength", 50)
31
+ boundaries.append({"scenario": f"{field_name} Empty String", "value": "", "expected": 400})
32
+ boundaries.append({"scenario": f"{field_name} Below MinLength ({min_l - 1} chars)", "value": "A" * max(0, min_l - 1), "expected": 400 if min_l > 0 else 200})
33
+ boundaries.append({"scenario": f"{field_name} Exact MaxLength ({max_l} chars)", "value": "A" * max_l, "expected": 200})
34
+ boundaries.append({"scenario": f"{field_name} Exceed MaxLength ({max_l + 1} chars)", "value": "A" * (max_l + 1), "expected": 400})
35
+ boundaries.append({"scenario": f"{field_name} Special & XSS Payload", "value": "<script>alert(1)</script>", "expected": 400})
36
+
37
+ # --- Enterprise Edge-Case Fuzzing & Input Sanitization Scenarios ---
38
+ if get_field_fuzz_scenarios:
39
+ fuzz_items = get_field_fuzz_scenarios(field_name, "string")
40
+ for item in fuzz_items:
41
+ boundaries.append({
42
+ "scenario": item["scenario"],
43
+ "value": item["value"],
44
+ "expected": item["expected"]
45
+ })
46
+ else:
47
+ boundaries.append({"scenario": f"{field_name} SQLi Polyglot Probe", "value": "' OR '1'='1' -- ", "expected": 400})
48
+ boundaries.append({"scenario": f"{field_name} Unicode BiDi RTL Override", "value": "\u202E\u0041\u0042\u0043_AUTO_TX", "expected": 400})
49
+ boundaries.append({"scenario": f"{field_name} Emoji Surrogate & ZWJ Sequence", "value": "👨‍👩‍👧‍👦_QA_TAG", "expected": 200})
50
+ boundaries.append({"scenario": f"{field_name} Extreme Length 10^4 Buffer Stress", "value": "A" * 10000, "expected": 400})
51
+ boundaries.append({"scenario": f"{field_name} Null Byte Truncation Injection", "value": "data\x00.json", "expected": 400})
52
+
53
+ elif f_type in ("integer", "number"):
54
+ minimum = field_schema.get("minimum", 0)
55
+ maximum = field_schema.get("maximum", 1000)
56
+ boundaries.append({"scenario": f"{field_name} Negative Value ({minimum - 1})", "value": minimum - 1, "expected": 400})
57
+ boundaries.append({"scenario": f"{field_name} Boundary Minimum ({minimum})", "value": minimum, "expected": 200})
58
+ boundaries.append({"scenario": f"{field_name} Boundary Maximum ({maximum})", "value": maximum, "expected": 200})
59
+ boundaries.append({"scenario": f"{field_name} Exceed Maximum ({maximum + 1})", "value": maximum + 1, "expected": 400})
60
+ boundaries.append({"scenario": f"{field_name} 32-bit Integer Overflow (2^31)", "value": 2147483648, "expected": 400})
61
+
62
+ elif f_type == "boolean":
63
+ boundaries.append({"scenario": f"{field_name} Invalid String Instead of Boolean", "value": "not_a_bool", "expected": 400})
64
+
65
+ return boundaries
66
+
67
+
68
+ def parse_spec(spec_data):
69
+ matrix = []
70
+ paths = spec_data.get("paths", {})
71
+
72
+ for endpoint, methods in paths.items():
73
+ for method, details in methods.items():
74
+ if method.lower() not in ("get", "post", "put", "delete", "patch"):
75
+ continue
76
+
77
+ summary = details.get("summary", details.get("operationId", f"{method.upper()} {endpoint}"))
78
+ req_body = details.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema", {})
79
+ properties = req_body.get("properties", {})
80
+ required_fields = req_body.get("required", [])
81
+
82
+ # 1. Happy Path
83
+ matrix.append({
84
+ "endpoint": endpoint,
85
+ "method": method.upper(),
86
+ "type": "POSITIVE",
87
+ "name": f"TC_API_{method.upper()}_{endpoint.replace('/', '_')}_HappyPath",
88
+ "description": f"Valid payload execution for {summary}",
89
+ "expected_status": 200 if method.lower() == "get" else 201
90
+ })
91
+
92
+ # 2. Missing Required Fields (Negative)
93
+ for req in required_fields:
94
+ matrix.append({
95
+ "endpoint": endpoint,
96
+ "method": method.upper(),
97
+ "type": "NEGATIVE",
98
+ "name": f"TC_API_{method.upper()}_{endpoint.replace('/', '_')}_Missing_{req}",
99
+ "description": f"Submit request omitting required field '{req}'",
100
+ "expected_status": 400
101
+ })
102
+
103
+ # 3. Boundary Values & Edge-Case Fuzzing
104
+ for fname, fschema in properties.items():
105
+ b_list = generate_field_boundaries(fname, fschema)
106
+ for b in b_list:
107
+ clean_scenario = b['scenario'].replace(' ', '_').replace('(', '').replace(')', '').replace('&', 'and')
108
+ val_str = str(b['value'])
109
+ if len(val_str) > 30:
110
+ val_str = val_str[:27] + "..."
111
+ matrix.append({
112
+ "endpoint": endpoint,
113
+ "method": method.upper(),
114
+ "type": "BOUNDARY",
115
+ "name": f"TC_API_{method.upper()}_{endpoint.replace('/', '_')}_{clean_scenario}",
116
+ "description": f"Boundary/Fuzz testing: {b['scenario']} with value '{val_str}'",
117
+ "expected_status": b["expected"]
118
+ })
119
+
120
+ return matrix
121
+
122
+
123
+ def to_markdown(matrix):
124
+ lines = []
125
+ lines.append("# Automated E2E & API Contract Testing Matrix")
126
+ lines.append("")
127
+ lines.append("| ID | Method | Endpoint | Test Type | Scenario Description | Expected Status |")
128
+ lines.append("| :--- | :---: | :--- | :---: | :--- | :---: |")
129
+
130
+ for idx, tc in enumerate(matrix, 1):
131
+ lines.append(f"| TC-API-{idx:03d} | `{tc['method']}` | `{tc['endpoint']}` | **{tc['type']}** | {tc['description']} | `{tc['expected_status']}` |")
132
+
133
+ return "\n".join(lines)
134
+
135
+
136
+ def main():
137
+ parser = argparse.ArgumentParser(description="Generate E2E Positive, Boundary, and Negative test matrix from OpenAPI/Swagger schema.")
138
+ parser.add_argument("--spec", "-s", help="Path to OpenAPI / Swagger JSON schema file.")
139
+ parser.add_argument("--format", "-f", choices=["md", "json"], default="md", help="Output format (markdown or json).")
140
+ parser.add_argument("--output", "-o", help="Output file path.")
141
+ parser.add_argument("--sample", action="store_true", help="Generate a sample matrix using built-in demonstration schema.")
142
+ args = parser.parse_args()
143
+
144
+ if args.sample or not args.spec:
145
+ sample_spec = {
146
+ "paths": {
147
+ "/api/v1/categories": {
148
+ "post": {
149
+ "summary": "Create new category",
150
+ "requestBody": {
151
+ "content": {
152
+ "application/json": {
153
+ "schema": {
154
+ "type": "object",
155
+ "required": ["code", "name"],
156
+ "properties": {
157
+ "code": {"type": "string", "minLength": 3, "maxLength": 20},
158
+ "name": {"type": "string", "minLength": 1, "maxLength": 100},
159
+ "priority": {"type": "integer", "minimum": 1, "maximum": 100}
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ spec_data = sample_spec
170
+ else:
171
+ if not os.path.exists(args.spec):
172
+ print(f"Error: Specification file not found: {args.spec}", file=sys.stderr)
173
+ sys.exit(1)
174
+ with open(args.spec, "r", encoding="utf-8") as fp:
175
+ spec_data = json.load(fp)
176
+
177
+ matrix = parse_spec(spec_data)
178
+
179
+ if args.format == "json":
180
+ result = json.dumps(matrix, indent=2, ensure_ascii=False)
181
+ else:
182
+ result = to_markdown(matrix)
183
+
184
+ if args.output:
185
+ with open(args.output, "w", encoding="utf-8") as fp:
186
+ fp.write(result)
187
+ print(f"✅ Matrix successfully generated to: {args.output} ({len(matrix)} test cases)")
188
+ else:
189
+ print(result)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ main()