diff --git a/tests/test_ingestor_base.py b/tests/test_ingestor_base.py index cac2254..6b79df5 100644 --- a/tests/test_ingestor_base.py +++ b/tests/test_ingestor_base.py @@ -610,6 +610,70 @@ def test_validate_data_validator_exception_raises(): ing.validate_data("src") +# --------------------------------------------------------------------------- +# validator label normalization (#396 Bugbot: log labels drop suffix) +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "name, expected", + [ + # Already ends in "Validator" — must NOT double it. + ("Data Validator", "Data Validator"), + # Case-insensitive on the existing suffix. + ("Pascal VOC XML validator", "Pascal VOC XML validator"), + # Missing the suffix — must gain it. + ("Ingestable Records", "Ingestable Records Validator"), + ("Text Content", "Text Content Validator"), + ("BIO Label", "BIO Label Validator"), + ("Keypoint Annotation", "Keypoint Annotation Validator"), + # Surrounding whitespace is stripped before deciding. + (" Data Validator ", "Data Validator"), + ], +) +def test_validator_label_normalizes(name, expected): + assert base_mod._validator_label(name) == expected + + +def test_validate_data_success_line_keeps_suffix_when_missing(capsys): + # A name that does NOT end in "Validator" (e.g. "Ingestable Records") must + # keep the suffix in its "successfully passed" line. + ing = make_ingestor(category=None) + v = MagicMock() + v.name = "Ingestable Records" + v.validate.return_value = ValidationResult(True, [], [], {}) + with patch.object(base_mod, "map_validators", return_value=[v]): + assert ing.validate_data("src") is True + out = capsys.readouterr().out + assert "Ingestable Records Validator successfully passed" in out + + +def test_validate_data_success_line_does_not_double_suffix(capsys): + # A name that already ends in "Validator" (e.g. "Data Validator") must not + # double the word. + ing = make_ingestor(category=None) + v = MagicMock() + v.name = "Data Validator" + v.validate.return_value = ValidationResult(True, [], [], {}) + with patch.object(base_mod, "map_validators", return_value=[v]): + assert ing.validate_data("src") is True + out = capsys.readouterr().out + assert "Data Validator successfully passed" in out + assert "Data Validator Validator" not in out + + +def test_validate_data_failure_message_keeps_suffix_when_missing(): + # The error-append line normalizes too, so a suffix-less name isn't stripped. + ing = make_ingestor(category=None) + v = MagicMock() + v.name = "Ingestable Records" + v.validate.return_value = ValidationResult(False, ["nope"], [], {}) + with patch.object(base_mod, "map_validators", return_value=[v]): + with pytest.raises(ValueError) as exc: + ing.validate_data("src") + assert "Ingestable Records Validator failed" in str(exc.value) + + # --------------------------------------------------------------------------- # ingest (full flow, Session patched) # --------------------------------------------------------------------------- diff --git a/tracebloc_ingestor/ingestors/base.py b/tracebloc_ingestor/ingestors/base.py index 73c03d6..8555de3 100644 --- a/tracebloc_ingestor/ingestors/base.py +++ b/tracebloc_ingestor/ingestors/base.py @@ -137,6 +137,20 @@ def _rows_state_clause(inserted_records: int) -> str: return "no rows were ingested, so nothing was left in the database" +def _validator_label(name: str) -> str: + """Human-readable label for a validator's preflight log/error lines. + + Validator ``.name`` values are inconsistent: some already end in + "Validator" (e.g. "Data Validator") while many do not (e.g. "Ingestable + Records", "Text Content", "BIO Label", "Keypoint Annotation"). Normalize at + the log site so every label reads " Validator" exactly once — + appending the word only when it's missing, so suffixed names don't double + ("Data Validator Validator") and unsuffixed names don't drop it. + """ + name = (name or "").strip() + return name if name.lower().endswith("validator") else f"{name} Validator" + + # NOTE: _TABULAR_FAMILY_CATEGORIES and _FILE_BEARING_CATEGORIES are imported # above from modalities.registry (derived from the per-category ModalitySpec # flags — backend#796 P3a). The ``self.category in `` checks throughout @@ -545,14 +559,15 @@ def validate_data(self, source: Any) -> bool: validation_errors = [] for validator in validators: + label = _validator_label(validator.name) try: - logger.info(f"{CYAN}Running validator: {validator.name}{RESET}") + logger.info(f"{CYAN}Running validator: {label}{RESET}") result = validator.validate(source) if not result.is_valid: all_valid = False validation_errors.append( - f"{BOLD}{validator.name} failed: {RESET} \n {RED}" + f"{BOLD}{label} failed: {RESET} \n {RED}" ) validation_errors.extend(result.errors) validation_errors.append(f"{RESET}") @@ -560,16 +575,17 @@ def validate_data(self, source: Any) -> bool: # Log warnings if any for warning in result.warnings: logger.warning( - f"{YELLOW}Validation warning - {validator.name}: {warning}{RESET}" + f"{YELLOW}Validation warning - {label}: {warning}{RESET}" ) if result.is_valid: - # validator.name already ends in "Validator" (e.g. "Data - # Validator"), so don't append the word again — that produced - # "Data Validator Validator successfully passed". - print(f"{GREEN}{validator.name} successfully passed{RESET}") + # Normalize the label so it always reads " Validator" + # exactly once: names that already end in "Validator" (e.g. + # "Data Validator") don't double, and names that don't (e.g. + # "Ingestable Records") keep the suffix. + print(f"{GREEN}{label} successfully passed{RESET}") except Exception as e: all_valid = False - validation_errors.append(f"{validator.name} error: {str(e)}") + validation_errors.append(f"{label} error: {str(e)}") if not all_valid: error_summary = "\n".join(validation_errors)