"""SP18 — PII scrubber tests. Covers each PHI pattern, the false-positive guard, and the disable toggle. """ from __future__ import annotations import logging import pytest from cyclone.logging_config import ( PiiScrubber, get_scrubber, setup_logging, ) def _make_record(msg: str, extras: dict | None = None) -> logging.LogRecord: record = logging.getLogger("test.scrub").makeRecord( name="test.scrub", level=logging.INFO, fn="t.py", lno=1, msg=msg, args=(), exc_info=None, ) if extras: for k, v in extras.items(): setattr(record, k, v) return record @pytest.fixture(autouse=True) def _reset_scrubber(): """Make sure the scrubber is enabled + on the root after each test.""" yield get_scrubber().enable() # --------------------------------------------------------------------------- # NPI # --------------------------------------------------------------------------- def test_scrubs_ten_digit_npi_in_message(): scrubber = PiiScrubber() rec = _make_record("processed claim with npi 1881068062 ok") assert scrubber.filter(rec) is True assert rec.getMessage() == "processed claim with npi ok" def test_scrubs_npi_in_extras(): scrubber = PiiScrubber() rec = _make_record("ok", extras={"provider_npi": "1881068062"}) scrubber.filter(rec) assert rec.provider_npi == "" def test_does_not_scrub_short_numbers(): scrubber = PiiScrubber() rec = _make_record("processed 5 claims in 2 batches") scrubber.filter(rec) assert rec.getMessage() == "processed 5 claims in 2 batches" def test_does_not_scrub_eleven_digit_numbers(): """11+ digit numbers aren't NPIs — leave them alone.""" scrubber = PiiScrubber() rec = _make_record("control number 12345678901") scrubber.filter(rec) assert rec.getMessage() == "control number 12345678901" # --------------------------------------------------------------------------- # SSN # --------------------------------------------------------------------------- def test_scrubs_dashed_ssn_in_message(): scrubber = PiiScrubber() rec = _make_record("ssn=123-45-6789 detected") scrubber.filter(rec) assert rec.getMessage() == "ssn= detected" def test_scrubs_undashed_ssn_at_phrase_boundary(): """A bare 9-digit number is ambiguous — we scrub it when followed by whitespace/punctuation/closing paren/brace/comma (so we don't hit claim control numbers or zip codes mid-sentence).""" scrubber = PiiScrubber() rec = _make_record("ssn 123456789 on file") scrubber.filter(rec) assert "" in rec.getMessage() # --------------------------------------------------------------------------- # DOB # --------------------------------------------------------------------------- def test_scrubs_dob_field(): scrubber = PiiScrubber() rec = _make_record("dob=1980-04-12 verified") scrubber.filter(rec) assert rec.getMessage() == "dob= verified" def test_scrubs_dob_in_extras(): scrubber = PiiScrubber() rec = _make_record("ok", extras={"date_of_birth": "1980-04-12"}) scrubber.filter(rec) assert rec.date_of_birth == "" def test_does_not_scrub_bare_iso_date(): """A YYYY-MM-DD without a dob= prefix isn't necessarily PHI.""" scrubber = PiiScrubber() rec = _make_record("parsed at 2026-06-21") scrubber.filter(rec) assert rec.getMessage() == "parsed at 2026-06-21" # --------------------------------------------------------------------------- # Patient name # --------------------------------------------------------------------------- def test_scrubs_patient_name_field(): scrubber = PiiScrubber() rec = _make_record('patient_name="John Doe" verified') scrubber.filter(rec) assert "John Doe" not in rec.getMessage() assert "" in rec.getMessage() def test_does_not_scrub_random_words(): scrubber = PiiScrubber() rec = _make_record("the parser ran successfully") scrubber.filter(rec) assert rec.getMessage() == "the parser ran successfully" # --------------------------------------------------------------------------- # Disable toggle # --------------------------------------------------------------------------- def test_scrubber_disabled_leaves_message_intact(): scrubber = PiiScrubber() scrubber.disable() rec = _make_record("npi 1881068062 ok") scrubber.filter(rec) assert rec.getMessage() == "npi 1881068062 ok" def test_scrubber_does_not_crash_on_unusual_records(): """Even with weird attribute combinations the filter returns True.""" scrubber = PiiScrubber() rec = _make_record("ok", extras={"weird": object()}) assert scrubber.filter(rec) is True