|
2 | 2 | import pytest |
3 | 3 | from unittest.mock import Mock |
4 | 4 | from botocore.exceptions import ClientError |
5 | | -from mesh_download.document_store import DocumentStore, IntermediaryBodyStoreError, DocumentAlreadyExistsError |
| 5 | +from mesh_download.document_store import ( |
| 6 | + DocumentStore, |
| 7 | + IntermediaryBodyStoreError, |
| 8 | + DocumentAlreadyExistsError, |
| 9 | + DocumentAlreadyExistsInternalRetryError, |
| 10 | +) |
6 | 11 |
|
7 | 12 |
|
8 | 13 | def make_client_error(code): |
@@ -36,11 +41,12 @@ def test_store_document_success(self): |
36 | 41 | content=b'test content' |
37 | 42 | ) |
38 | 43 |
|
39 | | - assert result == 'document-reference/SENDER-001/ref-123_mesh-456' |
| 44 | + assert result == 'document-reference/SENDER-001/ref-123' |
40 | 45 | mock_s3_client.put_object.assert_called_once_with( |
41 | 46 | Bucket='test-pii-bucket', |
42 | | - Key='document-reference/SENDER-001/ref-123_mesh-456', |
| 47 | + Key='document-reference/SENDER-001/ref-123', |
43 | 48 | Body=b'test content', |
| 49 | + Metadata={'mesh_message_id': 'mesh-456'}, |
44 | 50 | IfNoneMatch='*' |
45 | 51 | ) |
46 | 52 |
|
@@ -84,21 +90,56 @@ def test_store_document_raises_error_on_non_200_response(self): |
84 | 90 | content=b'test content' |
85 | 91 | ) |
86 | 92 |
|
87 | | - def test_store_document_precondition_failed_raises_document_already_exists(self): |
88 | | - """Raises DocumentAlreadyExistsError when S3 returns PreconditionFailed (object already exists)""" |
| 93 | + def test_store_document_precondition_failed_same_mesh_message_id_raises_internal_retry(self): |
| 94 | + """Raises DocumentAlreadyExistsInternalRetryError when stored meshMessageId matches incoming (internal retry)""" |
89 | 95 | mock_s3_client = Mock() |
90 | 96 | mock_s3_client.put_object.side_effect = make_client_error('PreconditionFailed') |
| 97 | + mock_s3_client.head_object.return_value = { |
| 98 | + 'Metadata': {'mesh_message_id': 'mesh-456'} |
| 99 | + } |
91 | 100 |
|
92 | 101 | config = Mock() |
93 | 102 | config.s3_client = mock_s3_client |
94 | 103 | config.transactional_data_bucket = 'test-pii-bucket' |
95 | 104 |
|
96 | 105 | store = DocumentStore(config) |
97 | 106 |
|
98 | | - with pytest.raises(DocumentAlreadyExistsError, match='document-reference/SENDER-001/ref-123_mesh-456'): |
| 107 | + with pytest.raises(DocumentAlreadyExistsInternalRetryError, match='document-reference/SENDER-001/ref-123'): |
99 | 108 | store.store_document( |
100 | 109 | sender_id='SENDER-001', |
101 | 110 | message_reference='ref-123', |
102 | 111 | mesh_message_id='mesh-456', |
103 | 112 | content=b'test content' |
104 | 113 | ) |
| 114 | + |
| 115 | + mock_s3_client.head_object.assert_called_once_with( |
| 116 | + Bucket='test-pii-bucket', |
| 117 | + Key='document-reference/SENDER-001/ref-123' |
| 118 | + ) |
| 119 | + |
| 120 | + def test_store_document_precondition_failed_different_mesh_message_id_raises_trust_duplicate(self): |
| 121 | + """Raises DocumentAlreadyExistsError when stored meshMessageId differs from incoming (trust duplicate)""" |
| 122 | + mock_s3_client = Mock() |
| 123 | + mock_s3_client.put_object.side_effect = make_client_error('PreconditionFailed') |
| 124 | + mock_s3_client.head_object.return_value = { |
| 125 | + 'Metadata': {'mesh_message_id': 'original-mesh-id'} |
| 126 | + } |
| 127 | + |
| 128 | + config = Mock() |
| 129 | + config.s3_client = mock_s3_client |
| 130 | + config.transactional_data_bucket = 'test-pii-bucket' |
| 131 | + |
| 132 | + store = DocumentStore(config) |
| 133 | + |
| 134 | + with pytest.raises(DocumentAlreadyExistsError, match='document-reference/SENDER-001/ref-123'): |
| 135 | + store.store_document( |
| 136 | + sender_id='SENDER-001', |
| 137 | + message_reference='ref-123', |
| 138 | + mesh_message_id='new-mesh-id', |
| 139 | + content=b'test content' |
| 140 | + ) |
| 141 | + |
| 142 | + mock_s3_client.head_object.assert_called_once_with( |
| 143 | + Bucket='test-pii-bucket', |
| 144 | + Key='document-reference/SENDER-001/ref-123' |
| 145 | + ) |
0 commit comments