Spaces:
Runtime error
Runtime error
| """ | |
| Tests for the three custom Presidio PatternRecognizers in src/privacy/redactor.py. | |
| No spaCy model required — these tests exercise pure regex pattern matching only. | |
| Run fast subset: pytest tests/privacy/test_recognizers.py | |
| """ | |
| import pytest | |
| from presidio_analyzer import AnalyzerEngine | |
| from presidio_analyzer.nlp_engine import NlpEngineProvider | |
| from src.privacy.redactor import ( | |
| _aadhaar_recognizer, | |
| _pan_recognizer, | |
| _vehicle_registration_recognizer, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _matches(recognizer, text): | |
| """Return analyzer results from a single recognizer against text.""" | |
| return recognizer.analyze(text=text, entities=[recognizer.supported_entities[0]]) | |
| def _engine_with(*recognizers): | |
| """Build a minimal AnalyzerEngine with only the given recognizers (no spaCy).""" | |
| nlp_config = { | |
| "nlp_engine_name": "spacy", | |
| "models": [{"lang_code": "en", "model_name": "en_core_web_sm"}], | |
| } | |
| try: | |
| nlp_engine = NlpEngineProvider(nlp_configuration=nlp_config).create_engine() | |
| except Exception: | |
| pytest.skip("spaCy en_core_web_sm not available for collision test") | |
| engine = AnalyzerEngine(nlp_engine=nlp_engine, supported_languages=["en"]) | |
| # Remove all default recognizers, add only ours | |
| engine.registry.recognizers.clear() | |
| for r in recognizers: | |
| engine.registry.add_recognizer(r) | |
| return engine | |
| # --------------------------------------------------------------------------- | |
| # IN_AADHAAR — valid formats | |
| # --------------------------------------------------------------------------- | |
| def test_aadhaar_valid_formats(): | |
| """Space-separated, hyphen-separated, and continuous formats all match.""" | |
| recognizer = _aadhaar_recognizer() | |
| space_result = _matches(recognizer, "My aadhaar is 2345 6789 0123") | |
| assert len(space_result) == 1 | |
| assert space_result[0].entity_type == "IN_AADHAAR" | |
| hyphen_result = _matches(recognizer, "UID: 2345-6789-0123") | |
| assert len(hyphen_result) == 1 | |
| assert hyphen_result[0].entity_type == "IN_AADHAAR" | |
| continuous_result = _matches(recognizer, "234567890123") | |
| assert len(continuous_result) == 1 | |
| assert continuous_result[0].entity_type == "IN_AADHAAR" | |
| # --------------------------------------------------------------------------- | |
| # IN_AADHAAR — invalid formats | |
| # --------------------------------------------------------------------------- | |
| def test_aadhaar_invalid_formats(): | |
| """First digit 1, 11-digit, and 13-digit inputs must not match.""" | |
| recognizer = _aadhaar_recognizer() | |
| assert len(_matches(recognizer, "1345 6789 0123")) == 0, "first digit 1 should not match" | |
| assert len(_matches(recognizer, "2345 6789 012")) == 0, "11 digits should not match" | |
| assert len(_matches(recognizer, "2345 6789 01234")) == 0, "13 digits should not match" | |
| # --------------------------------------------------------------------------- | |
| # IN_PAN — valid format | |
| # --------------------------------------------------------------------------- | |
| def test_pan_valid_format(): | |
| """Canonical PAN format (5 uppercase + 4 digits + 1 uppercase) matches.""" | |
| recognizer = _pan_recognizer() | |
| result = _matches(recognizer, "My PAN is ABCDE1234F") | |
| assert len(result) == 1 | |
| assert result[0].entity_type == "IN_PAN" | |
| # --------------------------------------------------------------------------- | |
| # IN_PAN — invalid formats | |
| # --------------------------------------------------------------------------- | |
| def test_pan_invalid_formats(): | |
| """Lowercase, digit-ending, and 4-letter-prefix inputs must not match.""" | |
| recognizer = _pan_recognizer() | |
| assert len(_matches(recognizer, "abcde1234f")) == 0, "lowercase PAN should not match" | |
| assert len(_matches(recognizer, "ABCDE12345")) == 0, "PAN ending in digit should not match" | |
| assert len(_matches(recognizer, "ABCD1234F")) == 0, "only 4 leading letters should not match" | |
| # --------------------------------------------------------------------------- | |
| # IN_VEHICLE_REGISTRATION — valid formats | |
| # --------------------------------------------------------------------------- | |
| def test_vehicle_registration_valid_formats(): | |
| """2-digit district, 1-digit district, and 1-letter series all match.""" | |
| recognizer = _vehicle_registration_recognizer() | |
| result_std = _matches(recognizer, "vehicle number MH01AB1234") | |
| assert len(result_std) == 1 | |
| assert result_std[0].entity_type == "IN_VEHICLE_REGISTRATION" | |
| result_1d = _matches(recognizer, "reg no DL3CAF0001") | |
| assert len(result_1d) == 1 | |
| assert result_1d[0].entity_type == "IN_VEHICLE_REGISTRATION" | |
| result_1s = _matches(recognizer, "number plate KA01A1234") | |
| assert len(result_1s) == 1 | |
| assert result_1s[0].entity_type == "IN_VEHICLE_REGISTRATION" | |
| # --------------------------------------------------------------------------- | |
| # PAN vs vehicle registration collision — PAN score (0.85) wins | |
| # --------------------------------------------------------------------------- | |
| def test_pan_wins_over_vehicle_on_collision(): | |
| """When a span matches both IN_PAN and IN_VEHICLE_REGISTRATION, IN_PAN wins.""" | |
| # ABCDE1234F — 5 letters + 4 digits + 1 letter — matches PAN regex exactly. | |
| # It also satisfies the vehicle reg pattern (2+1+3+4 split: AB·C·DE·1234F is | |
| # borderline, but Presidio score resolves it). We assert IN_PAN dominates. | |
| pan_r = _pan_recognizer() | |
| veh_r = _vehicle_registration_recognizer() | |
| engine = _engine_with(pan_r, veh_r) | |
| results = engine.analyze( | |
| text="My PAN is ABCDE1234F", | |
| entities=["IN_PAN", "IN_VEHICLE_REGISTRATION"], | |
| language="en", | |
| ) | |
| entity_types = {r.entity_type for r in results} | |
| assert "IN_PAN" in entity_types | |
| assert "IN_VEHICLE_REGISTRATION" not in entity_types | |