from __future__ import annotations
from pathlib import Path
import pytest
import mrrc
_REPO_ROOT = Path(__file__).resolve().parents[2]
_FIXTURES = _REPO_ROOT / "tests" / "data" / "error_fixtures"
def _read_fixture(name: str) -> bytes:
return (_FIXTURES / name).read_bytes()
def test_clean_record_has_empty_errors() -> None:
bytes_ = (_REPO_ROOT / "tests" / "data" / "simple_book.mrc").read_bytes()
reader = mrrc.MARCReader(bytes_)
record = next(reader)
assert record.errors == []
def test_lenient_populates_record_errors() -> None:
bytes_ = _read_fixture("e201_bad_indicator.bin")
reader = mrrc.MARCReader(
bytes_,
recovery_mode="lenient",
validation_level="strict_marc",
)
record = next(reader)
assert record is not None
assert len(record.errors) >= 1
assert record.errors[0].code == "E201"
def test_iter_with_errors_clean_records() -> None:
bytes_ = (_REPO_ROOT / "tests" / "data" / "multi_records.mrc").read_bytes()
reader = mrrc.MARCReader(bytes_)
for record, errors in reader.iter_with_errors():
assert record is not None
assert errors == []
def test_iter_with_errors_lenient_yields_tuples_with_errors() -> None:
bytes_ = _read_fixture("e202_non_printable_subfield_code.bin")
reader = mrrc.MARCReader(
bytes_,
recovery_mode="lenient",
validation_level="strict_marc",
)
items = list(reader.iter_with_errors())
assert len(items) == 1
record, errors = items[0]
assert record is not None
assert len(errors) >= 1
assert errors[0].code == "E202"
def test_permissive_iter_yields_none_for_swallowed() -> None:
bytes_ = _read_fixture("e201_bad_indicator.bin")
reader = mrrc.MARCReader(bytes_, permissive=True, validation_level="strict_marc")
items = list(reader)
assert items == [None]
def test_permissive_iter_with_errors_yields_none_plus_exception() -> None:
bytes_ = _read_fixture("e201_bad_indicator.bin")
reader = mrrc.MARCReader(bytes_, permissive=True, validation_level="strict_marc")
items = list(reader.iter_with_errors())
assert len(items) == 1
record, errors = items[0]
assert record is None, "expected None for swallowed record"
assert len(errors) == 1, f"expected one captured exception, got {errors}"
assert errors[0].code == "E201"
def test_permissive_iter_with_errors_returns_records_when_clean() -> None:
bytes_ = (_REPO_ROOT / "tests" / "data" / "multi_records.mrc").read_bytes()
reader = mrrc.MARCReader(bytes_, permissive=True)
for record, errors in reader.iter_with_errors():
assert record is not None
assert errors == []
@pytest.mark.parametrize(
("fixture", "code"),
[
("e001_record_length_non_digit.bin", "E001"),
("e002_indicator_count_non_digit.bin", "E002"),
("e003_base_address_non_digit.bin", "E003"),
("e004_base_address_past_record.bin", "E004"),
],
)
def test_fatal_errors_raise_in_every_mode(fixture: str, code: str) -> None:
bytes_ = _read_fixture(fixture)
for recovery_mode in ("strict", "lenient"):
reader = mrrc.MARCReader(bytes_, recovery_mode=recovery_mode)
with pytest.raises(Exception) as exc_info:
list(reader)
assert getattr(exc_info.value, "code", None) == code, (
f"{fixture} at recovery_mode={recovery_mode}: "
f"expected {code}, got {exc_info.value!r}"
)
def test_strict_only_e006_raises_in_strict_clean_in_lenient() -> None:
bytes_ = _read_fixture("e006_no_record_terminator.bin")
reader = mrrc.MARCReader(bytes_, recovery_mode="strict")
with pytest.raises(Exception) as exc_info:
list(reader)
assert getattr(exc_info.value, "code", None) == "E006"
reader = mrrc.MARCReader(bytes_, recovery_mode="lenient")
records = list(reader)
assert len(records) == 1
assert records[0] is not None
@pytest.mark.parametrize(
("fixture", "code"),
[
("e005_truncated_record.bin", "E005"),
("e101_directory_non_digit_length.bin", "E101"),
("e106_field_length_past_data.bin", "E106"),
],
)
def test_recoverable_errors_captured_in_lenient(fixture: str, code: str) -> None:
bytes_ = _read_fixture(fixture)
reader = mrrc.MARCReader(bytes_, recovery_mode="lenient")
records = list(reader)
assert len(records) == 1
record = records[0]
assert record is not None
codes = [e.code for e in record.errors]
assert code in codes, (
f"{fixture}: expected {code} on record.errors, got {codes}"
)