Skip to content

Commit 9272ac0

Browse files
committed
create more tests
1 parent 84fc4fe commit 9272ac0

File tree

2 files changed

+147
-1
lines changed

2 files changed

+147
-1
lines changed

aws_lambda_powertools/utilities/data_masking/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def _mask_nested_field(self, data: dict, field_path: str, mask_function):
321321
if not isinstance(current, dict):
322322
return
323323
if keys[-1] in current:
324-
current[keys[-1]] = mask_function(current[keys[-1]])
324+
current[keys[-1]] = self.provider.erase(current[keys[-1]], **mask_function)
325325

326326
@staticmethod
327327
def _call_action(

tests/unit/data_masking/_aws_encryption_sdk/test_unit_data_masking.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,149 @@ def test_erase_json_dict_with_complex_masking_rules(data_masker):
277277
"age": "**",
278278
"address": {"zip": "xxx", "street": "123 Main St", "details": {"name": "Home", "type": "Primary"}},
279279
}
280+
281+
282+
def test_no_matches_for_masking_rule(data_masker):
283+
# GIVEN a dictionary without the expected field
284+
data = {"name": "Ana"}
285+
masking_rules = {"$.missing_field": {"dynamic_mask": True}}
286+
287+
# WHEN applying the masking rule
288+
with pytest.warns(UserWarning, match=r"No matches found for path: \$\.missing_field"):
289+
result = data_masker._apply_masking_rules(data, masking_rules)
290+
291+
# THEN the original data remains unchanged
292+
assert result == data
293+
294+
295+
def test_warning_during_masking_value(data_masker):
296+
# GIVEN data and a masking rule
297+
data = {"value": "test"}
298+
299+
# Mock provider that raises an error
300+
class MockProvider:
301+
def erase(self, value, **kwargs):
302+
raise ValueError("Mock error")
303+
304+
data_masker.provider = MockProvider()
305+
306+
# WHEN erase is called
307+
with pytest.warns(UserWarning, match="Error masking value for path value: Mock error"):
308+
masked_data = data_masker.erase(data, masking_rules={"value": {"rule": "value"}})
309+
310+
# THEN the original data should remain unchanged
311+
assert masked_data["value"] == "test"
312+
313+
314+
def test_mask_nested_field_with_non_dict_value(data_masker):
315+
# GIVEN nested data where a middle path component is not a dictionary
316+
data = {"user": {"contact": "not_a_dict", "details": {"ssn": "123-45-6789"}}} # This will stop the traversal
317+
318+
# WHEN attempting to mask a field through a path containing a non-dict value
319+
data_masker._mask_nested_field(data, "user.contact.details.ssn", lambda x: "MASKED")
320+
321+
# THEN the data should remain unchanged since traversal stopped at non-dict value
322+
assert data == {"user": {"contact": "not_a_dict", "details": {"ssn": "123-45-6789"}}}
323+
324+
325+
def test_mask_nested_field_success(data_masker):
326+
# GIVEN nested data with a field to mask
327+
data = {"user": {"contact": {"details": {"address": {"street": "123 Main St", "zip": "12345"}}}}}
328+
329+
# WHEN masking a nested field with a masking rule
330+
data_masker._mask_nested_field(data, "user.contact.details.address.zip", {"custom_mask": "xxx"})
331+
332+
# THEN the nested field should be masked while other data remains unchanged
333+
assert data == {"user": {"contact": {"details": {"address": {"street": "123 Main St", "zip": "xxx"}}}}}
334+
335+
336+
## teste aqui
337+
def test_erase_dictionary_with_masking_rules(data_masker):
338+
# GIVEN a dictionary with nested sensitive data
339+
data = {"user": {"name": "John Doe", "ssn": "123-45-6789", "address": {"street": "123 Main St", "zip": "12345"}}}
340+
341+
# AND masking rules for specific fields
342+
masking_rules = {"user.ssn": {"custom_mask": "XXX-XX-XXXX"}, "user.address.zip": {"custom_mask": "00000"}}
343+
344+
# WHEN erase is called with masking rules
345+
result = data_masker.erase(data, masking_rules=masking_rules)
346+
347+
# THEN only the specified fields should be masked
348+
assert result == {
349+
"user": {
350+
"name": "John Doe", # unchanged
351+
"ssn": "XXX-XX-XXXX", # masked
352+
"address": {"street": "123 Main St", "zip": "00000"}, # unchanged # masked
353+
},
354+
}
355+
356+
357+
def test_erase_dictionary_with_global_mask(data_masker):
358+
# GIVEN a dictionary with sensitive data
359+
data = {"user": {"name": "John Doe", "ssn": "123-45-6789"}}
360+
361+
# WHEN erase is called with a custom mask for all fields
362+
result = data_masker.erase(data, custom_mask="REDACTED")
363+
364+
# THEN all fields should use the custom mask
365+
assert result == {"user": {"name": "REDACTED", "ssn": "REDACTED"}}
366+
367+
368+
def test_erase_empty_dictionary(data_masker):
369+
# GIVEN an empty dictionary
370+
data = {}
371+
372+
# WHEN erase is called
373+
result = data_masker.erase(data, custom_mask="MASKED")
374+
375+
# THEN an empty dictionary should be returned
376+
assert result == {}
377+
378+
379+
def test_erase_different_iterables_with_masking(data_masker):
380+
# GIVEN different types of iterables
381+
list_data = ["name", "phone", "email"]
382+
tuple_data = ("name", "phone", "email")
383+
set_data = {"name", "phone", "email"}
384+
385+
# WHEN erase is called with a custom mask
386+
masked_list = data_masker.erase(list_data, custom_mask="XXX")
387+
masked_tuple = data_masker.erase(tuple_data, custom_mask="XXX")
388+
masked_set = data_masker.erase(set_data, custom_mask="XXX")
389+
390+
# THEN the masked data should maintain its original type
391+
assert isinstance(masked_list, list)
392+
assert isinstance(masked_tuple, tuple)
393+
assert isinstance(masked_set, set)
394+
395+
# AND all values should be masked
396+
expected_values = {"XXX"}
397+
assert set(masked_list) == expected_values
398+
assert set(masked_tuple) == expected_values
399+
assert masked_set == expected_values
400+
401+
402+
def test_erase_handles_invalid_regex_pattern(data_masker):
403+
# GIVEN a string and an invalid regex pattern
404+
data = "test123"
405+
406+
# WHEN masking with invalid regex
407+
result = data_masker.erase(
408+
data,
409+
regex_pattern="[",
410+
mask_format="X", # Invalid regex pattern that will raise re.error
411+
)
412+
413+
# THEN original data should be returned
414+
assert result == "test123"
415+
416+
417+
def test_erase_handles_empty_string_with_dynamic_mask(data_masker):
418+
# GIVEN an empty string
419+
data = ""
420+
421+
# WHEN erase is called with dynamic_mask
422+
result = data_masker.erase(data, dynamic_mask=True)
423+
424+
# THEN empty string should be returned
425+
assert result == ""

0 commit comments

Comments
 (0)