"""Unit tests for SSM-backed state helpers.""" import json import pytest from botocore.exceptions import ClientError import config from src import state @pytest.fixture() def mock_ssm(mocker): return mocker.patch('src.state._ssm') def _ssm_param(value: str): return {'Parameter': {'Value': value}} def _client_error(code: str): return ClientError({'Error': {'Code': code, 'Message': code}}, 'GetParameter') def test_load_returns_state_when_param_exists(mock_ssm): mock_ssm.get_parameter.return_value = _ssm_param('{"pending_date": "2026-06-29"}') assert state.load() == {'pending_date': '2026-06-29'} def test_load_returns_empty_dict_when_param_not_found(mock_ssm): mock_ssm.get_parameter.side_effect = _client_error('ParameterNotFound') assert state.load() == {} def test_load_returns_empty_dict_on_invalid_json(mock_ssm): """Corrupted SSM value (e.g. manual edit) must not crash the Lambda.""" mock_ssm.get_parameter.return_value = _ssm_param('not valid json {{{') assert state.load() == {} @pytest.mark.parametrize('raw', ['null', '[]', '"a string"', '42']) def test_load_returns_empty_dict_on_non_dict_json(mock_ssm, raw): """Valid JSON that is not an object must not crash downstream .get() calls.""" mock_ssm.get_parameter.return_value = _ssm_param(raw) assert state.load() == {} def test_load_reraises_unexpected_ssm_errors(mock_ssm): mock_ssm.get_parameter.side_effect = _client_error('AccessDeniedException') with pytest.raises(ClientError): state.load() def test_save_puts_json_to_ssm(mock_ssm): state.save({'alerted': True}) mock_ssm.put_parameter.assert_called_once_with( Name=config.SSM_STATE_PARAM, Value=json.dumps({'alerted': True}), Type='String', Overwrite=True, )