diff --git a/tests/integration/test_report_generation.py b/tests/integration/test_report_generation.py new file mode 100644 index 0000000..d2ad439 --- /dev/null +++ b/tests/integration/test_report_generation.py @@ -0,0 +1,293 @@ +"""Integration tests for report generation. + +Tests the full report generation pipeline from test run to PDF output. +""" + +import json +import tempfile +from datetime import datetime +from pathlib import Path +from uuid import UUID, uuid4 + +import pytest + +from py_dvt_ate.data.models import Measurement, TestStatus +from py_dvt_ate.data.repository import SQLiteRepository + + +class TestReportGenerationIntegration: + """Integration tests for the report generation pipeline.""" + + @pytest.fixture + def temp_dir(self) -> Path: + """Create a temporary directory for test files.""" + with tempfile.TemporaryDirectory() as tmpdir: + yield Path(tmpdir) + + @pytest.fixture + def repository(self, temp_dir: Path) -> SQLiteRepository: + """Create a test repository with sample data.""" + db_path = temp_dir / "test.db" + measurements_dir = temp_dir / "measurements" + + repo = SQLiteRepository(db_path, measurements_dir) + + # Create a test run + test_config = { + "temperatures": [-40.0, 25.0, 85.0], + "input_voltage": 5.0, + "load_current": 0.1, + } + + run_id = repo.create_run( + test_name="tempco", + config=test_config, + operator="test_operator", + description="Integration test run", + ) + + # Add some results + repo.save_result( + run_id=run_id, + parameter="tempco", + value=48.5, + unit="ppm/C", + lower_limit=None, + upper_limit=100.0, + ) + + repo.save_result( + run_id=run_id, + parameter="output_voltage_m40c", + value=3.2965, + unit="V", + lower_limit=3.2, + upper_limit=3.4, + ) + + repo.save_result( + run_id=run_id, + parameter="output_voltage_25c", + value=3.3000, + unit="V", + lower_limit=3.2, + upper_limit=3.4, + ) + + repo.save_result( + run_id=run_id, + parameter="output_voltage_85c", + value=3.2901, + unit="V", + lower_limit=3.2, + upper_limit=3.4, + ) + + # Add measurements + measurements = [] + temperatures = [-40.0, 0.0, 25.0, 50.0, 85.0] + voltages = [3.2965, 3.2985, 3.3000, 3.2960, 3.2901] + + for i, (temp, voltage) in enumerate(zip(temperatures, voltages, strict=False)): + measurements.append( + Measurement( + timestamp=float(i * 60), + parameter="output_voltage", + value=voltage, + unit="V", + temperature=temp, + input_voltage=5.0, + load_current=0.1, + ) + ) + + repo.save_measurements(run_id, measurements) + + # Complete the run + repo.complete_run(run_id, TestStatus.PASSED) + + return repo + + @pytest.fixture + def run_id(self, repository: SQLiteRepository) -> UUID: + """Get the test run ID from the repository.""" + runs = repository.get_all_runs() + return UUID(runs[0].id) + + def test_full_report_generation( + self, repository: SQLiteRepository, run_id: UUID, temp_dir: Path + ) -> None: + """Test complete report generation pipeline.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + + # Create report config + config = ReportConfig( + company_name="Test Company Ltd", + include_charts=True, + chart_dpi=100, # Lower for faster tests + ) + + # Create generator + reports_dir = temp_dir / "reports" + generator = ReportGenerator( + repository=repository, + config=config, + reports_dir=reports_dir, + ) + + # Generate report + pdf_path = generator.generate(run_id) + + # Verify PDF was created + assert pdf_path.exists() + assert pdf_path.suffix == ".pdf" + assert pdf_path.stat().st_size > 1000 # Should be non-trivial size + + # Verify it's in the reports directory + assert pdf_path.parent == reports_dir + + def test_report_generation_custom_path( + self, repository: SQLiteRepository, run_id: UUID, temp_dir: Path + ) -> None: + """Test report generation with custom output path.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + + config = ReportConfig(include_charts=False) # No charts for faster test + + generator = ReportGenerator( + repository=repository, + config=config, + ) + + # Generate to custom path + custom_path = temp_dir / "custom_report.pdf" + pdf_path = generator.generate(run_id, output_path=custom_path) + + assert pdf_path == custom_path + assert pdf_path.exists() + + def test_report_generation_as_bytes( + self, repository: SQLiteRepository, run_id: UUID + ) -> None: + """Test generating report as bytes.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + + config = ReportConfig(include_charts=False) + + generator = ReportGenerator( + repository=repository, + config=config, + ) + + # Generate as bytes + pdf_bytes = generator.generate_bytes(run_id) + + # Verify it's a valid PDF + assert isinstance(pdf_bytes, bytes) + assert pdf_bytes.startswith(b"%PDF") # PDF magic bytes + assert len(pdf_bytes) > 1000 + + def test_report_includes_all_data( + self, repository: SQLiteRepository, run_id: UUID, temp_dir: Path + ) -> None: + """Test that generated report includes all expected data.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + from py_dvt_ate.reporting.renderers.html import HTMLRenderer + + config = ReportConfig( + company_name="Test Company", + include_charts=False, + ) + + generator = ReportGenerator( + repository=repository, + config=config, + ) + + # Get HTML (intermediate step) to check content + data = generator._gather_data(run_id) + html_renderer = HTMLRenderer() + html = html_renderer.render(data) + + # Check for expected content + assert "tempco" in html + assert "Test Company" in html + assert "48.500000" in html # tempco value + assert "3.300000" in html # output voltage + assert "test_operator" in html + assert "Integration test run" in html + assert "PASS" in html + + def test_report_with_failed_results( + self, temp_dir: Path + ) -> None: + """Test report generation with failed test results.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + + # Create repository with a failed test + db_path = temp_dir / "failed_test.db" + repo = SQLiteRepository(db_path, temp_dir / "measurements") + + run_id = repo.create_run( + test_name="failed_test", + config={}, + operator="test", + ) + + # Add failing result + repo.save_result( + run_id=run_id, + parameter="test_param", + value=150.0, # Exceeds limit + unit="X", + lower_limit=0.0, + upper_limit=100.0, + ) + + repo.complete_run(run_id, TestStatus.FAILED) + + # Generate report + config = ReportConfig(include_charts=False) + generator = ReportGenerator(repository=repo, config=config) + + pdf_bytes = generator.generate_bytes(run_id) + + # Should still generate + assert pdf_bytes.startswith(b"%PDF") + + def test_report_generation_invalid_run_id( + self, repository: SQLiteRepository, temp_dir: Path + ) -> None: + """Test that invalid run ID raises appropriate error.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerationError, ReportGenerator + + config = ReportConfig() + generator = ReportGenerator(repository=repository, config=config) + + invalid_id = uuid4() + + with pytest.raises(ReportGenerationError): + generator.generate(invalid_id) + + def test_report_charts_generation( + self, repository: SQLiteRepository, run_id: UUID, temp_dir: Path + ) -> None: + """Test that charts are generated when enabled.""" + from py_dvt_ate.reporting import ReportConfig, ReportGenerator + + config = ReportConfig(include_charts=True, chart_dpi=72) + + generator = ReportGenerator( + repository=repository, + config=config, + reports_dir=temp_dir / "reports", + ) + + # Gather data and check charts + data = generator._gather_data(run_id) + + # Should have at least one chart (results bar chart) + assert len(data.charts) >= 1 + + # Voltage vs temperature chart should be present (we have voltage measurements) + assert "Voltage vs Temperature" in data.charts or "Results Summary" in data.charts