221 lines
5.8 KiB
Python
221 lines
5.8 KiB
Python
"""
|
|
©AngelaMos | 2026
|
|
test_engine.py
|
|
"""
|
|
|
|
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
from collections.abc import Generator
|
|
|
|
import pytest
|
|
|
|
from dlp_scanner.config import ScanConfig
|
|
from dlp_scanner.engine import ScanEngine
|
|
from dlp_scanner.models import (
|
|
Finding,
|
|
Location,
|
|
ScanResult,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def engine() -> ScanEngine:
|
|
"""
|
|
Provide a ScanEngine with default config
|
|
"""
|
|
return ScanEngine(ScanConfig())
|
|
|
|
|
|
@pytest.fixture
|
|
def pii_dir() -> Generator[Path, None, None]:
|
|
"""
|
|
Provide a temp directory with valid detectable SSNs
|
|
"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
root = Path(tmpdir)
|
|
csv_path = root / "employees.csv"
|
|
csv_path.write_text(
|
|
"name,ssn\n"
|
|
"Alice,456-78-9012\n"
|
|
"Bob,234-56-7890\n"
|
|
)
|
|
yield root
|
|
|
|
|
|
@pytest.fixture
|
|
def clean_dir() -> Generator[Path, None, None]:
|
|
"""
|
|
Provide a temp directory with no sensitive data
|
|
"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
root = Path(tmpdir)
|
|
txt_path = root / "readme.txt"
|
|
txt_path.write_text("This file contains no sensitive data.")
|
|
yield root
|
|
|
|
|
|
@pytest.fixture
|
|
def result_with_findings() -> ScanResult:
|
|
"""
|
|
Provide a ScanResult with test findings
|
|
"""
|
|
result = ScanResult(targets_scanned = 1)
|
|
result.findings = [
|
|
Finding(
|
|
rule_id = "PII_SSN",
|
|
rule_name = "US Social Security Number",
|
|
severity = "critical",
|
|
confidence = 0.95,
|
|
location = Location(
|
|
source_type = "file",
|
|
uri = "data.csv",
|
|
line = 5,
|
|
),
|
|
redacted_snippet = "***-**-6789",
|
|
compliance_frameworks = ["HIPAA",
|
|
"CCPA"],
|
|
remediation = "Encrypt data",
|
|
),
|
|
]
|
|
return result
|
|
|
|
|
|
class TestScanEngine:
|
|
def test_scan_files_finds_pii(
|
|
self,
|
|
engine: ScanEngine,
|
|
pii_dir: Path
|
|
) -> None:
|
|
result = engine.scan_files(str(pii_dir))
|
|
assert len(result.findings) > 0
|
|
|
|
def test_scan_files_clean_dir(
|
|
self,
|
|
engine: ScanEngine,
|
|
clean_dir: Path
|
|
) -> None:
|
|
result = engine.scan_files(str(clean_dir))
|
|
assert len(result.findings) == 0
|
|
|
|
def test_scan_files_nonexistent(self, engine: ScanEngine) -> None:
|
|
result = engine.scan_files("/no/such/path")
|
|
assert len(result.errors) > 0
|
|
|
|
def test_scan_files_sets_completed_at(
|
|
self,
|
|
engine: ScanEngine,
|
|
pii_dir: Path
|
|
) -> None:
|
|
result = engine.scan_files(str(pii_dir))
|
|
assert result.scan_completed_at is not None
|
|
|
|
def test_scan_database_sqlite(self, engine: ScanEngine) -> None:
|
|
with tempfile.NamedTemporaryFile(suffix = ".db",
|
|
delete = False) as f:
|
|
db_path = f.name
|
|
|
|
import sqlite3
|
|
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("CREATE TABLE users "
|
|
"(name TEXT, ssn TEXT)")
|
|
conn.execute(
|
|
"INSERT INTO users VALUES "
|
|
"('Alice', '456-78-9012')"
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
uri = f"sqlite:///{db_path}"
|
|
result = engine.scan_database(uri)
|
|
assert len(result.findings) > 0
|
|
|
|
Path(db_path).unlink(missing_ok = True)
|
|
|
|
def test_generate_report_json(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
output = engine.generate_report(result_with_findings, "json")
|
|
data = json.loads(output)
|
|
assert "findings" in data
|
|
assert len(data["findings"]) == 1
|
|
|
|
def test_generate_report_sarif(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
output = engine.generate_report(result_with_findings, "sarif")
|
|
data = json.loads(output)
|
|
assert data["version"] == "2.1.0"
|
|
|
|
def test_generate_report_csv(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
output = engine.generate_report(result_with_findings, "csv")
|
|
lines = output.strip().split("\n")
|
|
assert len(lines) == 2
|
|
|
|
def test_generate_report_console(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
output = engine.generate_report(result_with_findings, "console")
|
|
assert "PII_SSN" in output or "Social" in output
|
|
|
|
def test_generate_report_uses_config_default(
|
|
self,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
config = ScanConfig()
|
|
config.output.format = "json"
|
|
engine = ScanEngine(config)
|
|
output = engine.generate_report(result_with_findings)
|
|
data = json.loads(output)
|
|
assert "findings" in data
|
|
|
|
def test_display_console(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
engine.display_console(result_with_findings)
|
|
|
|
def test_write_report(
|
|
self,
|
|
engine: ScanEngine,
|
|
result_with_findings: ScanResult,
|
|
) -> None:
|
|
with tempfile.NamedTemporaryFile(
|
|
suffix = ".json",
|
|
delete = False,
|
|
mode = "w",
|
|
) as f:
|
|
output_path = f.name
|
|
|
|
engine.write_report(
|
|
result_with_findings,
|
|
output_path,
|
|
"json",
|
|
)
|
|
content = Path(output_path).read_text()
|
|
data = json.loads(content)
|
|
assert len(data["findings"]) == 1
|
|
|
|
Path(output_path).unlink(missing_ok = True)
|
|
|
|
|
|
class TestReporterMap:
|
|
def test_all_formats_have_reporters(self) -> None:
|
|
from dlp_scanner.engine import REPORTER_MAP
|
|
|
|
expected = {"console", "json", "sarif", "csv"}
|
|
assert set(REPORTER_MAP.keys()) == expected
|