document-pii-redactor / tests /test_pseudonym.py
deploy
Deploy from 81eae33
649ee03
Raw
History Blame Contribute Delete
3.45 kB
from document_pii_redactor.pseudonym import PseudonymMapping, label_for
def test_label_for_friendly_and_fallback():
assert label_for("primary_subject_name") == "Person"
assert label_for("city_district") == "City"
assert label_for("not_a_real_category") == "NotARealCategory"
def test_assigns_sequential_pseudonyms_per_label():
m = PseudonymMapping()
assert m.pseudonym_for("primary_subject_name", "John") == "Person_1"
assert m.pseudonym_for("other_person_name", "Asha") == "Person_2"
assert m.pseudonym_for("city_district", "Bangalore") == "City_1"
def test_same_entity_reuses_pseudonym():
m = PseudonymMapping()
first = m.pseudonym_for("primary_subject_name", "John")
assert m.pseudonym_for("primary_subject_name", "John") == first
# Normalization: whitespace + case don't split an entity.
assert m.pseudonym_for("primary_subject_name", " JOHN ") == first
def test_shared_person_label_across_name_categories():
# The same name tagged under either person-name class is one entity.
m = PseudonymMapping()
a = m.pseudonym_for("primary_subject_name", "John")
b = m.pseudonym_for("other_person_name", "John")
assert a == b == "Person_1"
def test_mapping_stores_first_seen_original():
m = PseudonymMapping()
m.pseudonym_for("primary_subject_name", "John")
m.pseudonym_for("primary_subject_name", "JOHN")
assert m.entries["Person"] == {"Person_1": "John"}
def test_round_trip_serialization_continues_numbering():
m = PseudonymMapping()
m.pseudonym_for("primary_subject_name", "John")
revived = PseudonymMapping.from_dict(m.to_dict())
assert revived.pseudonym_for("primary_subject_name", "John") == "Person_1"
assert revived.pseudonym_for("primary_subject_name", "Asha") == "Person_2"
def test_from_dict_none_gives_empty_mapping():
m = PseudonymMapping.from_dict(None)
assert m.pseudonym_for("email", "a@b.com") == "Email_1"
# ------------------------------------------------------------ hash tokens --- #
def test_token_for_is_deterministic_and_normalized():
import re
from document_pii_redactor.pseudonym import token_for
t = token_for("primary_subject_name", "John Doe")
assert re.fullmatch(r"Person_[0-9a-f]{6}", t)
# Global determinism: same value -> same token, no shared state needed.
assert token_for("primary_subject_name", "John Doe") == t
# Normalization: whitespace/case variants agree.
assert token_for("primary_subject_name", " JOHN doe ") == t
# Different values -> different tokens.
assert token_for("primary_subject_name", "Asha Menon") != t
def test_token_for_secret_salts_the_hash():
from document_pii_redactor.pseudonym import token_for
plain = token_for("phone_mobile", "+91 98765 43210")
salted = token_for("phone_mobile", "+91 98765 43210", secret="s3cr3t")
assert plain != salted
# Same secret reproduces the same token — cross-system consistency.
assert token_for("phone_mobile", "+91 98765 43210", secret="s3cr3t") == salted
def test_record_token_captures_reverse_mapping_once():
m = PseudonymMapping()
from document_pii_redactor.pseudonym import token_for
tok = token_for("primary_subject_name", "John Doe")
assert m.record_token("primary_subject_name", "John Doe", tok) == tok
m.record_token("primary_subject_name", "JOHN DOE", tok) # normalized dup
assert m.entries["Person"] == {tok: "John Doe"}