diff --git a/backend/app/services/report_engine.py b/backend/app/services/report_engine.py index b1a2197..4e31e30 100644 --- a/backend/app/services/report_engine.py +++ b/backend/app/services/report_engine.py @@ -76,6 +76,10 @@ class ReportEngine: logger.info("DOCX generated: %s", output_path) return output_path + def generate_html(self, template_name: str, context: dict) -> str: + """Render and save a standalone HTML report (alias for spec compatibility).""" + return self.generate_html_file(template_name, context) + def generate_html_file(self, template_name: str, context: dict) -> str: """Render and save a standalone HTML report.""" html_content = self.render_html(template_name, context) diff --git a/backend/tests/test_report_engine.py b/backend/tests/test_report_engine.py new file mode 100644 index 0000000..8d5778a --- /dev/null +++ b/backend/tests/test_report_engine.py @@ -0,0 +1,63 @@ +"""Report engine unit tests (FASE-2.1).""" + +import os +import sys +from unittest.mock import MagicMock + +import pytest + +from app.services.report_engine import ReportEngine + + +@pytest.fixture +def engine(tmp_path, monkeypatch): + templates = tmp_path / "templates" + styles = templates / "styles" + styles.mkdir(parents=True) + (styles / "report.css").write_text("body { color: #0e7490; }", encoding="utf-8") + (templates / "coverage_report.html").write_text( + "
{{ score }}
", + encoding="utf-8", + ) + out_dir = tmp_path / "out" + monkeypatch.setattr("app.services.report_engine.settings.REPORT_TEMPLATES_DIR", str(templates)) + monkeypatch.setattr("app.services.report_engine.settings.REPORT_OUTPUT_DIR", str(out_dir)) + monkeypatch.setattr("app.services.report_engine.settings.COMPANY_NAME", "Test Org") + return ReportEngine() + + +def test_render_html_injects_company_and_timestamp(engine): + html = engine.render_html("coverage_report", {"score": 42}) + assert "Test Org" in html + assert "42" in html + + +def test_generate_pdf_writes_file(engine, monkeypatch, tmp_path): + """WeasyPrint is stubbed so tests run without GTK/Pango system libraries.""" + def _write_pdf(output_path, stylesheets=None): + with open(output_path, "wb") as f: + f.write(b"%PDF-1.4 mock") + + mock_html_instance = MagicMock() + mock_html_instance.write_pdf.side_effect = _write_pdf + mock_wp = MagicMock() + mock_wp.HTML.return_value = mock_html_instance + mock_wp.CSS = MagicMock() + for key in list(sys.modules): + if key == "weasyprint" or key.startswith("weasyprint."): + monkeypatch.delitem(sys.modules, key, raising=False) + monkeypatch.setitem(sys.modules, "weasyprint", mock_wp) + + path = engine.generate_pdf("coverage_report", {"score": 99}) + + assert path.endswith(".pdf") + assert os.path.isfile(path) + mock_html_instance.write_pdf.assert_called_once() + + +def test_generate_html_file_writes_file(engine): + path = engine.generate_html("coverage_report", {"score": 7}) + assert path.endswith(".html") + assert os.path.isfile(path) + with open(path, encoding="utf-8") as f: + assert "Test Org" in f.read()