"""Test ows_masters_registry model.""" import json from unittest.mock import MagicMock, patch import pytest from src.models import ows_masters_registry @pytest.mark.parametrize('status_code,text', [ (200, '{"success": true}'), (201, 'Created'), (202, 'Accepted'), ]) @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_success(mock_ows_client, status_code, text): """Test successful POST returns status, text, and empty skipped dict.""" mock_response = MagicMock() mock_response.status_code = status_code mock_response.text = text mock_ows_client.post.return_value = mock_response result_code, result_text, skipped = ows_masters_registry.post_upcs(['111', '222']) assert result_code == str(status_code) assert result_text == text assert skipped == {} mock_ows_client.post.assert_called_once_with( 'ows-masters-registry', path='/ownership/upcs', headers={'Orchard-User-Id': 'oa:179'}, json={'upcs': ['111', '222']}, ) @pytest.mark.parametrize('status_code', [401, 403, 500, 503]) @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_http_error(mock_ows_client, status_code): """Test that non-400 HTTP errors propagate via raise_for_status.""" mock_response = MagicMock() mock_response.status_code = status_code mock_response.raise_for_status.side_effect = Exception(f'{status_code} Error') mock_ows_client.post.return_value = mock_response with pytest.raises(Exception, match=f'{status_code} Error'): ows_masters_registry.post_upcs(['111']) @pytest.mark.parametrize('error_type,error_msg', [ (ConnectionError, 'Connection timeout'), (TimeoutError, 'Request timeout'), ]) @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_connection_error(mock_ows_client, error_type, error_msg): """Test that connection errors from ows_client propagate directly.""" mock_ows_client.post.side_effect = error_type(error_msg) with pytest.raises(error_type, match=error_msg): ows_masters_registry.post_upcs(['111']) @pytest.mark.parametrize('upcs,error_items,expected_skipped,expected_retry_upcs', [ ( ['111', '222', '333'], [{'upc': '222', 'error_message': 'UPC is Not for Distribution'}], {'222': 'UPC is Not for Distribution'}, ['111', '333'], ), ( ['111', '222', '333'], [ {'upc': '111', 'error_message': 'UPC is Not for Distribution'}, {'upc': '333', 'error_message': 'UPC is Not for Distribution'}, ], {'111': 'UPC is Not for Distribution', '333': 'UPC is Not for Distribution'}, ['222'], ), ( ['111', '222'], [{'upc': '111'}], {'111': 'Unknown error'}, ['222'], ), ]) @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_skips_invalid_and_retries(mock_ows_client, upcs, error_items, expected_skipped, expected_retry_upcs): """Test that invalid UPCs are skipped and valid ones are retried.""" error_response = MagicMock() error_response.status_code = 400 error_response.text = json.dumps(error_items) success_response = MagicMock() success_response.status_code = 200 success_response.text = '{"success": true}' mock_ows_client.post.side_effect = [error_response, success_response] result_code, _, skipped = ows_masters_registry.post_upcs(upcs) assert result_code == '200' assert skipped == expected_skipped assert mock_ows_client.post.call_count == 2 mock_ows_client.post.assert_called_with( 'ows-masters-registry', path='/ownership/upcs', headers={'Orchard-User-Id': 'oa:179'}, json={'upcs': expected_retry_upcs}, ) @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_all_skipped(mock_ows_client): """Test that when all UPCs are invalid, returns with all skipped and no retry.""" error_response = MagicMock() error_response.status_code = 400 error_response.text = json.dumps([ {'upc': '111', 'error_message': 'UPC is Not for Distribution'}, {'upc': '222', 'error_message': 'UPC is Not for Distribution'}, ]) mock_ows_client.post.return_value = error_response result_code, result_text, skipped = ows_masters_registry.post_upcs(['111', '222']) assert result_code == '200' assert skipped == {'111': 'UPC is Not for Distribution', '222': 'UPC is Not for Distribution'} mock_ows_client.post.assert_called_once() @patch('src.models.ows_masters_registry.ows_client') def test_post_upcs_400_unparseable_raises(mock_ows_client): """Test that a 400 with no parseable UPC info raises via raise_for_status.""" error_response = MagicMock() error_response.status_code = 400 error_response.text = 'Bad Request' error_response.raise_for_status.side_effect = Exception('400 Bad Request') mock_ows_client.post.return_value = error_response with pytest.raises(Exception, match='400 Bad Request'): ows_masters_registry.post_upcs(['111']) @pytest.mark.parametrize('batch_size,upc_count,expected_batches', [ (3, 0, 0), (3, 1, 1), (3, 3, 1), (3, 4, 2), (5, 10, 2), (5, 11, 3), ]) @patch('src.models.ows_masters_registry.post_upcs') @patch('src.models.ows_masters_registry.config') def test_update_registry_batch_boundaries(mock_config, mock_post, batch_size, upc_count, expected_batches): """Test update_registry with various batch boundary conditions.""" mock_config.MAX_UPCS_COUNT = batch_size mock_post.return_value = ('200', 'OK', {}) upcs = [str(i) for i in range(upc_count)] results, skipped = ows_masters_registry.update_registry(upcs) assert len(results) == expected_batches assert all(result == ('200', 'OK') for result in results) assert mock_post.call_count == expected_batches assert skipped == {} @pytest.mark.parametrize('failure_indices', [ [], [0], [1], [0, 2], [0, 1, 2], ]) @patch('src.models.ows_masters_registry.post_upcs') @patch('src.models.ows_masters_registry.config') def test_update_registry_mixed_results(mock_config, mock_post, failure_indices): """Test update_registry with various success/failure combinations.""" mock_config.MAX_UPCS_COUNT = 1 call_count = [0] def post_upcs_side_effect(batch): if call_count[0] in failure_indices: call_count[0] += 1 raise RuntimeError(f'Batch {call_count[0]} failed') call_count[0] += 1 return ('200', 'OK', {}) mock_post.side_effect = post_upcs_side_effect upcs = ['111', '222', '333'] results, skipped = ows_masters_registry.update_registry(upcs) assert len(results) == 3 for i, result in enumerate(results): if i in failure_indices: assert result[0] == 'ERROR' else: assert result[0] == '200' assert skipped == {} @pytest.mark.parametrize('batch_size,post_results,expected_skipped', [ ( 2, [('200', 'OK', {'111': 'UPC is Not for Distribution'}), ('200', 'OK', {'333': 'UPC is Not for Distribution'})], {'111': 'UPC is Not for Distribution', '333': 'UPC is Not for Distribution'}, ), ( 2, [('200', 'OK', {'111': 'UPC is Not for Distribution'}), ('200', 'OK', {})], {'111': 'UPC is Not for Distribution'}, ), ( 4, [('200', 'OK', {'111': 'UPC is Not for Distribution', '222': 'Invalid'})], {'111': 'UPC is Not for Distribution', '222': 'Invalid'}, ), ]) @patch('src.models.ows_masters_registry.post_upcs') @patch('src.models.ows_masters_registry.config') def test_update_registry_aggregates_skipped_upcs(mock_config, mock_post, batch_size, post_results, expected_skipped): """Test that skipped UPCs from multiple batches are aggregated into one dict.""" mock_config.MAX_UPCS_COUNT = batch_size mock_post.side_effect = post_results upcs = ['111', '222', '333', '444'] results, skipped = ows_masters_registry.update_registry(upcs) assert skipped == expected_skipped assert all(r[0] == '200' for r in results) @pytest.mark.parametrize('upc_count', [0, 1, 5, 10, 100]) @patch('src.models.ows_masters_registry.post_upcs') def test_update_registry_logging(mock_post, upc_count): """Test update_registry logs correct information.""" mock_post.return_value = ('200', 'OK', {}) upcs = [str(i) for i in range(upc_count)] with patch('src.models.ows_masters_registry.logger') as mock_logger: ows_masters_registry.update_registry(upcs) mock_logger.info.assert_called() calls = [call[0][0] for call in mock_logger.info.call_args_list] if upc_count > 0: assert any('Updating registry' in call for call in calls) assert any('Registry update complete' in call for call in calls)