"""Unit tests for chart generator.""" import base64 from datetime import datetime import pandas as pd import pytest from py_dvt_ate.data.models import TestResult, TestRun, TestStatus from py_dvt_ate.reporting.charts.matplotlib_charts import ChartGenerator from py_dvt_ate.reporting.exceptions import ChartGenerationError class TestChartGenerator: """Tests for ChartGenerator class.""" @pytest.fixture def generator(self) -> ChartGenerator: """Create a chart generator instance.""" return ChartGenerator(dpi=100) # Lower DPI for faster tests @pytest.fixture def sample_run(self) -> TestRun: """Create a sample test run.""" return TestRun( id="12345678-1234-1234-1234-123456789abc", test_name="tempco", started_at=datetime(2024, 1, 15, 10, 30, 0), status=TestStatus.PASSED, config_json="{}", ) @pytest.fixture def sample_results(self) -> list[TestResult]: """Create sample test results.""" return [ TestResult( id="result-1", test_run_id="12345678-1234-1234-1234-123456789abc", parameter="tempco", value=45.0, unit="ppm/C", measured_at=datetime(2024, 1, 15, 10, 35, 0), ), TestResult( id="result-2", test_run_id="12345678-1234-1234-1234-123456789abc", parameter="output_voltage_25c", value=3.3001, unit="V", measured_at=datetime(2024, 1, 15, 10, 33, 0), ), ] @pytest.fixture def voltage_measurements(self) -> pd.DataFrame: """Create sample voltage measurements.""" return pd.DataFrame( { "timestamp": [0.0, 100.0, 200.0, 300.0, 400.0], "parameter": [ "output_voltage", "output_voltage", "output_voltage", "output_voltage", "output_voltage", ], "value": [3.300, 3.298, 3.295, 3.290, 3.285], "unit": ["V", "V", "V", "V", "V"], "temperature": [-40.0, 0.0, 25.0, 50.0, 85.0], } ) def test_generator_initialisation(self) -> None: """Test chart generator initialisation.""" generator = ChartGenerator(dpi=200) assert generator.dpi == 200 def test_generate_voltage_vs_temperature( self, generator: ChartGenerator, voltage_measurements: pd.DataFrame ) -> None: """Test generating voltage vs temperature chart.""" chart_b64 = generator.generate_voltage_vs_temperature(voltage_measurements) # Should be valid base64 assert isinstance(chart_b64, str) assert len(chart_b64) > 100 # Should have meaningful content # Should decode to PNG image decoded = base64.b64decode(chart_b64) assert decoded[:8] == b"\x89PNG\r\n\x1a\n" # PNG magic bytes def test_generate_voltage_vs_temperature_no_data( self, generator: ChartGenerator ) -> None: """Test that error is raised with no voltage data.""" empty_df = pd.DataFrame( { "timestamp": [0.0], "parameter": ["other_param"], "value": [1.0], "unit": ["X"], "temperature": [25.0], } ) with pytest.raises(ChartGenerationError): generator.generate_voltage_vs_temperature(empty_df) def test_generate_results_bar_chart( self, generator: ChartGenerator, sample_results: list[TestResult] ) -> None: """Test generating results bar chart.""" chart_b64 = generator.generate_results_bar_chart(sample_results) # Should be valid base64 assert isinstance(chart_b64, str) assert len(chart_b64) > 100 # Should decode to PNG image decoded = base64.b64decode(chart_b64) assert decoded[:8] == b"\x89PNG\r\n\x1a\n" def test_generate_results_bar_chart_empty( self, generator: ChartGenerator ) -> None: """Test that error is raised with no results.""" with pytest.raises(ChartGenerationError): generator.generate_results_bar_chart([]) def test_generate_all_with_measurements( self, generator: ChartGenerator, sample_run: TestRun, sample_results: list[TestResult], voltage_measurements: pd.DataFrame, ) -> None: """Test generate_all produces expected charts.""" charts = generator.generate_all(sample_run, sample_results, voltage_measurements) # Should have both chart types assert "Voltage vs Temperature" in charts assert "Results Summary" in charts # All should be valid base64 for name, b64 in charts.items(): assert isinstance(b64, str) decoded = base64.b64decode(b64) assert decoded[:8] == b"\x89PNG\r\n\x1a\n", f"Chart {name} is not valid PNG" def test_generate_all_no_measurements( self, generator: ChartGenerator, sample_run: TestRun, sample_results: list[TestResult], ) -> None: """Test generate_all with no measurements.""" charts = generator.generate_all(sample_run, sample_results, None) # Should only have results chart assert "Voltage vs Temperature" not in charts assert "Results Summary" in charts def test_generate_all_no_results( self, generator: ChartGenerator, sample_run: TestRun, voltage_measurements: pd.DataFrame, ) -> None: """Test generate_all with no results.""" charts = generator.generate_all(sample_run, [], voltage_measurements) # Should only have voltage chart assert "Voltage vs Temperature" in charts assert "Results Summary" not in charts def test_generate_all_empty( self, generator: ChartGenerator, sample_run: TestRun ) -> None: """Test generate_all with no data.""" charts = generator.generate_all(sample_run, [], None) # Should be empty assert charts == {} def test_matplotlib_lazy_load(self) -> None: """Test that matplotlib is lazy loaded.""" generator = ChartGenerator() # _plt should be None before first use assert generator._plt is None # After calling _get_matplotlib, it should be loaded plt, mpl = generator._get_matplotlib() assert generator._plt is not None assert plt is not None def test_dpi_affects_output_size(self) -> None: """Test that higher DPI produces larger output.""" low_dpi = ChartGenerator(dpi=50) high_dpi = ChartGenerator(dpi=150) results = [ TestResult( id="result-1", test_run_id="12345678-1234-1234-1234-123456789abc", parameter="test", value=1.0, unit="X", measured_at=datetime(2024, 1, 15, 10, 35, 0), ), ] low_chart = low_dpi.generate_results_bar_chart(results) high_chart = high_dpi.generate_results_bar_chart(results) # Higher DPI should produce larger image assert len(high_chart) > len(low_chart)