Skip to content

Commit bc10540

Browse files
committed
CCM-12616: add senderId to CloudEvent data
1 parent 8631401 commit bc10540

File tree

5 files changed

+71
-90
lines changed

5 files changed

+71
-90
lines changed

lambdas/mesh-poll/src/__tests__/test_client_lookup.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import json
55
import pytest
66
from unittest.mock import Mock, call
7+
from src.client_lookup import ClientLookup
78

89

910
def setup_mocks():
10-
1111
ssm = Mock()
1212

1313
config = Mock()
@@ -19,7 +19,6 @@ def setup_mocks():
1919

2020

2121
def create_client_parameter(client_id, mailbox_id):
22-
2322
return {
2423
"Name": f"/dl/test/mesh/clients/{client_id}",
2524
"Value": json.dumps({
@@ -35,7 +34,6 @@ class TestClientLookup:
3534

3635
def test_load_valid_senders_single_page(self):
3736
"""Test loading valid senders from SSM (single page)"""
38-
from src.client_lookup import ClientLookup
3937

4038
ssm, config, logger = setup_mocks()
4139

@@ -60,7 +58,6 @@ def test_load_valid_senders_single_page(self):
6058

6159
def test_load_valid_senders_multiple_pages(self):
6260
"""Test loading valid senders from SSM with pagination"""
63-
from src.client_lookup import ClientLookup
6461

6562
ssm, config, logger = setup_mocks()
6663

@@ -85,17 +82,14 @@ def test_load_valid_senders_multiple_pages(self):
8582
assert ssm.get_parameters_by_path.call_count == 2
8683
ssm.get_parameters_by_path.assert_has_calls([
8784
call(Path="/dl/test/mesh/clients/", WithDecryption=True),
88-
call(Path="/dl/test/mesh/clients/",
89-
WithDecryption=True, NextToken="token123")
90-
])
85+
call(Path="/dl/test/mesh/clients/", WithDecryption=True, NextToken="token123")
86+
], any_order=False)
9187
assert client_lookup.is_valid_sender("MAILBOX_001")
9288
assert client_lookup.is_valid_sender("MAILBOX_002")
9389
assert client_lookup.is_valid_sender("MAILBOX_003")
9490

9591
def test_is_valid_sender_case_insensitive(self):
9692
"""Test that sender validation is case-insensitive"""
97-
from src.client_lookup import ClientLookup
98-
9993
ssm, config, logger = setup_mocks()
10094

10195
ssm.get_parameters_by_path.return_value = {
@@ -113,8 +107,6 @@ def test_is_valid_sender_case_insensitive(self):
113107

114108
def test_is_valid_sender_returns_false_for_empty_mailbox_id(self):
115109
"""Test that empty mailbox IDs are rejected"""
116-
from src.client_lookup import ClientLookup
117-
118110
ssm, config, logger = setup_mocks()
119111

120112
ssm.get_parameters_by_path.return_value = {
@@ -130,8 +122,6 @@ def test_is_valid_sender_returns_false_for_empty_mailbox_id(self):
130122

131123
def test_load_valid_senders_handles_malformed_json(self):
132124
"""Test that malformed JSON in parameters is handled gracefully"""
133-
from src.client_lookup import ClientLookup
134-
135125
ssm, config, logger = setup_mocks()
136126

137127
ssm.get_parameters_by_path.return_value = {
@@ -149,12 +139,10 @@ def test_load_valid_senders_handles_malformed_json(self):
149139

150140
assert client_lookup.is_valid_sender("MAILBOX_001")
151141
assert client_lookup.is_valid_sender("MAILBOX_003")
152-
logger.warn.assert_called()
142+
assert logger.warn.called
153143

154144
def test_load_valid_senders_handles_missing_mailbox_id(self):
155145
"""Test that parameters without meshMailboxSenderId are skipped"""
156-
from src.client_lookup import ClientLookup
157-
158146
ssm, config, logger = setup_mocks()
159147

160148
ssm.get_parameters_by_path.return_value = {
@@ -179,8 +167,6 @@ def test_load_valid_senders_handles_missing_mailbox_id(self):
179167

180168
def test_load_valid_senders_handles_empty_mailbox_id(self):
181169
"""Test that empty meshMailboxSenderId values are skipped"""
182-
from src.client_lookup import ClientLookup
183-
184170
ssm, config, logger = setup_mocks()
185171

186172
ssm.get_parameters_by_path.return_value = {
@@ -206,8 +192,6 @@ def test_load_valid_senders_handles_empty_mailbox_id(self):
206192

207193
def test_load_valid_senders_with_trailing_slash_in_path(self):
208194
"""Test that paths with trailing slashes are handled correctly"""
209-
from src.client_lookup import ClientLookup
210-
211195
ssm, config, logger = setup_mocks()
212196
config.ssm_clients_parameter_path = "/dl/test/mesh/clients/" # Trailing slash
213197

@@ -227,8 +211,6 @@ def test_load_valid_senders_with_trailing_slash_in_path(self):
227211

228212
def test_load_valid_senders_handles_empty_response(self):
229213
"""Test that empty SSM response is handled correctly"""
230-
from src.client_lookup import ClientLookup
231-
232214
ssm, config, logger = setup_mocks()
233215

234216
ssm.get_parameters_by_path.return_value = {

lambdas/mesh-poll/src/__tests__/test_processor.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66
from unittest.mock import Mock, call, patch
77
from mesh_client import MeshClient
8+
from src.processor import MeshMessageProcessor
89

910

1011
def setup_mocks():
@@ -53,22 +54,20 @@ def setup_message_data(test_id="0"):
5354

5455

5556
def get_remaining_time_in_millis():
56-
return 1000000
57+
return 1000
5758

5859

5960
def get_remaining_time_in_millis_near_timeout():
6061
return 100
6162

6263

64+
@patch('src.processor.os.getenv')
65+
@patch('src.processor.EventPublisher')
6366
class TestMeshMessageProcessor:
6467
"""Test suite for MeshMessageProcessor"""
6568

66-
@patch('src.processor.os.getenv')
67-
@patch('src.processor.EventPublisher')
6869
def test_process_messages_iterates_through_inbox(self, mock_event_publisher_class, mock_getenv):
6970
"""Test that processor iterates through all messages in MESH inbox"""
70-
from src.processor import MeshMessageProcessor
71-
7271
(config, client_lookup, mesh_client, log, polling_metric) = setup_mocks()
7372
message1 = setup_message_data("1")
7473
message2 = setup_message_data("2")
@@ -96,19 +95,13 @@ def test_process_messages_iterates_through_inbox(self, mock_event_publisher_clas
9695

9796
mesh_client.handshake.assert_called_once()
9897
assert mesh_client.iterate_all_messages.call_count == 2
99-
# Messages are acknowledged only on auth errors, not on success
10098
polling_metric.record.assert_called_once()
10199

102-
@patch('src.processor.os.getenv')
103-
@patch('src.processor.EventPublisher')
104100
def test_process_messages_stops_near_timeout(self, mock_event_publisher_class, mock_getenv):
105101
"""Test that processor stops processing when near timeout"""
106-
from src.processor import MeshMessageProcessor
107-
108102
(config, client_lookup, mesh_client, log, polling_metric) = setup_mocks()
109103
message1 = setup_message_data("1")
110104

111-
# Mock environment variables
112105
mock_getenv.side_effect = lambda key, default='': {
113106
'EVENT_PUBLISHER_EVENT_BUS_ARN': 'arn:aws:events:eu-west-2:123456789012:event-bus/test',
114107
'EVENT_PUBLISHER_DLQ_URL': 'https://sqs.eu-west-2.amazonaws.com/123456789012/test-dlq'
@@ -127,20 +120,14 @@ def test_process_messages_stops_near_timeout(self, mock_event_publisher_class, m
127120

128121
processor.process_messages()
129122

130-
# Should not process messages when near timeout
131123
client_lookup.is_valid_sender.assert_not_called()
132124
polling_metric.record.assert_called_once()
133125

134-
@patch('src.processor.os.getenv')
135-
@patch('src.processor.EventPublisher')
136126
def test_process_message_with_valid_sender(self, mock_event_publisher_class, mock_getenv):
137127
"""Test processing a single message from valid sender"""
138-
from src.processor import MeshMessageProcessor
139-
140128
(config, client_lookup, mesh_client, log, polling_metric) = setup_mocks()
141129
message = setup_message_data("1")
142130

143-
# Mock environment variables
144131
mock_getenv.side_effect = lambda key, default='': {
145132
'EVENT_PUBLISHER_EVENT_BUS_ARN': 'arn:aws:events:eu-west-2:123456789012:event-bus/test',
146133
'EVENT_PUBLISHER_DLQ_URL': 'https://sqs.eu-west-2.amazonaws.com/123456789012/test-dlq'
@@ -168,16 +155,11 @@ def test_process_message_with_valid_sender(self, mock_event_publisher_class, moc
168155
mock_event_publisher.send_events.assert_called_once()
169156
message.acknowledge.assert_not_called() # Only acknowledged on auth error
170157

171-
@patch('src.processor.os.getenv')
172-
@patch('src.processor.EventPublisher')
173158
def test_process_message_with_unknown_sender(self, mock_event_publisher_class, mock_getenv):
174159
"""Test that messages from unknown senders are rejected silently"""
175-
from src.processor import MeshMessageProcessor
176-
177160
(config, client_lookup, mesh_client, log, polling_metric) = setup_mocks()
178161
message = setup_message_data("1")
179162

180-
# Mock environment variables
181163
mock_getenv.side_effect = lambda key, default='': {
182164
'EVENT_PUBLISHER_EVENT_BUS_ARN': 'arn:aws:events:eu-west-2:123456789012:event-bus/test',
183165
'EVENT_PUBLISHER_DLQ_URL': 'https://sqs.eu-west-2.amazonaws.com/123456789012/test-dlq'
@@ -197,22 +179,16 @@ def test_process_message_with_unknown_sender(self, mock_event_publisher_class, m
197179

198180
processor.process_message(message)
199181

200-
# Message should be acknowledged (removed from inbox)
201182
client_lookup.is_valid_sender.assert_called_once_with(message.sender)
202183
message.acknowledge.assert_called_once()
203184

204-
@patch('src.processor.os.getenv')
205-
@patch('src.processor.EventPublisher')
206185
def test_process_messages_across_multiple_iterations(self, mock_event_publisher_class, mock_getenv):
207186
"""Test that processor continues polling until no messages remain"""
208-
from src.processor import MeshMessageProcessor
209-
210187
(config, client_lookup, mesh_client, log, polling_metric) = setup_mocks()
211188
message1 = setup_message_data("1")
212189
message2 = setup_message_data("2")
213190
message3 = setup_message_data("3")
214191

215-
# Mock environment variables
216192
mock_getenv.side_effect = lambda key, default='': {
217193
'EVENT_PUBLISHER_EVENT_BUS_ARN': 'arn:aws:events:eu-west-2:123456789012:event-bus/test',
218194
'EVENT_PUBLISHER_DLQ_URL': 'https://sqs.eu-west-2.amazonaws.com/123456789012/test-dlq'
@@ -227,7 +203,6 @@ def test_process_messages_across_multiple_iterations(self, mock_event_publisher_
227203
polling_metric=polling_metric
228204
)
229205

230-
# Messages across multiple iterations
231206
mesh_client.iterate_all_messages.side_effect = [
232207
[message1, message2], # First iteration
233208
[message3], # Second iteration

lambdas/mesh-poll/src/client_lookup.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
class ClientLookup:
66
"""
7-
Lightweight client lookup for basic sender validation
7+
Lightweight client lookup for basic sender validation and client ID extraction
88
"""
99

1010
def __init__(self, ssm, config, logger):
1111
self.__ssm = ssm
1212
self.__config = config
1313
self.__logger = logger
1414
self.__valid_senders = set()
15+
self.__mailbox_to_client = {}
1516
self.load_valid_senders()
1617

1718
def is_valid_sender(self, mailbox_id):
@@ -23,27 +24,39 @@ def is_valid_sender(self, mailbox_id):
2324

2425
return mailbox_id.upper() in self.__valid_senders
2526

27+
def get_client_id(self, mailbox_id):
28+
"""
29+
Get the client ID for a given MESH mailbox ID
30+
"""
31+
if not mailbox_id:
32+
return None
33+
34+
return self.__mailbox_to_client.get(mailbox_id.upper())
35+
2636
def load_valid_senders(self):
2737
"""
28-
Loads just the mailbox IDs of valid senders into memory
38+
Loads mailbox IDs and their corresponding client IDs into memory
2939
"""
3040
mailbox_ids = set()
41+
mailbox_to_client = {}
3142
next_token = ""
3243
page_number = 0
3344

3445
while next_token or page_number < 1:
35-
(page, token) = self.__get_page(next_token)
36-
mailbox_ids.update(page)
46+
(page_mailbox_ids, page_mapping, token) = self.__get_page(next_token)
47+
mailbox_ids.update(page_mailbox_ids)
48+
mailbox_to_client.update(page_mapping)
3749
next_token = token
3850
page_number += 1
3951

4052
self.__valid_senders = mailbox_ids
53+
self.__mailbox_to_client = mailbox_to_client
4154
self.__logger.debug(
4255
f"Loaded {len(self.__valid_senders)} valid sender mailbox IDs")
4356

4457
def __get_page(self, next_token=""):
4558
"""
46-
Loads a page of client data and extracts just mailbox IDs
59+
Loads a page of client data and extracts mailbox IDs and client IDs
4760
"""
4861
if len(next_token) == 0:
4962
response = self.__ssm.get_parameters_by_path(
@@ -58,15 +71,19 @@ def __get_page(self, next_token=""):
5871
)
5972

6073
mailbox_ids = set()
74+
mailbox_to_client = {}
6175

6276
if "Parameters" in response:
6377
for parameter in response["Parameters"]:
6478
mailbox_id = self.__extract_mailbox_id(parameter)
65-
if mailbox_id:
66-
mailbox_ids.add(mailbox_id.upper())
79+
client_id = self.__extract_client_id(parameter)
80+
if mailbox_id and client_id:
81+
mailbox_id_upper = mailbox_id.upper()
82+
mailbox_ids.add(mailbox_id_upper)
83+
mailbox_to_client[mailbox_id_upper] = client_id
6784

6885
new_next_token = response.get("NextToken", "")
69-
return (mailbox_ids, new_next_token)
86+
return (mailbox_ids, mailbox_to_client, new_next_token)
7087

7188
def __extract_mailbox_id(self, parameter):
7289
"""
@@ -83,3 +100,19 @@ def __extract_mailbox_id(self, parameter):
83100
f"Failed to parse mailbox ID from parameter {parameter['Name']}")
84101
self.__logger.error(format_exception(exception))
85102
return None
103+
104+
def __extract_client_id(self, parameter):
105+
"""
106+
Extract just the client ID from a client parameter
107+
"""
108+
if "Value" not in parameter:
109+
return None
110+
111+
try:
112+
client_config = json.loads(parameter["Value"])
113+
return client_config.get("clientId", "")
114+
except (ValueError, AttributeError) as exception:
115+
self.__logger.warn(
116+
f"Failed to parse client ID from parameter {parameter['Name']}")
117+
self.__logger.error(format_exception(exception))
118+
return None

0 commit comments

Comments
 (0)