|
7 | 7 | from botocore.stub import Stubber |
8 | 8 |
|
9 | 9 | from t4_lambda_pkgevents import ( |
10 | | - EventsQueue, |
11 | | - PutEventsException, |
| 10 | + PUT_EVENTS_MAX_ENTRIES, |
12 | 11 | handler, |
13 | 12 | pkg_created_event, |
| 13 | + publish, |
14 | 14 | s3, |
15 | 15 | ) |
16 | 16 |
|
@@ -146,44 +146,161 @@ def test_pkg_created_event(pointer_file): |
146 | 146 | stubber.assert_no_pending_responses() |
147 | 147 |
|
148 | 148 |
|
149 | | -@mock.patch("t4_lambda_pkgevents.EventsQueue.flush") |
150 | | -@mock.patch("t4_lambda_pkgevents.EventsQueue.append") |
| 149 | +@mock.patch("t4_lambda_pkgevents.publish", return_value=frozenset()) |
151 | 150 | @mock.patch("t4_lambda_pkgevents.pkg_created_event", wraps=str) |
152 | | -def test_handler(pkg_created_event_mock, queue_append_mock, queue_flush_mock): |
| 151 | +def test_handler(pkg_created_event_mock, publish_mock): |
153 | 152 | event = { |
154 | 153 | 'Records': [ |
155 | 154 | { |
| 155 | + 'messageId': f'message-{idx}', |
156 | 156 | 'body': json.dumps( |
157 | 157 | { |
158 | 158 | 'Records': records |
159 | 159 | } |
160 | 160 | ) |
161 | 161 | } |
162 | | - for records in ( |
163 | | - (0, 1), |
164 | | - (2, 3, 4), |
165 | | - (5,) |
| 162 | + for idx, records in enumerate( |
| 163 | + ( |
| 164 | + (0, 1), |
| 165 | + (2, 3, 4), |
| 166 | + (5,) |
| 167 | + ) |
166 | 168 | ) |
167 | 169 | ] |
168 | 170 | } |
169 | | - handler(event, None) |
| 171 | + assert handler(event, None) == {'batchItemFailures': []} |
170 | 172 | assert pkg_created_event_mock.call_args_list == [((x,),) for x in range(6)] |
171 | | - assert queue_append_mock.call_args_list == [((str(x),),) for x in range(6)] |
172 | | - queue_flush_mock.assert_called_once_with() |
| 173 | + publish_mock.assert_called_once_with( |
| 174 | + [ |
| 175 | + (f'message-{idx}', str(x)) |
| 176 | + for x, idx in zip(range(6), (0, 0, 1, 1, 1, 2), strict=True) |
| 177 | + ] |
| 178 | + ) |
| 179 | + |
| 180 | + |
| 181 | +@mock.patch("t4_lambda_pkgevents.logger") |
| 182 | +@mock.patch("t4_lambda_pkgevents.publish", return_value=frozenset()) |
| 183 | +@mock.patch("t4_lambda_pkgevents.pkg_created_event", wraps=str) |
| 184 | +def test_handler_drops_test_events_silently( |
| 185 | + pkg_created_event_mock, publish_mock, logger_mock |
| 186 | +): |
| 187 | + test_event_body = json.dumps( |
| 188 | + { |
| 189 | + 'Service': 'Amazon S3', |
| 190 | + 'Event': 's3:TestEvent', |
| 191 | + 'Time': '2026-07-30T00:38:18.000Z', |
| 192 | + 'Bucket': 'test-bucket', |
| 193 | + 'RequestId': 'AAAAAAAAAAAAAAAA', |
| 194 | + 'HostId': 'aaaaaaaaaaaaaaaa', |
| 195 | + } |
| 196 | + ) |
| 197 | + event = { |
| 198 | + 'Records': [ |
| 199 | + {'messageId': 'message-0', 'body': json.dumps({'Records': (0, 1)})}, |
| 200 | + {'messageId': 'message-1', 'body': test_event_body}, |
| 201 | + {'messageId': 'message-2', 'body': json.dumps({'Records': (2,)})}, |
| 202 | + ] |
| 203 | + } |
| 204 | + assert handler(event, None) == {'batchItemFailures': []} |
| 205 | + assert pkg_created_event_mock.call_args_list == [((x,),) for x in range(3)] |
| 206 | + publish_mock.assert_called_once_with( |
| 207 | + [ |
| 208 | + (message_id, str(x)) |
| 209 | + for x, message_id in zip(range(3), ('message-0', 'message-0', 'message-2'), strict=True) |
| 210 | + ] |
| 211 | + ) |
| 212 | + # a test event arrives on every bucket add, it must not be logged |
| 213 | + logger_mock.warning.assert_not_called() |
| 214 | + logger_mock.exception.assert_not_called() |
| 215 | + |
| 216 | + |
| 217 | +@pytest.mark.parametrize( |
| 218 | + 'bad_body', |
| 219 | + ( |
| 220 | + json.dumps({'Event': 'unexpected'}), |
| 221 | + 'not JSON', |
| 222 | + 'null', |
| 223 | + '[]', |
| 224 | + '"s3:TestEvent"', |
| 225 | + ), |
| 226 | +) |
| 227 | +@mock.patch("t4_lambda_pkgevents.publish", return_value=frozenset()) |
| 228 | +@mock.patch("t4_lambda_pkgevents.pkg_created_event", wraps=str) |
| 229 | +def test_handler_reports_messages_without_records_as_failed( |
| 230 | + pkg_created_event_mock, publish_mock, bad_body |
| 231 | +): |
| 232 | + event = { |
| 233 | + 'Records': [ |
| 234 | + {'messageId': 'message-0', 'body': json.dumps({'Records': (0,)})}, |
| 235 | + {'messageId': 'message-1', 'body': bad_body}, |
| 236 | + ] |
| 237 | + } |
| 238 | + assert handler(event, None) == {'batchItemFailures': [{'itemIdentifier': 'message-1'}]} |
| 239 | + assert pkg_created_event_mock.call_args_list == [((0,),)] |
| 240 | + publish_mock.assert_called_once_with([('message-0', '0')]) |
173 | 241 |
|
174 | 242 |
|
175 | | -@pytest.mark.parametrize('failed_count', (0, 1)) |
176 | | -def test_queue(failed_count): |
| 243 | +@mock.patch("t4_lambda_pkgevents.publish", return_value=frozenset()) |
| 244 | +@mock.patch("t4_lambda_pkgevents.pkg_created_event") |
| 245 | +def test_handler_reports_message_with_failing_event( |
| 246 | + pkg_created_event_mock, publish_mock |
| 247 | +): |
| 248 | + pkg_created_event_mock.side_effect = (None, Exception('boom'), None) |
| 249 | + event = { |
| 250 | + 'Records': [ |
| 251 | + # the event after the failing one must not be processed |
| 252 | + {'messageId': 'message-0', 'body': json.dumps({'Records': (0, 1, 2)})}, |
| 253 | + {'messageId': 'message-1', 'body': json.dumps({'Records': (3,)})}, |
| 254 | + ] |
| 255 | + } |
| 256 | + assert handler(event, None) == {'batchItemFailures': [{'itemIdentifier': 'message-0'}]} |
| 257 | + assert pkg_created_event_mock.call_args_list == [((x,),) for x in (0, 1, 3)] |
| 258 | + publish_mock.assert_called_once_with([]) |
| 259 | + |
| 260 | + |
| 261 | +@mock.patch("t4_lambda_pkgevents.publish", return_value=frozenset({'message-0'})) |
| 262 | +@mock.patch("t4_lambda_pkgevents.pkg_created_event", wraps=str) |
| 263 | +def test_handler_reports_publish_failures( |
| 264 | + pkg_created_event_mock, publish_mock |
| 265 | +): |
| 266 | + event = { |
| 267 | + 'Records': [ |
| 268 | + {'messageId': 'message-0', 'body': json.dumps({'Records': (0,)})}, |
| 269 | + ] |
| 270 | + } |
| 271 | + assert handler(event, None) == {'batchItemFailures': [{'itemIdentifier': 'message-0'}]} |
| 272 | + |
| 273 | + |
| 274 | +def test_publish_success(): |
177 | 275 | with mock.patch("t4_lambda_pkgevents.event_bridge.put_events") as put_events_mock: |
178 | | - put_events_mock.return_value = {'FailedEntryCount': failed_count} |
179 | | - q = EventsQueue() |
180 | | - for x in range(EventsQueue.MAX_SIZE - 1): |
181 | | - q.append(x) |
182 | | - put_events_mock.assert_not_called() |
183 | | - |
184 | | - if failed_count: |
185 | | - with pytest.raises(PutEventsException): |
186 | | - q.append(EventsQueue.MAX_SIZE - 1) |
187 | | - else: |
188 | | - q.append(EventsQueue.MAX_SIZE - 1) |
189 | | - put_events_mock.assert_called_once_with(Entries=list(range(EventsQueue.MAX_SIZE))) |
| 276 | + put_events_mock.return_value = {'FailedEntryCount': 0} |
| 277 | + entries = [(f'message-{x}', x) for x in range(PUT_EVENTS_MAX_ENTRIES + 1)] |
| 278 | + |
| 279 | + assert publish(entries) == set() |
| 280 | + assert put_events_mock.call_args_list == [ |
| 281 | + mock.call(Entries=list(range(PUT_EVENTS_MAX_ENTRIES))), |
| 282 | + mock.call(Entries=[PUT_EVENTS_MAX_ENTRIES]), |
| 283 | + ] |
| 284 | + |
| 285 | + |
| 286 | +def test_publish_nothing(): |
| 287 | + with mock.patch("t4_lambda_pkgevents.event_bridge.put_events") as put_events_mock: |
| 288 | + assert publish([]) == set() |
| 289 | + put_events_mock.assert_not_called() |
| 290 | + |
| 291 | + |
| 292 | +def test_publish_failed_entries(): |
| 293 | + with mock.patch("t4_lambda_pkgevents.event_bridge.put_events") as put_events_mock: |
| 294 | + first_entries = [{'EventId': str(x)} for x in range(PUT_EVENTS_MAX_ENTRIES)] |
| 295 | + first_entries[3] = {'ErrorCode': 'ThrottlingException', 'ErrorMessage': 'try later'} |
| 296 | + put_events_mock.side_effect = ( |
| 297 | + {'FailedEntryCount': 1, 'Entries': first_entries}, |
| 298 | + { |
| 299 | + 'FailedEntryCount': 1, |
| 300 | + 'Entries': [{'ErrorCode': 'InternalFailure', 'ErrorMessage': 'oops'}], |
| 301 | + }, |
| 302 | + ) |
| 303 | + entries = [(f'message-{x}', x) for x in range(PUT_EVENTS_MAX_ENTRIES + 1)] |
| 304 | + |
| 305 | + assert publish(entries) == {'message-3', f'message-{PUT_EVENTS_MAX_ENTRIES}'} |
| 306 | + assert put_events_mock.call_count == 2 |
0 commit comments