Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions packages/keploy-framework/src/keploy_framework/test_runner.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Test runner with validation and reporting."""

import asyncio
import html
import subprocess
import json
from pathlib import Path
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from rich.console import Console
from rich.table import Table

Expand Down Expand Up @@ -72,12 +72,16 @@ async def run_all_tests(
"docker",
"run",
"--rm",
"--network", "host",
"-v", f"{self.keploy_dir.absolute()}:/keploy",
"--network",
"host",
"-v",
f"{self.keploy_dir.absolute()}:/keploy",
self.docker_image,
"test",
"-c", self.api_url,
"--delay", "5",
"-c",
self.api_url,
"--delay",
"5",
]

try:
Expand Down Expand Up @@ -164,9 +168,7 @@ def _validate_results(self, results: TestResults) -> None:
if results.is_success:
console.print("[bold green]✅ All tests passed![/bold green]")
else:
console.print(
f"[bold yellow]⚠️ {results.failed} test(s) failed[/bold yellow]"
)
console.print(f"[bold yellow]⚠️ {results.failed} test(s) failed[/bold yellow]")

def _generate_report(self, results: TestResults) -> None:
"""Generate HTML test report.
Expand All @@ -176,7 +178,18 @@ def _generate_report(self, results: TestResults) -> None:
"""
report_path = self.keploy_dir / "test-report.html"

html = f"""
# Build table rows with proper HTML escaping
table_rows = []
for tc in results.test_cases:
escaped_name = html.escape(tc["name"])
escaped_status = html.escape(tc["status"])
table_rows.append(
f'<tr><td>{escaped_name}</td>'
f'<td class="{escaped_status}">{escaped_status}</td></tr>'
)
table_rows_html = "".join(table_rows)

html_content = f"""
<!DOCTYPE html>
<html>
<head>
Expand Down Expand Up @@ -206,11 +219,11 @@ def _generate_report(self, results: TestResults) -> None:
<th>Test Name</th>
<th>Status</th>
</tr>
{"".join(f'<tr><td>{tc["name"]}</td><td class="{tc["status"]}">{tc["status"]}</td></tr>' for tc in results.test_cases)}
{table_rows_html}
</table>
</body>
</html>
"""

report_path.write_text(html)
report_path.write_text(html_content)
console.print(f"[bold green]📊 Report generated: {report_path}[/bold green]")
45 changes: 44 additions & 1 deletion packages/keploy-framework/tests/test_framework.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""Tests for Keploy Framework."""

import tempfile
from pathlib import Path

import pytest

from keploy_framework import KeployConfig, ResultValidator
from keploy_framework.test_runner import TestResults
from keploy_framework.test_runner import KeployTestRunner, TestResults


def test_config_creation():
Expand Down Expand Up @@ -72,3 +75,43 @@ def test_validation_assertion():

with pytest.raises(AssertionError, match="below threshold"):
validator.assert_pass_rate(results)


def test_html_report_escapes_xss():
"""Test HTML report escapes user-controlled data to prevent XSS."""
# Create a temporary directory for the report
with tempfile.TemporaryDirectory() as tmpdir:
keploy_dir = Path(tmpdir)

# Create test results with potentially malicious input
results = TestResults(
total=2,
passed=1,
failed=1,
pass_rate=50.0,
test_cases=[
{"name": "<script>alert('XSS')</script>", "status": "passed"},
{"name": "normal_test", "status": "<img src=x onerror=alert('XSS')>"},
],
)

# Create runner and generate report
runner = KeployTestRunner(
api_url="http://localhost:8000",
keploy_dir=keploy_dir,
)
runner._generate_report(results)

# Read the generated HTML
report_path = keploy_dir / "test-report.html"
html_content = report_path.read_text()

# Verify that malicious scripts are escaped
assert "<script>alert('XSS')</script>" not in html_content
assert "&lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;" in html_content

assert "<img src=x onerror=alert('XSS')>" not in html_content
assert "&lt;img src=x onerror=alert(&#x27;XSS&#x27;)&gt;" in html_content

# Verify normal test name is still present (but escaped)
assert "normal_test" in html_content
Loading