Skip to content

Commit d1c9cd7

Browse files
committed
CCM-12616: mesh-download lambda
1 parent bc10540 commit d1c9cd7

File tree

10 files changed

+836
-11
lines changed

10 files changed

+836
-11
lines changed

.gitleaksignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ f8546e35b77b69ba7b15dbe3174d2d7e375200ef:utils/utils/src/__tests__/key-generatio
1919
f8546e35b77b69ba7b15dbe3174d2d7e375200ef:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:23
2020
f8546e35b77b69ba7b15dbe3174d2d7e375200ef:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:30
2121
f8546e35b77b69ba7b15dbe3174d2d7e375200ef:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:46
22+
d1c0a37078cbed4fbedae044e5cbafac71717af0:utils/utils/src/__tests__/key-generation/validate-private-key.test.ts:private-key:7
23+
d1c0a37078cbed4fbedae044e5cbafac71717af0:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:23
24+
d1c0a37078cbed4fbedae044e5cbafac71717af0:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:30
25+
d1c0a37078cbed4fbedae044e5cbafac71717af0:utils/utils/src/__tests__/key-generation/get-private-key.test.ts:private-key:46
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
"""
2+
Tests for Lambda handler
3+
Following the pattern from mesh-poll tests
4+
"""
5+
import pytest
6+
from unittest.mock import Mock, patch, MagicMock
7+
8+
9+
def setup_mocks():
10+
"""
11+
Create all mock objects needed for handler testing
12+
"""
13+
mock_context = Mock()
14+
15+
mock_config = MagicMock()
16+
mock_config.mesh_client = Mock()
17+
18+
mock_processor = Mock()
19+
mock_processor.process_sqs_message = Mock()
20+
21+
return (
22+
mock_context,
23+
mock_config,
24+
mock_processor
25+
)
26+
27+
28+
def create_sqs_event(num_records=1, event_source='aws:sqs'):
29+
"""
30+
Create a mock SQS event for testing
31+
"""
32+
records = []
33+
for i in range(num_records):
34+
records.append({
35+
'messageId': f'msg-{i}',
36+
'eventSource': event_source,
37+
'body': '{"detail": {"data": {"meshMessageId": "test_id"}}}'
38+
})
39+
40+
return {'Records': records}
41+
42+
43+
class TestHandler:
44+
"""Test suite for Lambda handler"""
45+
46+
@patch('src.handler.Config')
47+
@patch('src.handler.MeshDownloadProcessor')
48+
def test_handler_success_single_message(self, mock_processor_class, mock_config_class):
49+
"""Test successful handler execution with single SQS message"""
50+
from src.handler import handler
51+
52+
(mock_context, mock_config, mock_processor) = setup_mocks()
53+
54+
mock_config_class.return_value.__enter__.return_value = mock_config
55+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
56+
mock_processor_class.return_value = mock_processor
57+
58+
event = create_sqs_event(num_records=1)
59+
60+
result = handler(event, mock_context)
61+
62+
# Verify Config was created and used as context manager
63+
mock_config_class.assert_called_once()
64+
mock_config_class.return_value.__enter__.assert_called_once()
65+
66+
# Verify MeshDownloadProcessor was created with correct parameters
67+
mock_processor_class.assert_called_once()
68+
call_kwargs = mock_processor_class.call_args[1]
69+
assert call_kwargs['mesh_client'] == mock_config.mesh_client
70+
71+
# Verify process_sqs_message was called
72+
mock_processor.process_sqs_message.assert_called_once()
73+
74+
# Verify return value (no failures)
75+
assert result == {"batchItemFailures": []}
76+
77+
@patch('src.handler.Config')
78+
@patch('src.handler.MeshDownloadProcessor')
79+
def test_handler_success_multiple_messages(self, mock_processor_class, mock_config_class):
80+
"""Test successful handler execution with multiple SQS messages"""
81+
from src.handler import handler
82+
83+
(mock_context, mock_config, mock_processor) = setup_mocks()
84+
85+
mock_config_class.return_value.__enter__.return_value = mock_config
86+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
87+
mock_processor_class.return_value = mock_processor
88+
89+
event = create_sqs_event(num_records=3)
90+
91+
result = handler(event, mock_context)
92+
93+
# Verify process_sqs_message was called 3 times
94+
assert mock_processor.process_sqs_message.call_count == 3
95+
96+
# Verify return value (no failures)
97+
assert result == {"batchItemFailures": []}
98+
99+
@patch('src.handler.Config')
100+
@patch('src.handler.MeshDownloadProcessor')
101+
def test_handler_config_cleanup_on_success(self, mock_processor_class, mock_config_class):
102+
"""Test that Config context manager cleanup is called on success"""
103+
from src.handler import handler
104+
105+
(mock_context, mock_config, mock_processor) = setup_mocks()
106+
107+
mock_config_class.return_value.__enter__.return_value = mock_config
108+
mock_exit = Mock(return_value=None)
109+
mock_config_class.return_value.__exit__ = mock_exit
110+
mock_processor_class.return_value = mock_processor
111+
112+
event = create_sqs_event(num_records=1)
113+
114+
handler(event, mock_context)
115+
116+
# Verify __exit__ was called (cleanup happened)
117+
mock_exit.assert_called_once()
118+
# __exit__ should be called with (None, None, None) on success
119+
assert mock_exit.call_args[0] == (None, None, None)
120+
121+
@patch('src.handler.Config')
122+
@patch('src.handler.MeshDownloadProcessor')
123+
def test_handler_partial_batch_failure(self, mock_processor_class, mock_config_class):
124+
"""Test handler handles partial batch failures correctly"""
125+
from src.handler import handler
126+
127+
(mock_context, mock_config, mock_processor) = setup_mocks()
128+
129+
mock_config_class.return_value.__enter__.return_value = mock_config
130+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
131+
mock_processor_class.return_value = mock_processor
132+
133+
# Make second message fail
134+
mock_processor.process_sqs_message.side_effect = [
135+
None, # First succeeds
136+
Exception("Test error"), # Second fails
137+
None # Third succeeds
138+
]
139+
140+
event = create_sqs_event(num_records=3)
141+
142+
result = handler(event, mock_context)
143+
144+
# Verify only the failed message is in batch item failures
145+
assert len(result["batchItemFailures"]) == 1
146+
assert result["batchItemFailures"][0]["itemIdentifier"] == "msg-1"
147+
148+
@patch('src.handler.Config')
149+
@patch('src.handler.MeshDownloadProcessor')
150+
def test_handler_skips_non_sqs_records(self, mock_processor_class, mock_config_class):
151+
"""Test handler skips records that are not from SQS"""
152+
from src.handler import handler
153+
154+
(mock_context, mock_config, mock_processor) = setup_mocks()
155+
156+
mock_config_class.return_value.__enter__.return_value = mock_config
157+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
158+
mock_processor_class.return_value = mock_processor
159+
160+
event = create_sqs_event(num_records=1, event_source='aws:dynamodb')
161+
162+
result = handler(event, mock_context)
163+
164+
# Verify process_sqs_message was NOT called
165+
mock_processor.process_sqs_message.assert_not_called()
166+
167+
# Verify return value (no failures)
168+
assert result == {"batchItemFailures": []}
169+
170+
@patch('src.handler.Config')
171+
@patch('src.handler.MeshDownloadProcessor')
172+
def test_handler_config_cleanup_on_exception(self, mock_processor_class, mock_config_class):
173+
"""Test that Config context manager cleanup is called even on exception"""
174+
from src.handler import handler
175+
176+
(mock_context, mock_config, mock_processor) = setup_mocks()
177+
178+
# Make config.__enter__ raise an exception
179+
test_exception = RuntimeError("Config error")
180+
mock_config_class.return_value.__enter__.side_effect = test_exception
181+
mock_exit = Mock(return_value=None)
182+
mock_config_class.return_value.__exit__ = mock_exit
183+
184+
event = create_sqs_event(num_records=1)
185+
186+
# Handler should raise the exception
187+
with pytest.raises(RuntimeError, match="Config error"):
188+
handler(event, mock_context)
189+
190+
@patch('src.handler.Config')
191+
@patch('src.handler.MeshDownloadProcessor')
192+
def test_handler_returns_empty_failures_on_empty_event(self, mock_processor_class, mock_config_class):
193+
"""Test handler handles empty event gracefully"""
194+
from src.handler import handler
195+
196+
(mock_context, mock_config, mock_processor) = setup_mocks()
197+
198+
mock_config_class.return_value.__enter__.return_value = mock_config
199+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
200+
mock_processor_class.return_value = mock_processor
201+
202+
event = {'Records': []}
203+
204+
result = handler(event, mock_context)
205+
206+
# Verify process_sqs_message was NOT called
207+
mock_processor.process_sqs_message.assert_not_called()
208+
209+
# Verify return value
210+
assert result == {"batchItemFailures": []}
211+
212+
@patch('src.handler.Config')
213+
@patch('src.handler.MeshDownloadProcessor')
214+
def test_handler_passes_correct_parameters_to_processor(self, mock_processor_class, mock_config_class):
215+
"""Test that handler passes all required parameters to MeshDownloadProcessor"""
216+
from src.handler import handler
217+
218+
(mock_context, mock_config, mock_processor) = setup_mocks()
219+
220+
mock_mesh_client = Mock()
221+
mock_config.mesh_client = mock_mesh_client
222+
223+
mock_config_class.return_value.__enter__.return_value = mock_config
224+
mock_config_class.return_value.__exit__ = Mock(return_value=None)
225+
mock_processor_class.return_value = mock_processor
226+
227+
event = create_sqs_event(num_records=1)
228+
229+
handler(event, mock_context)
230+
231+
# Verify all parameters are passed correctly
232+
mock_processor_class.assert_called_once()
233+
call_kwargs = mock_processor_class.call_args[1]
234+
235+
# Check each parameter individually
236+
assert call_kwargs['mesh_client'] == mock_mesh_client
237+
assert 'log' in call_kwargs

0 commit comments

Comments
 (0)