from __future__ import annotations from app.services.export_service import ExportService def test_project_report_html_uses_handoff_layout_and_print_styles() -> None: summary = { "project": { "id": "project-1", "name": "Demo ", "description": "QA handoff", "region": "Kempen", "status": "active", }, "datasets": [ { "id": "dataset-1", "name": "reference.geojson", "dataset_type": "vector", "dataset_role": "reference", "source_name": "fixture", "reference_layer_name": "buildings", "status": "ready", "crs": "EPSG:4326", "bounds_json": None, "feature_count": 2, } ], "quality_checks": [ { "id": "quality-1", "analysis_run_id": None, "candidate_dataset_id": "candidate-1", "reference_dataset_id": "dataset-1", "check_type": "demo_candidate_vs_reference", "status": "ok", "score": 0.75, } ], "exports": [ { "id": "export-1", "analysis_run_id": None, "export_type": "project_metadata_json", "storage_path": "storage/exports/metadata.json", "metadata_json": {"readiness_state": "ready"}, "created_at": "2026-06-18T10:00:00+00:00", } ], "readiness_summary": { "overall_state": "ready", "items": [ {"key": "project", "label": "Project", "state": "ready", "detail": "Demo (Kempen)"}, {"key": "datasets", "label": "Datasets", "state": "ready", "detail": "1/1 ready"}, ], "counts": { "area_count": 1, "dataset_count": 1, "ready_dataset_count": 1, "vector_dataset_count": 1, "raster_dataset_count": 0, "reference_dataset_count": 1, "quality_check_count": 1, "export_count": 1, }, }, "known_limitations": ["No live GRB/OSM/Sentinel fetching is performed by the report export."], } html = ExportService._render_project_report_html(summary) assert 'class="report-shell"' in html assert 'class="report-hero"' in html assert 'class="report-scorecards"' in html assert 'class="readiness-pill readiness-ready"' in html assert 'class="section-kicker"' in html assert "@media print" in html assert "page-break-inside: avoid" in html assert "Generated from persisted GeoIntel state" in html assert "Dataset inventory" in html assert "QA/QC evidence" in html assert "Artifact history" in html assert "Demo <Kempen>" in html def test_project_report_html_escapes_table_values_in_polished_layout() -> None: summary = { "project": { "id": "project-1", "name": "", "description": "unsafe", "region": "Kempen", "status": "active", }, "datasets": [], "quality_checks": [], "exports": [], "readiness_summary": { "overall_state": "needs_attention", "items": [ {"key": "project", "label": "", "state": "waiting", "detail": ""}, ], "counts": { "area_count": 0, "dataset_count": 0, "ready_dataset_count": 0, "vector_dataset_count": 0, "raster_dataset_count": 0, "reference_dataset_count": 0, "quality_check_count": 0, "export_count": 0, }, }, "known_limitations": [""], } html = ExportService._render_project_report_html(summary) assert "" not in html assert "<script>alert(1)</script>" in html assert "unsafe" not in html assert "<b>unsafe</b>" in html assert "<Project>" in html assert "<unsafe limitation>" in html assert 'class="readiness-pill readiness-needs_attention"' in html