robotmk-bridge-testdatagenerator 0.3.0__tar.gz

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.
@@ -0,0 +1,16 @@
1
+ dist/
2
+ build/
3
+ *.egg-info/
4
+ __pycache__/
5
+ *.py[cod]
6
+ .venv/
7
+ .env
8
+ *.whl
9
+ *.tar.gz
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .claude
14
+ _bmad-output
15
+ docs
16
+ _bmad
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## [0.3.0](https://github.com/elabit/robotmk-bridge-testdatagenerator/compare/v0.2.0...v0.3.0) (2026-06-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * init commit, pypi init ([1d278e6](https://github.com/elabit/robotmk-bridge-testdatagenerator/commit/1d278e64b068e6b17a922cb3fe85ad330971e41c))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * deleted publish workflow ([a046128](https://github.com/elabit/robotmk-bridge-testdatagenerator/commit/a046128ed5e7f9fcc2c271b89676f35ee37ca45e))
14
+
15
+ ## [0.2.0](https://github.com/elabit/robotmk-bridge-testdatagenerator/compare/v0.1.0...v0.2.0) (2026-06-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * init commit, pypi init ([1d278e6](https://github.com/elabit/robotmk-bridge-testdatagenerator/commit/1d278e64b068e6b17a922cb3fe85ad330971e41c))
21
+
22
+ ## Changelog
23
+
24
+ All notable changes to this project will be documented in this file.
25
+
26
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
27
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
28
+
29
+ Changelog entries are managed automatically by [release-please](https://github.com/googleapis/release-please).
30
+
31
+ <!-- Release notes are appended above this line by release-please -->
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Meggle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotmk-bridge-testdatagenerator
3
+ Version: 0.3.0
4
+ Summary: Generates synthetic test result files (JUnit, ZAP, Gatling) for robotmk-bridge-plugin testing
5
+ Project-URL: Homepage, https://github.com/simonmeggle/robotmk-bridge-testdatagenerator
6
+ Project-URL: Issues, https://github.com/simonmeggle/robotmk-bridge-testdatagenerator/issues
7
+ Author-email: Simon Meggle <simon.meggle@checkmk.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: checkmk,gatling,junit,robotmk,test-data,testing,zap
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: pyyaml>=5.4
22
+ Description-Content-Type: text/markdown
23
+
24
+ # robotmk-bridge-testdatagenerator
25
+
26
+ Generates synthetic test result files for [robotmk-bridge-plugin](https://github.com/simonmeggle/robotmk-bridge-plugin) testing. Produces realistic, randomized output in the formats consumed by the bridge plugin's handlers.
27
+
28
+ ## Supported Formats
29
+
30
+ | Handler | Format | Extension |
31
+ |---|---|---|
32
+ | `junit` | JUnit XML | `.xml` |
33
+ | `zaproxy` | OWASP ZAP XML v2.7.0 | `.xml` |
34
+ | `gatling` | Gatling simulation.log v2.0 | `.log` |
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install robotmk-bridge-testdatagenerator
40
+ ```
41
+
42
+ ## CLI Usage
43
+
44
+ ```bash
45
+ # Generate all handlers (output: tests/e2e/data/)
46
+ rmkb-testgen
47
+
48
+ # Specify output directory and status
49
+ rmkb-testgen --output-dir /tmp/test-data --status failed
50
+
51
+ # Generate specific handlers only
52
+ rmkb-testgen --handlers junit gatling
53
+
54
+ # Continuous mode (Ctrl+C to stop)
55
+ rmkb-testgen --continuous --interval 5
56
+
57
+ # List available handlers
58
+ rmkb-testgen --list
59
+ ```
60
+
61
+ ### Options
62
+
63
+ | Flag | Short | Default | Description |
64
+ |---|---|---|---|
65
+ | `--output-dir` | `-o` | `tests/e2e/data` | Output directory |
66
+ | `--status` | `-s` | `passed` | `passed` / `failed` / `mixed` |
67
+ | `--handlers` | `-H` | all | Specific handlers to generate |
68
+ | `--pattern` | `-p` | `{handler}.{ext}` | Filename pattern |
69
+ | `--continuous` | `-c` | off | Regenerate on interval |
70
+ | `--interval` | `-i` | `5.0` | Seconds between generations |
71
+ | `--list` | `-l` | — | List handlers and exit |
72
+ | `--verbose` | `-v` | off | Verbose output |
73
+
74
+ ## Library Usage
75
+
76
+ ```python
77
+ from robotmk_bridge_testdatagenerator import (
78
+ generate_all_handler_files,
79
+ generate_handler_file,
80
+ get_supported_handlers,
81
+ )
82
+ from pathlib import Path
83
+
84
+ # Generate all handlers
85
+ files = generate_all_handler_files(Path("/tmp/test-data"), test_status="mixed")
86
+
87
+ # Generate a single handler
88
+ generate_handler_file("junit", Path("/tmp/result.xml"), test_status="failed")
89
+
90
+ # List handlers
91
+ handlers = get_supported_handlers() # ['junit', 'gatling', 'zaproxy']
92
+ ```
93
+
94
+ ## Custom handlers.yaml
95
+
96
+ By default the bundled `handlers.yaml` is used. Override via environment variable:
97
+
98
+ ```bash
99
+ ROBOTMK_HANDLERS_YAML=/path/to/your/handlers.yaml rmkb-testgen
100
+ ```
101
+
102
+ ## Test Status Semantics
103
+
104
+ | Status | JUnit | ZAP | Gatling |
105
+ |---|---|---|---|
106
+ | `passed` | All pass | Low-risk alerts only | All requests OK |
107
+ | `failed` | All fail | High-risk alerts | All requests KO |
108
+ | `mixed` | Every 3rd fails | Low + medium risk | Every 4th KO |
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,89 @@
1
+ # robotmk-bridge-testdatagenerator
2
+
3
+ Generates synthetic test result files for [robotmk-bridge-plugin](https://github.com/simonmeggle/robotmk-bridge-plugin) testing. Produces realistic, randomized output in the formats consumed by the bridge plugin's handlers.
4
+
5
+ ## Supported Formats
6
+
7
+ | Handler | Format | Extension |
8
+ |---|---|---|
9
+ | `junit` | JUnit XML | `.xml` |
10
+ | `zaproxy` | OWASP ZAP XML v2.7.0 | `.xml` |
11
+ | `gatling` | Gatling simulation.log v2.0 | `.log` |
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install robotmk-bridge-testdatagenerator
17
+ ```
18
+
19
+ ## CLI Usage
20
+
21
+ ```bash
22
+ # Generate all handlers (output: tests/e2e/data/)
23
+ rmkb-testgen
24
+
25
+ # Specify output directory and status
26
+ rmkb-testgen --output-dir /tmp/test-data --status failed
27
+
28
+ # Generate specific handlers only
29
+ rmkb-testgen --handlers junit gatling
30
+
31
+ # Continuous mode (Ctrl+C to stop)
32
+ rmkb-testgen --continuous --interval 5
33
+
34
+ # List available handlers
35
+ rmkb-testgen --list
36
+ ```
37
+
38
+ ### Options
39
+
40
+ | Flag | Short | Default | Description |
41
+ |---|---|---|---|
42
+ | `--output-dir` | `-o` | `tests/e2e/data` | Output directory |
43
+ | `--status` | `-s` | `passed` | `passed` / `failed` / `mixed` |
44
+ | `--handlers` | `-H` | all | Specific handlers to generate |
45
+ | `--pattern` | `-p` | `{handler}.{ext}` | Filename pattern |
46
+ | `--continuous` | `-c` | off | Regenerate on interval |
47
+ | `--interval` | `-i` | `5.0` | Seconds between generations |
48
+ | `--list` | `-l` | — | List handlers and exit |
49
+ | `--verbose` | `-v` | off | Verbose output |
50
+
51
+ ## Library Usage
52
+
53
+ ```python
54
+ from robotmk_bridge_testdatagenerator import (
55
+ generate_all_handler_files,
56
+ generate_handler_file,
57
+ get_supported_handlers,
58
+ )
59
+ from pathlib import Path
60
+
61
+ # Generate all handlers
62
+ files = generate_all_handler_files(Path("/tmp/test-data"), test_status="mixed")
63
+
64
+ # Generate a single handler
65
+ generate_handler_file("junit", Path("/tmp/result.xml"), test_status="failed")
66
+
67
+ # List handlers
68
+ handlers = get_supported_handlers() # ['junit', 'gatling', 'zaproxy']
69
+ ```
70
+
71
+ ## Custom handlers.yaml
72
+
73
+ By default the bundled `handlers.yaml` is used. Override via environment variable:
74
+
75
+ ```bash
76
+ ROBOTMK_HANDLERS_YAML=/path/to/your/handlers.yaml rmkb-testgen
77
+ ```
78
+
79
+ ## Test Status Semantics
80
+
81
+ | Status | JUnit | ZAP | Gatling |
82
+ |---|---|---|---|
83
+ | `passed` | All pass | Low-risk alerts only | All requests OK |
84
+ | `failed` | All fail | High-risk alerts | All requests KO |
85
+ | `mixed` | Every 3rd fails | Low + medium risk | Every 4th KO |
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "robotmk-bridge-testdatagenerator"
7
+ version = "0.3.0"
8
+ description = "Generates synthetic test result files (JUnit, ZAP, Gatling) for robotmk-bridge-plugin testing"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Simon Meggle", email = "simon.meggle@checkmk.com" }]
13
+ keywords = ["robotmk", "checkmk", "testing", "junit", "gatling", "zap", "test-data"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Testing",
24
+ ]
25
+ dependencies = ["pyyaml>=5.4"]
26
+
27
+ [project.scripts]
28
+ rmkb-testgen = "robotmk_bridge_testdatagenerator.__main__:main"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/simonmeggle/robotmk-bridge-testdatagenerator"
32
+ Issues = "https://github.com/simonmeggle/robotmk-bridge-testdatagenerator/issues"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/robotmk_bridge_testdatagenerator"]
36
+
37
+ [tool.hatch.build.targets.wheel.shared-data]
38
+ # handlers.yaml is included as package data, not shared-data
39
+ # (it's inside the package directory, hatchling includes it automatically)
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ include = [
43
+ "src/",
44
+ "README.md",
45
+ "CHANGELOG.md",
46
+ "LICENSE",
47
+ ]
@@ -0,0 +1,17 @@
1
+ """Test data generator for robotmk-bridge-plugin.
2
+
3
+ Generates realistic test result files for different handler types
4
+ (JUnit, ZAP, Gatling). Used by both e2e tests and unit test fixtures.
5
+ """
6
+
7
+ from .generator import (
8
+ generate_all_handler_files,
9
+ generate_handler_file,
10
+ get_supported_handlers,
11
+ )
12
+
13
+ __all__ = [
14
+ "generate_all_handler_files",
15
+ "generate_handler_file",
16
+ "get_supported_handlers",
17
+ ]
@@ -0,0 +1,215 @@
1
+ """CLI entry point for the test data generator.
2
+
3
+ Usage:
4
+ rmkb-testgen [options]
5
+ python -m robotmk_bridge_testdatagenerator [options]
6
+
7
+ Examples:
8
+ # Generate all handler test files in default location
9
+ rmkb-testgen
10
+
11
+ # Generate in a specific directory
12
+ rmkb-testgen --output-dir /tmp/test-data
13
+
14
+ # Generate with failed test status
15
+ rmkb-testgen --status failed
16
+
17
+ # Generate only specific handlers
18
+ rmkb-testgen --handlers junit gatling
19
+
20
+ # Continuous mode: regenerate every 5 seconds
21
+ rmkb-testgen --continuous --interval 5
22
+
23
+ # Use a custom handlers.yaml
24
+ ROBOTMK_HANDLERS_YAML=/path/to/handlers.yaml rmkb-testgen
25
+ """
26
+
27
+ import argparse
28
+ import signal
29
+ import sys
30
+ import time
31
+ from pathlib import Path
32
+
33
+ from .generator import (
34
+ generate_all_handler_files,
35
+ generate_handler_file,
36
+ get_supported_handlers,
37
+ )
38
+
39
+ _shutdown_requested = False
40
+
41
+
42
+ def signal_handler(signum, frame):
43
+ global _shutdown_requested
44
+ _shutdown_requested = True
45
+ print("\n\nShutdown requested. Stopping after current generation...")
46
+
47
+
48
+ def generate_files(args):
49
+ """Generate test data files based on arguments."""
50
+ if args.handlers:
51
+ generated_files = {}
52
+ for handler in args.handlers:
53
+ from .generator import load_handlers_registry
54
+ handlers_def = load_handlers_registry()
55
+ handler_def = next(
56
+ (h for h in handlers_def if h["name"] == handler), None
57
+ )
58
+ if not handler_def:
59
+ print(f"Error: Handler '{handler}' not found", file=sys.stderr)
60
+ sys.exit(1)
61
+
62
+ result_ext = handler_def.get("result_ext", "txt")
63
+ filename = args.pattern.format(handler=handler, ext=result_ext)
64
+ output_path = args.output_dir / filename
65
+ generate_handler_file(
66
+ handler_name=handler,
67
+ output_path=output_path,
68
+ test_status=args.status,
69
+ )
70
+ generated_files[handler] = output_path
71
+ else:
72
+ generated_files = generate_all_handler_files(
73
+ output_dir=args.output_dir,
74
+ test_status=args.status,
75
+ filename_pattern=args.pattern,
76
+ )
77
+ return generated_files
78
+
79
+
80
+ def main():
81
+ """CLI entry point for test data generation."""
82
+ parser = argparse.ArgumentParser(
83
+ description="Generate test result files for Robotmk Bridge handlers",
84
+ formatter_class=argparse.RawDescriptionHelpFormatter,
85
+ epilog="""
86
+ Examples:
87
+ Generate all handlers:
88
+ rmkb-testgen
89
+
90
+ Specific output directory:
91
+ rmkb-testgen --output-dir /tmp/test-data
92
+
93
+ Failed status:
94
+ rmkb-testgen --status failed
95
+
96
+ Specific handlers:
97
+ rmkb-testgen --handlers junit gatling
98
+
99
+ Continuous mode (Ctrl+C to stop):
100
+ rmkb-testgen --continuous --interval 5
101
+
102
+ Custom handlers.yaml:
103
+ ROBOTMK_HANDLERS_YAML=/path/to/handlers.yaml rmkb-testgen
104
+ """,
105
+ )
106
+
107
+ parser.add_argument(
108
+ "-o", "--output-dir",
109
+ type=Path,
110
+ default=Path("tests/e2e/data"),
111
+ help="Output directory for generated test files (default: tests/e2e/data)",
112
+ )
113
+ parser.add_argument(
114
+ "-s", "--status",
115
+ choices=["passed", "failed", "mixed"],
116
+ default="passed",
117
+ help="Test status for generated files (default: passed)",
118
+ )
119
+ parser.add_argument(
120
+ "-H", "--handlers",
121
+ nargs="+",
122
+ choices=get_supported_handlers(),
123
+ help="Generate only specific handlers (default: all)",
124
+ )
125
+ parser.add_argument(
126
+ "-p", "--pattern",
127
+ default="{handler}.{ext}",
128
+ help="Filename pattern (default: {handler}.{ext})",
129
+ )
130
+ parser.add_argument(
131
+ "-c", "--continuous",
132
+ action="store_true",
133
+ help="Continuous mode: regenerate files at regular intervals",
134
+ )
135
+ parser.add_argument(
136
+ "-i", "--interval",
137
+ type=float,
138
+ default=5.0,
139
+ help="Interval in seconds between generations in continuous mode (default: 5)",
140
+ )
141
+ parser.add_argument(
142
+ "-l", "--list",
143
+ action="store_true",
144
+ help="List supported handlers and exit",
145
+ )
146
+ parser.add_argument(
147
+ "-v", "--verbose",
148
+ action="store_true",
149
+ help="Enable verbose output",
150
+ )
151
+
152
+ args = parser.parse_args()
153
+
154
+ if args.list:
155
+ print("Supported handlers:")
156
+ for handler in get_supported_handlers():
157
+ print(f" - {handler}")
158
+ return 0
159
+
160
+ if args.interval <= 0:
161
+ print("Error: Interval must be greater than 0", file=sys.stderr)
162
+ return 1
163
+
164
+ args.output_dir.mkdir(parents=True, exist_ok=True)
165
+ signal.signal(signal.SIGINT, signal_handler)
166
+ signal.signal(signal.SIGTERM, signal_handler)
167
+
168
+ if args.verbose:
169
+ print(f"Output directory: {args.output_dir.absolute()}")
170
+ print(f"Test status: {args.status}")
171
+ print(f"Filename pattern: {args.pattern}")
172
+ if args.continuous:
173
+ print(f"Continuous mode: regenerating every {args.interval}s")
174
+ print("Press Ctrl+C to stop\n")
175
+
176
+ try:
177
+ if args.continuous:
178
+ generation_count = 0
179
+ while not _shutdown_requested:
180
+ generation_count += 1
181
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
182
+ if args.verbose:
183
+ print(f"[{timestamp}] Generation #{generation_count}")
184
+ generated_files = generate_files(args)
185
+ if generation_count == 1 or args.verbose:
186
+ print(f"Generated {len(generated_files)} test file(s):")
187
+ for handler, path in generated_files.items():
188
+ size = path.stat().st_size
189
+ print(f" ✓ {handler:12s} → {path.name:20s} ({size:,} bytes)")
190
+ else:
191
+ print(f"[{timestamp}] Generation #{generation_count}: "
192
+ f"{len(generated_files)} files updated")
193
+ if not _shutdown_requested:
194
+ if args.verbose:
195
+ print(f"Waiting {args.interval}s until next generation...\n")
196
+ time.sleep(args.interval)
197
+ print(f"\nStopped after {generation_count} generation(s)")
198
+ else:
199
+ generated_files = generate_files(args)
200
+ print(f"Generated {len(generated_files)} test file(s):")
201
+ for handler, path in generated_files.items():
202
+ size = path.stat().st_size
203
+ print(f" ✓ {handler:12s} → {path.name:20s} ({size:,} bytes)")
204
+ return 0
205
+
206
+ except Exception as e:
207
+ print(f"Error: {e}", file=sys.stderr)
208
+ if args.verbose:
209
+ import traceback
210
+ traceback.print_exc()
211
+ return 1
212
+
213
+
214
+ if __name__ == "__main__":
215
+ sys.exit(main())
@@ -0,0 +1,167 @@
1
+ """Core test data generator module.
2
+
3
+ Reads the bundled handlers.yaml (or ROBOTMK_HANDLERS_YAML env override) and
4
+ provides a unified API for generating test result files for different handler types.
5
+ """
6
+
7
+ import os
8
+ from importlib import resources
9
+ from pathlib import Path
10
+ from typing import Dict, List
11
+
12
+ import yaml
13
+
14
+
15
+ def _open_handlers_yaml():
16
+ """Return an open file-like object for handlers.yaml.
17
+
18
+ Checks ROBOTMK_HANDLERS_YAML env var first; falls back to the file
19
+ bundled inside the package via importlib.resources.
20
+ """
21
+ env_override = os.environ.get("ROBOTMK_HANDLERS_YAML")
22
+ if env_override:
23
+ return open(env_override, "r", encoding="utf-8")
24
+ ref = resources.files("robotmk_bridge_testdatagenerator").joinpath("handlers.yaml")
25
+ return ref.open("r", encoding="utf-8")
26
+
27
+
28
+ def load_handlers_registry() -> List[Dict]:
29
+ """Load handler definitions from handlers.yaml.
30
+
31
+ Returns:
32
+ List of handler definitions, each with 'name', 'title', 'class_import',
33
+ 'result_ext', and optional 'handler_params'.
34
+ """
35
+ with _open_handlers_yaml() as f:
36
+ data = yaml.safe_load(f)
37
+
38
+ if not data or "handlers" not in data:
39
+ raise ValueError("handlers.yaml must contain a 'handlers' key")
40
+
41
+ return data["handlers"]
42
+
43
+
44
+ def get_supported_handlers() -> List[str]:
45
+ """Return list of handler names from handlers.yaml.
46
+
47
+ Returns:
48
+ List of handler names (e.g., ['junit', 'gatling', 'zaproxy'])
49
+ """
50
+ return [h["name"] for h in load_handlers_registry()]
51
+
52
+
53
+ def generate_handler_file(
54
+ handler_name: str,
55
+ output_path: Path,
56
+ test_status: str = "passed",
57
+ **kwargs
58
+ ) -> Path:
59
+ """Generate a test result file for the specified handler.
60
+
61
+ Args:
62
+ handler_name: Name of handler (e.g., 'junit', 'zaproxy', 'gatling')
63
+ output_path: Path where the result file should be written
64
+ test_status: Test outcome - 'passed', 'failed', or 'mixed' (default: 'passed')
65
+ **kwargs: Handler-specific parameters (e.g., num_tests, duration_ms)
66
+
67
+ Returns:
68
+ Path to the generated file
69
+
70
+ Raises:
71
+ ValueError: If handler_name is not supported
72
+ RuntimeError: If handler generator fails
73
+ """
74
+ handlers = load_handlers_registry()
75
+ handler_def = next((h for h in handlers if h["name"] == handler_name), None)
76
+
77
+ if not handler_def:
78
+ supported = [h["name"] for h in handlers]
79
+ raise ValueError(
80
+ f"Handler '{handler_name}' not found in handlers.yaml. "
81
+ f"Supported handlers: {', '.join(supported)}"
82
+ )
83
+
84
+ handler_to_module = {
85
+ "zaproxy": "zap",
86
+ "junit": "junit",
87
+ "gatling": "gatling",
88
+ }
89
+
90
+ module_name = handler_to_module.get(handler_name, handler_name)
91
+
92
+ try:
93
+ from . import handlers as handler_module
94
+ generator_name = f"{module_name}_generator"
95
+ generator = getattr(handler_module, generator_name)
96
+ except (ImportError, AttributeError) as e:
97
+ raise RuntimeError(
98
+ f"Could not import generator for '{handler_name}'. "
99
+ f"Expected module: robotmk_bridge_testdatagenerator.handlers.{generator_name}"
100
+ ) from e
101
+
102
+ try:
103
+ generator.generate(
104
+ output_path=output_path,
105
+ test_status=test_status,
106
+ handler_def=handler_def,
107
+ **kwargs
108
+ )
109
+ except Exception as e:
110
+ raise RuntimeError(
111
+ f"Failed to generate test data for handler '{handler_name}': {e}"
112
+ ) from e
113
+
114
+ try:
115
+ if output_path.exists():
116
+ output_path.chmod(0o644)
117
+ except Exception:
118
+ pass
119
+
120
+ return output_path
121
+
122
+
123
+ def generate_all_handler_files(
124
+ output_dir: Path,
125
+ test_status: str = "passed",
126
+ filename_pattern: str = "{handler}.{ext}"
127
+ ) -> Dict[str, Path]:
128
+ """Generate test result files for all supported handlers.
129
+
130
+ Args:
131
+ output_dir: Directory where files should be written
132
+ test_status: Test outcome - 'passed', 'failed', or 'mixed'
133
+ filename_pattern: Pattern for output filenames (variables: {handler}, {ext})
134
+
135
+ Returns:
136
+ Dict mapping handler name to generated file path
137
+
138
+ Example:
139
+ >>> files = generate_all_handler_files(Path("tests/e2e/data"))
140
+ >>> files
141
+ {'junit': Path('tests/e2e/data/junit.xml'),
142
+ 'gatling': Path('tests/e2e/data/gatling.log'),
143
+ 'zaproxy': Path('tests/e2e/data/zaproxy.xml')}
144
+ """
145
+ output_dir = Path(output_dir)
146
+ output_dir.mkdir(parents=True, exist_ok=True)
147
+ try:
148
+ output_dir.chmod(0o755)
149
+ except Exception:
150
+ pass
151
+
152
+ handlers = load_handlers_registry()
153
+ generated_files = {}
154
+
155
+ for handler_def in handlers:
156
+ handler_name = handler_def["name"]
157
+ result_ext = handler_def.get("result_ext", "txt")
158
+ filename = filename_pattern.format(handler=handler_name, ext=result_ext)
159
+ output_path = output_dir / filename
160
+ generate_handler_file(
161
+ handler_name=handler_name,
162
+ output_path=output_path,
163
+ test_status=test_status
164
+ )
165
+ generated_files[handler_name] = output_path
166
+
167
+ return generated_files
@@ -0,0 +1,11 @@
1
+ """Handler-specific test data generators."""
2
+
3
+ from . import junit_generator
4
+ from . import gatling_generator
5
+ from . import zap_generator
6
+
7
+ __all__ = [
8
+ "junit_generator",
9
+ "gatling_generator",
10
+ "zap_generator",
11
+ ]
@@ -0,0 +1,103 @@
1
+ """Gatling simulation log test data generator."""
2
+
3
+ import random
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Dict
7
+
8
+
9
+ def generate(
10
+ output_path: Path,
11
+ test_status: str = "passed",
12
+ handler_def: Dict = None,
13
+ num_requests: int = 10,
14
+ duration_ms: int = 5000,
15
+ **kwargs
16
+ ) -> None:
17
+ """Generate a Gatling simulation.log file in v2.0 format.
18
+
19
+ Gatling v2.x logs use tab-separated format with different record types:
20
+ - RUN: simulation metadata (v2.0 format with FQCN)
21
+ - REQUEST: individual HTTP request with timing (no simulation name)
22
+ - USER: user session start/end (group name + numeric ID)
23
+
24
+ Args:
25
+ output_path: Path where the log file should be written
26
+ test_status: 'passed', 'failed', or 'mixed'
27
+ handler_def: Handler definition from handlers.yaml (unused here)
28
+ num_requests: Number of requests to simulate
29
+ duration_ms: Total simulation duration in milliseconds
30
+ **kwargs: Additional parameters (ignored)
31
+ """
32
+ base_timestamp = int(time.time() * 1000) # Current time in milliseconds
33
+ simulation_fqcn = "computerdatabase.advanced.AdvancedSimulationStep05"
34
+ run_id = "simulation-001"
35
+ normalized_name = "advancedsimulationstep05"
36
+ user_count = max(1, num_requests // 3)
37
+
38
+ # Define user groups (mix of regular users and admins)
39
+ user_groups = ["Users", "Users", "Admins"] # More regular users than admins
40
+
41
+ lines = []
42
+
43
+ # RUN record - simulation start (v2.0 format)
44
+ # Format: RUN\tsimulation_fqcn\trun_id\tnormalized_name\ttimestamp\t \tversion
45
+ lines.append(f"RUN\t{simulation_fqcn}\t{run_id}\t{normalized_name}\t{base_timestamp}\t \t2.0")
46
+
47
+ # USER records - user sessions start
48
+ user_sessions = [] # Track (group, id) tuples
49
+ for user_id in range(user_count):
50
+ numeric_id = user_id + 1
51
+ user_group = user_groups[user_id % len(user_groups)]
52
+ start_time = base_timestamp + (user_id * duration_ms // user_count)
53
+ user_sessions.append((user_group, numeric_id, start_time))
54
+ lines.append(f"USER\t{user_group}\t{numeric_id}\tSTART\t{start_time}\t{start_time}")
55
+
56
+ # Generate varied request names
57
+ request_types = ["Home", "Home Redirect 1", "Search", "Select", "Page 0", "Page 1",
58
+ "Page 2", "Page 3", "Form", "Post", "Post Redirect 1"]
59
+
60
+ # REQUEST records
61
+ request_interval = duration_ms // num_requests if num_requests > 0 else 100
62
+
63
+ for i in range(num_requests):
64
+ user_idx = i % user_count
65
+ user_group, user_id, _ = user_sessions[user_idx]
66
+ request_name = request_types[i % len(request_types)]
67
+
68
+ request_start = base_timestamp + (i * request_interval)
69
+ # Add variance: +/- 40% of average duration
70
+ avg_duration = request_interval // 2
71
+ request_duration = int(avg_duration * random.uniform(0.6, 1.4))
72
+ request_end = request_start + request_duration
73
+
74
+ # Determine if this request should fail
75
+ should_fail = False
76
+ if test_status == "failed":
77
+ should_fail = True
78
+ elif test_status == "mixed" and i % 4 == 0:
79
+ should_fail = True
80
+
81
+ if should_fail:
82
+ status = "KO"
83
+ message = "status.find.is(201), but actually found 200"
84
+ else:
85
+ status = "OK"
86
+ message = " " # Single space for OK status
87
+
88
+ # REQUEST format (v2.0): REQUEST\tuser_group\tuser_id\t\trequest_name\tstart\tend\tstatus\tmessage
89
+ # Note: Empty field between user_id and request_name (double tab)
90
+ lines.append(
91
+ f"REQUEST\t{user_group}\t{user_id}\t\t{request_name}\t"
92
+ f"{request_start}\t{request_end}\t{status}\t{message}"
93
+ )
94
+
95
+ # USER records - user sessions end
96
+ end_time = base_timestamp + duration_ms
97
+ for user_group, user_id, start_time in user_sessions:
98
+ lines.append(f"USER\t{user_group}\t{user_id}\tEND\t{start_time}\t{end_time}")
99
+
100
+ # Write to file
101
+ with open(output_path, "w", encoding="utf-8") as f:
102
+ f.write("\n".join(lines))
103
+ f.write("\n") # Trailing newline
@@ -0,0 +1,84 @@
1
+ """JUnit XML test data generator."""
2
+
3
+ import random
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from xml.etree import ElementTree as ET
7
+ from typing import Dict
8
+
9
+
10
+ def generate(
11
+ output_path: Path,
12
+ test_status: str = "passed",
13
+ handler_def: Dict = None,
14
+ num_tests: int = 5,
15
+ duration_s: float = 2.5,
16
+ **kwargs
17
+ ) -> None:
18
+ """Generate a JUnit XML result file.
19
+
20
+ Args:
21
+ output_path: Path where the XML file should be written
22
+ test_status: 'passed', 'failed', or 'mixed'
23
+ handler_def: Handler definition from handlers.yaml (unused here)
24
+ num_tests: Number of test cases to generate
25
+ duration_s: Total duration in seconds
26
+ **kwargs: Additional parameters (ignored)
27
+ """
28
+ # Create root testsuite element
29
+ testsuite = ET.Element("testsuite")
30
+ testsuite.set("name", "RobotmkBridgeTests")
31
+ testsuite.set("tests", str(num_tests))
32
+ testsuite.set("time", f"{duration_s:.3f}")
33
+ testsuite.set("timestamp", datetime.now().strftime("%Y-%m-%dT%H:%M:%S"))
34
+
35
+ failures = 0
36
+ errors = 0
37
+
38
+ # Generate test cases based on test_status
39
+ for i in range(num_tests):
40
+ testcase = ET.SubElement(testsuite, "testcase")
41
+ testcase.set("classname", f"tests.example.TestSuite{i // 3 + 1}")
42
+ testcase.set("name", f"test_example_{i + 1}")
43
+ # Add variance: +/- 30% of average time
44
+ avg_time = duration_s / num_tests
45
+ test_time = avg_time * random.uniform(0.7, 1.3)
46
+ testcase.set("time", f"{test_time:.3f}")
47
+
48
+ # Determine if this test should fail
49
+ should_fail = False
50
+ if test_status == "failed":
51
+ should_fail = True
52
+ elif test_status == "mixed" and i % 3 == 0:
53
+ should_fail = True
54
+
55
+ if should_fail:
56
+ if i % 2 == 0:
57
+ # Add failure
58
+ failure = ET.SubElement(testcase, "failure")
59
+ failure.set("message", f"Assertion failed: Expected value was incorrect")
60
+ failure.set("type", "AssertionError")
61
+ failure.text = f"AssertionError: Expected 'success' but got 'failure' at line {i * 10 + 42}"
62
+ failures += 1
63
+ else:
64
+ # Add error
65
+ error = ET.SubElement(testcase, "error")
66
+ error.set("message", f"Test runtime error")
67
+ error.set("type", "RuntimeError")
68
+ error.text = f"RuntimeError: Connection timeout after 30 seconds"
69
+ errors += 1
70
+ else:
71
+ # Optionally add system-out for passed tests
72
+ if i % 2 == 0:
73
+ system_out = ET.SubElement(testcase, "system-out")
74
+ system_out.text = f"Test {i + 1} executed successfully with no warnings"
75
+
76
+ # Update testsuite attributes
77
+ testsuite.set("failures", str(failures))
78
+ testsuite.set("errors", str(errors))
79
+ testsuite.set("skipped", "0")
80
+
81
+ # Write XML to file
82
+ tree = ET.ElementTree(testsuite)
83
+ ET.indent(tree, space=" ")
84
+ tree.write(output_path, encoding="utf-8", xml_declaration=True)
@@ -0,0 +1,256 @@
1
+ """OWASP ZAP XML test data generator."""
2
+
3
+ import random
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict
7
+
8
+
9
+ def generate(
10
+ output_path: Path,
11
+ test_status: str = "passed",
12
+ handler_def: Dict = None,
13
+ num_sites: int = 3,
14
+ **kwargs
15
+ ) -> None:
16
+ """Generate an OWASP ZAP XML report file matching v2.7.0 format.
17
+
18
+ ZAP reports contain alerts with risk levels (0=Info, 1=Low, 2=Medium, 3=High)
19
+ and confidence levels (0=Low, 1=Medium, 2=High, 3=Confirmed).
20
+
21
+ Args:
22
+ output_path: Path where the XML file should be written
23
+ test_status: 'passed' (low risk only), 'failed' (high risk), 'mixed' (varied)
24
+ handler_def: Handler definition from handlers.yaml (unused here)
25
+ num_sites: Number of sites to include in report
26
+ **kwargs: Additional parameters (ignored)
27
+ """
28
+ # Generate timestamp in ZAP format: "Tue, 7 Aug 2018 13:17:56"
29
+ now = datetime.now()
30
+ timestamp = now.strftime("%a, %-d %b %Y %H:%M:%S")
31
+
32
+ lines = ['<?xml version="1.0"?>']
33
+ lines.append(f'<OWASPZAPReport version="2.7.0" generated="{timestamp}">')
34
+
35
+ # Define sites to scan with varied number
36
+ sites = [
37
+ {"name": "http://localhost:7272", "host": "localhost", "port": "7272", "ssl": "false"},
38
+ {"name": "http://127.0.0.1:7272", "host": "127.0.0.1", "port": "7272", "ssl": "false"},
39
+ {"name": "http://192.168.50.56:7272", "host": "192.168.50.56", "port": "7272", "ssl": "false"},
40
+ ][:num_sites]
41
+
42
+ # Get all available alert types - only use low risk (passing tests)
43
+ low_alerts = get_low_risk_alerts()
44
+
45
+ # Generate sites with alerts (only low-risk = all passing)
46
+ for site_idx, site_config in enumerate(sites):
47
+ lines.append(f'<site name="{site_config["name"]}" host="{site_config["host"]}" '
48
+ f'port="{site_config["port"]}" ssl="{site_config["ssl"]}">')
49
+ lines.append('<alerts>')
50
+
51
+ # Randomize number of alerts per site (vary runtime simulation)
52
+ num_alerts = random.randint(2, 6)
53
+ site_alerts = []
54
+
55
+ for _ in range(num_alerts):
56
+ # Only choose low risk alerts (all tests pass)
57
+ alert = random.choice(low_alerts).copy()
58
+
59
+ # Vary the number of instances (affects processing time)
60
+ alert["max_instances"] = random.randint(1, 8)
61
+ site_alerts.append(alert)
62
+
63
+ for alert in site_alerts:
64
+ lines.extend(generate_alert_xml(alert, site_config, site_idx))
65
+
66
+ lines.append('</alerts>')
67
+ lines.append('</site>')
68
+
69
+ lines.append('</OWASPZAPReport>\n')
70
+
71
+ # Write to file
72
+ with open(output_path, "w", encoding="utf-8") as f:
73
+ f.write("".join(lines))
74
+
75
+
76
+ def generate_alert_xml(alert_template: Dict, site_config: Dict, site_idx: int):
77
+ """Generate XML lines for a single alert item."""
78
+ lines = ['<alertitem>\n']
79
+
80
+ # Core alert info
81
+ lines.append(f' <pluginid>{alert_template["pluginid"]}</pluginid>\n')
82
+ lines.append(f' <alert>{alert_template["name"]}</alert>\n')
83
+ lines.append(f' <name>{alert_template["name"]}</name>\n')
84
+ lines.append(f' <riskcode>{alert_template["riskcode"]}</riskcode>\n')
85
+ lines.append(f' <confidence>{alert_template["confidence"]}</confidence>\n')
86
+ lines.append(f' <riskdesc>{alert_template["riskdesc"]}</riskdesc>\n')
87
+ lines.append(f' <desc>{alert_template["desc"]}</desc>\n')
88
+
89
+ # Instances - vary count to simulate different scan complexities/runtimes
90
+ lines.append(' <instances>\n')
91
+ max_instances = alert_template.get("max_instances", 3)
92
+ num_instances = random.randint(1, max_instances)
93
+
94
+ for i in range(num_instances):
95
+ lines.append(' <instance>\n')
96
+
97
+ # Generate URI variations
98
+ base_url = site_config["name"]
99
+ paths = alert_template.get("paths", ["/", "/index.html", "/api/data"])
100
+ uri = f'{base_url}{random.choice(paths)}'
101
+ lines.append(f' <uri>{uri}</uri>\n')
102
+
103
+ method = random.choice(["GET", "POST"])
104
+ lines.append(f' <method>{method}</method>\n')
105
+
106
+ lines.append(f' <param>{alert_template["param"]}</param>\n')
107
+
108
+ if "evidence" in alert_template:
109
+ lines.append(f' <evidence>{alert_template["evidence"]}</evidence>\n')
110
+
111
+ lines.append(' </instance>\n')
112
+
113
+ lines.append(' </instances>\n')
114
+ lines.append(f' <count>{num_instances}</count>\n')
115
+
116
+ # Solutions and references
117
+ lines.append(f' <solution>{alert_template["solution"]}</solution>\n')
118
+
119
+ if "otherinfo" in alert_template:
120
+ lines.append(f' <otherinfo>{alert_template["otherinfo"]}</otherinfo>\n')
121
+
122
+ lines.append(f' <reference>{alert_template["reference"]}</reference>\n')
123
+ lines.append(f' <cweid>{alert_template["cweid"]}</cweid>\n')
124
+ lines.append(f' <wascid>{alert_template["wascid"]}</wascid>\n')
125
+ lines.append(f' <sourceid>3</sourceid>\n')
126
+ lines.append('</alertitem>\n')
127
+
128
+ return lines
129
+
130
+
131
+ def get_low_risk_alerts():
132
+ """Return low-risk security alerts."""
133
+ return [
134
+ {
135
+ "pluginid": "10021",
136
+ "name": "X-Content-Type-Options Header Missing",
137
+ "riskcode": "1",
138
+ "confidence": "2",
139
+ "riskdesc": "Low (Medium)",
140
+ "desc": "&lt;p&gt;The Anti-MIME-Sniffing header X-Content-Type-Options was not set to &apos;nosniff&apos;. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.&lt;/p&gt;",
141
+ "param": "X-Content-Type-Options",
142
+ "max_instances": 5,
143
+ "paths": ["/", "/demo.css", "/welcome.html", "/error.html"],
144
+ "solution": "&lt;p&gt;Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to &apos;nosniff&apos; for all web pages.&lt;/p&gt;&lt;p&gt;If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.&lt;/p&gt;",
145
+ "otherinfo": "&lt;p&gt;This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.&lt;/p&gt;&lt;p&gt;At &quot;High&quot; threshold this scanner will not alert on client or server error responses.&lt;/p&gt;",
146
+ "reference": "&lt;p&gt;http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx&lt;/p&gt;&lt;p&gt;https://www.owasp.org/index.php/List_of_useful_HTTP_headers&lt;/p&gt;",
147
+ "cweid": "16",
148
+ "wascid": "15",
149
+ },
150
+ {
151
+ "pluginid": "10016",
152
+ "name": "Web Browser XSS Protection Not Enabled",
153
+ "riskcode": "1",
154
+ "confidence": "2",
155
+ "riskdesc": "Low (Medium)",
156
+ "desc": "&lt;p&gt;Web Browser XSS Protection is not enabled, or is disabled by the configuration of the &apos;X-XSS-Protection&apos; HTTP response header on the web server&lt;/p&gt;",
157
+ "param": "X-XSS-Protection",
158
+ "max_instances": 7,
159
+ "paths": ["/", "/welcome.html", "/error.html", "/favicon.ico", "/robots.txt", "/sitemap.xml"],
160
+ "solution": "&lt;p&gt;Ensure that the web browser&apos;s XSS filter is enabled, by setting the X-XSS-Protection HTTP response header to &apos;1&apos;.&lt;/p&gt;",
161
+ "otherinfo": "&lt;p&gt;The X-XSS-Protection HTTP response header allows the web server to enable or disable the web browser&apos;s XSS protection mechanism. The following values would attempt to enable it: &lt;/p&gt;&lt;p&gt;X-XSS-Protection: 1; mode=block&lt;/p&gt;&lt;p&gt;X-XSS-Protection: 1; report=http://www.example.com/xss&lt;/p&gt;&lt;p&gt;The following values would disable it:&lt;/p&gt;&lt;p&gt;X-XSS-Protection: 0&lt;/p&gt;&lt;p&gt;The X-XSS-Protection HTTP response header is currently supported on Internet Explorer, Chrome and Safari (WebKit).&lt;/p&gt;&lt;p&gt;Note that this alert is only raised if the response body could potentially contain an XSS payload (with a text-based content type, with a non-zero length).&lt;/p&gt;",
162
+ "reference": "&lt;p&gt;https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet&lt;/p&gt;&lt;p&gt;https://blog.veracode.com/2014/03/guidelines-for-setting-security-headers/&lt;/p&gt;",
163
+ "cweid": "933",
164
+ "wascid": "14",
165
+ },
166
+ {
167
+ "pluginid": "10012",
168
+ "name": "Password Autocomplete in Browser",
169
+ "riskcode": "1",
170
+ "confidence": "2",
171
+ "riskdesc": "Low (Medium)",
172
+ "desc": "&lt;p&gt;The AUTOCOMPLETE attribute is not disabled on an HTML FORM/INPUT element containing password type input. Passwords may be stored in browsers and retrieved.&lt;/p&gt;",
173
+ "param": "password_field",
174
+ "max_instances": 2,
175
+ "paths": ["/", "/login"],
176
+ "evidence": "&lt;input id=&quot;password_field&quot; size=&quot;30&quot; type=&quot;password&quot;&gt;",
177
+ "solution": "&lt;p&gt;Turn off the AUTOCOMPLETE attribute in forms or individual input elements containing password inputs by using AUTOCOMPLETE=&apos;OFF&apos;.&lt;/p&gt;",
178
+ "reference": "&lt;p&gt;http://www.w3schools.com/tags/att_input_autocomplete.asp&lt;/p&gt;&lt;p&gt;https://msdn.microsoft.com/en-us/library/ms533486%28v=vs.85%29.aspx&lt;/p&gt;",
179
+ "cweid": "525",
180
+ "wascid": "15",
181
+ },
182
+ ]
183
+
184
+
185
+ def get_high_risk_alerts():
186
+ """Return high-risk security alerts."""
187
+ return [
188
+ {
189
+ "pluginid": "40018",
190
+ "name": "SQL Injection",
191
+ "riskcode": "3",
192
+ "confidence": "3",
193
+ "riskdesc": "High (High)",
194
+ "desc": "&lt;p&gt;SQL injection may be possible.&lt;/p&gt;",
195
+ "param": "id",
196
+ "max_instances": 3,
197
+ "paths": ["/api/users", "/search", "/products"],
198
+ "evidence": "You have an error in your SQL syntax",
199
+ "solution": "&lt;p&gt;Use prepared statements and parameterized queries.&lt;/p&gt;",
200
+ "reference": "&lt;p&gt;https://www.owasp.org/index.php/SQL_Injection&lt;/p&gt;",
201
+ "cweid": "89",
202
+ "wascid": "19",
203
+ },
204
+ {
205
+ "pluginid": "40012",
206
+ "name": "Cross Site Scripting (Reflected)",
207
+ "riskcode": "3",
208
+ "confidence": "2",
209
+ "riskdesc": "High (Medium)",
210
+ "desc": "&lt;p&gt;Cross-site Scripting (XSS) is possible.&lt;/p&gt;",
211
+ "param": "query",
212
+ "max_instances": 2,
213
+ "paths": ["/search", "/comment"],
214
+ "evidence": "&lt;script&gt;alert(1)&lt;/script&gt;",
215
+ "solution": "&lt;p&gt;Validate all input and encode all output.&lt;/p&gt;",
216
+ "reference": "&lt;p&gt;https://www.owasp.org/index.php/XSS&lt;/p&gt;",
217
+ "cweid": "79",
218
+ "wascid": "8",
219
+ },
220
+ {
221
+ "pluginid": "10020",
222
+ "name": "X-Frame-Options Header Not Set",
223
+ "riskcode": "2",
224
+ "confidence": "2",
225
+ "riskdesc": "Medium (Medium)",
226
+ "desc": "&lt;p&gt;X-Frame-Options header is not included in the HTTP response to protect against &apos;ClickJacking&apos; attacks.&lt;/p&gt;",
227
+ "param": "X-Frame-Options",
228
+ "max_instances": 4,
229
+ "paths": ["/", "/welcome.html", "/error.html"],
230
+ "solution": "&lt;p&gt;Most modern Web browsers support the X-Frame-Options HTTP header. Ensure it&apos;s set on all web pages returned by your site (if you expect the page to be framed only by pages on your server (e.g. it&apos;s part of a FRAMESET) then you&apos;ll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY. ALLOW-FROM allows specific websites to frame the web page in supported web browsers).&lt;/p&gt;",
231
+ "reference": "&lt;p&gt;http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx&lt;/p&gt;",
232
+ "cweid": "16",
233
+ "wascid": "15",
234
+ },
235
+ ]
236
+
237
+
238
+ def get_mixed_risk_alerts():
239
+ """Return mix of risk levels."""
240
+ return get_low_risk_alerts() + [
241
+ {
242
+ "pluginid": "10020",
243
+ "name": "X-Frame-Options Header Not Set",
244
+ "riskcode": "2",
245
+ "confidence": "2",
246
+ "riskdesc": "Medium (Medium)",
247
+ "desc": "&lt;p&gt;X-Frame-Options header is not included in the HTTP response to protect against &apos;ClickJacking&apos; attacks.&lt;/p&gt;",
248
+ "param": "X-Frame-Options",
249
+ "max_instances": 4,
250
+ "paths": ["/", "/welcome.html", "/error.html"],
251
+ "solution": "&lt;p&gt;Most modern Web browsers support the X-Frame-Options HTTP header. Ensure it&apos;s set on all web pages returned by your site (if you expect the page to be framed only by pages on your server (e.g. it&apos;s part of a FRAMESET) then you&apos;ll want to use SAMEORIGIN, otherwise if you never expect the page to be framed, you should use DENY. ALLOW-FROM allows specific websites to frame the web page in supported web browsers).&lt;/p&gt;",
252
+ "reference": "&lt;p&gt;http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx&lt;/p&gt;",
253
+ "cweid": "16",
254
+ "wascid": "15",
255
+ },
256
+ ]
@@ -0,0 +1,53 @@
1
+ # handlers.yaml — Central registry of supported Robotmk Bridge handlers.
2
+ #
3
+ # This file is the single source of truth for:
4
+ # 1. The Bakery UI (handler dropdown choices + per-handler parameter fields),
5
+ # generated by scripts/generate_bakery_handler_fields.py.
6
+ # 2. The test data generator (tests/data_generator/).
7
+ #
8
+ # Fields per handler:
9
+ # name: Internal key; must match the handler name expected by RobotmkBridgeCore.
10
+ # title: Human-readable name shown in the Bakery UI.
11
+ # class_import: Fully qualified Python class path (for introspection).
12
+ # result_ext: Typical file extension of result files produced by this tool.
13
+ # description: Short one-liner for documentation purposes.
14
+
15
+ handlers:
16
+ - name: junit
17
+ title: JUnit
18
+ class_import: rmkbridge.junit.JUnitHandler
19
+ result_ext: xml
20
+ description: >
21
+ Parses JUnit XML result files produced by any JUnit-compatible
22
+ test framework (pytest, Maven Surefire, NUnit, etc.).
23
+
24
+ - name: gatling
25
+ title: Gatling
26
+ class_import: rmkbridge.gatling.GatlingHandler
27
+ result_ext: log
28
+ description: >
29
+ Parses Gatling simulation log files (simulation.log) produced
30
+ by Gatling performance testing runs.
31
+
32
+ - name: zaproxy
33
+ title: OWASP ZAP (Zed Attack Proxy)
34
+ class_import: rmkbridge.zap.ZAProxyHandler
35
+ result_ext: xml
36
+ description: >
37
+ Parses OWASP ZAP security scan result files (XML or JSON format).
38
+ Supports filtering by accepted_risk_level and required_confidence_level.
39
+ handler_params:
40
+ - name: accepted_risk_level
41
+ type: int
42
+ required: false
43
+ default: null
44
+ description: >
45
+ Minimum risk level of alerts to include (0=Info, 1=Low, 2=Medium, 3=High).
46
+ Alerts below this level are ignored.
47
+ - name: required_confidence_level
48
+ type: int
49
+ required: false
50
+ default: null
51
+ description: >
52
+ Minimum confidence level of alerts to include (1=Low, 2=Medium, 3=High).
53
+ Alerts below this confidence are ignored.