diff --git a/testing/backend/unit/fixtures/secret_scanner/sample_output.txt b/testing/backend/unit/fixtures/secret_scanner/sample_output.txt new file mode 100644 index 000000000..6ee9de7ea --- /dev/null +++ b/testing/backend/unit/fixtures/secret_scanner/sample_output.txt @@ -0,0 +1,16 @@ +[ + { + "RuleID": "github-pat", + "File": "src/config/credentials.py", + "StartLine": 12, + "Offender": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "Secret": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "RuleID": "aws-access-key", + "File": "deployments/env", + "StartLine": 3, + "Offender": "AKIAIOSFODNN7EXAMPLE", + "Secret": "AKIAIOSFODNN7EXAMPLE" + } +] diff --git a/testing/backend/unit/test_secret_scanner_plugin.py b/testing/backend/unit/test_secret_scanner_plugin.py new file mode 100644 index 000000000..c6e924d6b --- /dev/null +++ b/testing/backend/unit/test_secret_scanner_plugin.py @@ -0,0 +1,117 @@ +"""Parser and contract coverage for plugins/secret_scanner (issue #1533).""" + +from __future__ import annotations + +import asyncio +import importlib.util +from pathlib import Path + +import pytest + +from backend.secuscan.config import settings +from backend.secuscan.executor import executor +from backend.secuscan.plugins import PluginManager + +PLUGIN_ID = "secret_scanner" +FIXTURE_PATH = Path(__file__).parent / "fixtures" / PLUGIN_ID / "sample_output.txt" +PARSER_PATH = Path(settings.plugins_dir) / PLUGIN_ID / "parser.py" + + +def _load_secret_scanner_parser(): + spec = importlib.util.spec_from_file_location("secret_scanner_parser", PARSER_PATH) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +@pytest.fixture +def plugin_manager(setup_test_environment) -> PluginManager: + manager = PluginManager(settings.plugins_dir) + asyncio.run(manager.load_plugins()) + return manager + + +def test_secret_scanner_metadata_loads_through_validation_path(plugin_manager): + plugin = plugin_manager.get_plugin(PLUGIN_ID) + assert plugin is not None + assert plugin.id == PLUGIN_ID + schema = plugin_manager.get_plugin_schema(PLUGIN_ID) + assert schema is not None + + +def test_secret_scanner_build_command_renders_representative_target(plugin_manager): + target = "https://github.com/example/repo" + command = plugin_manager.build_command(PLUGIN_ID, {"target": target}) + + assert command is not None + assert "gitleaks" in " ".join(command) or any("git" in arg for arg in command) + + +def test_secret_scanner_parser_fixture_produces_stable_findings(plugin_manager): + parser = _load_secret_scanner_parser() + raw_output = FIXTURE_PATH.read_text(encoding="utf-8") + + parsed = parser.parse(raw_output) + assert parsed["count"] == 2 + assert len(parsed["findings"]) == 2 + + first = parsed["findings"][0] + assert first["title"] == "Secret Leak: github-pat in src/config/credentials.py" + assert first["category"] == "Credential Leak" + assert first["severity"] == "critical" + assert first["metadata"]["file"] == "src/config/credentials.py" + assert first["metadata"]["line"] == 12 + + second = parsed["findings"][1] + assert second["title"] == "Secret Leak: aws-access-key in deployments/env" + assert second["severity"] == "critical" + + +def test_secret_scanner_parser_empty_json_array_returns_empty_findings(plugin_manager): + parser = _load_secret_scanner_parser() + parsed = parser.parse("[]") + + assert parsed["findings"] == [] + assert parsed["count"] == 0 + + +def test_secret_scanner_parser_no_leaks_message_returns_empty_findings(plugin_manager): + parser = _load_secret_scanner_parser() + parsed = parser.parse("No leaks found.") + + assert parsed["findings"] == [] + assert parsed["count"] == 0 + + +def test_secret_scanner_parser_malformed_json_returns_empty_findings(plugin_manager): + parser = _load_secret_scanner_parser() + parsed = parser.parse("not-valid-json {{{{broken") + + assert parsed["findings"] == [] + assert parsed["count"] == 0 + + +def test_secret_scanner_parser_uses_default_rule_id_when_missing(plugin_manager): + parser = _load_secret_scanner_parser() + raw = '[{"File": "src/main.py", "StartLine": 1}]' + parsed = parser.parse(raw) + + assert len(parsed["findings"]) == 1 + assert parsed["findings"][0]["title"] == "Secret Leak: Secret Detected in src/main.py" + + +def test_secret_scanner_executor_normalizes_parser_fixture(plugin_manager): + parser = _load_secret_scanner_parser() + plugin = plugin_manager.get_plugin(PLUGIN_ID) + assert plugin is not None + + parsed = parser.parse(FIXTURE_PATH.read_text(encoding="utf-8")) + normalized = executor._normalize_parsed_result( + plugin, FIXTURE_PATH.read_text(encoding="utf-8"), parsed + ) + + assert normalized["count"] == 2 + assert len(normalized["findings"]) == 2 + assert normalized["findings"][0]["severity"] == "critical" + assert all(f["title"] for f in normalized["findings"])