import io import sys from datetime import UTC, datetime from unittest.mock import MagicMock, Mock, patch import pytest from production_analytics.calculations.material_consumption import MaterialIntegrationState from production_analytics.enlyze.exploration import ConfigurationError from production_analytics.enlyze.gateway import EnlyzeProductionRun from production_analytics.service.material_polling import MaterialPollResult from production_analytics.service.material_runner import MaterialPollingRunner from production_analytics.service.postgres_material import ( PostgresMaterialSnapshotWriter, PostgresSettings, ) ENV = dict(POSTGRES_HOST='localhost', POSTGRES_PORT='5432', POSTGRES_DB='analytics', POSTGRES_USER='writer', POSTGRES_PASSWORD='secret-password') NOW = datetime(2026, 1, 1, tzinfo=UTC) @pytest.mark.parametrize('key', ENV) @pytest.mark.parametrize('value', [None, '', ' ', '\x00']) def test_required_settings(key, value): environment = dict(ENV) if value is None: del environment[key] else: environment[key] = value with pytest.raises(ConfigurationError, match=key) as error: PostgresSettings.from_environment(environment) assert 'secret-password' not in str(error.value) @pytest.mark.parametrize('port', ['0', '-1', '65536', '1.5', 'secret-password']) def test_invalid_port(port): with pytest.raises(ConfigurationError, match='POSTGRES_PORT') as error: PostgresSettings.from_environment(dict(ENV, POSTGRES_PORT=port)) assert str(error.value) == 'POSTGRES_PORT must be an integer from 1 to 65535' def test_valid_settings_and_password_repr(): settings = PostgresSettings.from_environment(ENV) assert (settings.host, settings.port, settings.dbname, settings.user) == ( 'localhost', 5432, 'analytics', 'writer', ) assert settings.password == 'secret-password' assert settings.password not in repr(settings) def test_parameterized_insert_and_connection_lifecycle(): driver = MagicMock() connection = driver.connect.return_value.__enter__.return_value writer = PostgresMaterialSnapshotWriter(PostgresSettings.from_environment(ENV)) fields = dict(timestamp=NOW, calculation_id='calc', machine_id='machine', production_order=" 00842'; DROP TABLE x; -- ", run_id='run', consumption_kg=12.5) with patch.dict(sys.modules, psycopg=driver): writer.write(**fields) sql, parameters = connection.execute.call_args.args assert sql.count('%s') == 6 assert fields['production_order'] not in sql assert parameters == tuple(fields.values()) assert 'ON CONFLICT' in sql connection.execute.assert_called_once() driver.connect.assert_called_once_with( host='localhost', port=5432, dbname='analytics', user='writer', password='secret-password', connect_timeout=10, options='-c statement_timeout=10000', ) driver.connect.return_value.__exit__.assert_called_once_with(None, None, None) def test_writer_failure_propagates_and_next_write_reconnects(): driver = MagicMock() driver.connect.return_value.__enter__.return_value.execute.side_effect = [ RuntimeError('secret'), None, ] writer = PostgresMaterialSnapshotWriter(PostgresSettings.from_environment(ENV)) fields = dict(timestamp=NOW, calculation_id='calc', machine_id='machine', production_order='order', run_id='run', consumption_kg=0) with patch.dict(sys.modules, psycopg=driver): with pytest.raises(RuntimeError): writer.write(**fields) writer.write(**fields) assert driver.connect.call_count == 2 assert driver.connect.return_value.__exit__.call_count == 2 @pytest.mark.parametrize('consumption', [0, 12.5]) def test_runner_snapshot_fields_and_none(consumption): result = MaterialPollResult( EnlyzeProductionRun('run', 'machine', None, ' 00842 ', NOW, None), MaterialIntegrationState(consumption, 20), ) writer = Mock() output = io.StringIO() MaterialPollingRunner( Mock(poll_once=Mock(side_effect=[None, result])), machine_id='machine', calculation_id='configured-calculation', snapshot_writer=writer, poll_interval_seconds=10, clock=lambda: NOW, sleep=Mock(side_effect=[None, KeyboardInterrupt]), stdout=output, ).run() writer.write.assert_called_once_with( timestamp=NOW, calculation_id='configured-calculation', machine_id='machine', production_order=' 00842 ', run_id='run', consumption_kg=consumption, ) assert 'no open Production Run' in output.getvalue() assert f'consumption_kg={consumption:.9f}' in output.getvalue() def test_snapshot_failure_reports_safely_and_continues(): result = MaterialPollResult( EnlyzeProductionRun('run', 'machine', None, 'order', NOW, None), MaterialIntegrationState(12.5, 20), ) service = Mock(poll_once=Mock(return_value=result)) writer = Mock(write=Mock(side_effect=[RuntimeError('secret-password arbitrary SQL'), None])) output, errors = io.StringIO(), io.StringIO() MaterialPollingRunner( service, machine_id='machine', calculation_id='calc', snapshot_writer=writer, poll_interval_seconds=10, clock=lambda: NOW, sleep=Mock(side_effect=[None, KeyboardInterrupt]), stdout=output, stderr=errors, ).run() assert service.poll_once.call_count == writer.write.call_count == 2 assert errors.getvalue().count('PostgreSQL snapshot write failed') == 1 assert 'secret-password' not in errors.getvalue() assert 'arbitrary SQL' not in errors.getvalue() assert output.getvalue().count('consumption_kg=12.500000000') == 2