diff --git a/poetry.lock b/poetry.lock index a9ccef2..46b98fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -115,6 +115,18 @@ files = [ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] +[[package]] +name = "legacy-cgi" +version = "2.6.4" +description = "Fork of the standard library cgi and cgitb modules removed in Python 3.13" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "legacy_cgi-2.6.4-py3-none-any.whl", hash = "sha256:7e235ce58bf1e25d1fc9b2d299015e4e2cd37305eccafec1e6bac3fc04b878cd"}, + {file = "legacy_cgi-2.6.4.tar.gz", hash = "sha256:abb9dfc7835772f7c9317977c63253fd22a7484b5c9bbcdca60a29dcce97c577"}, +] + [[package]] name = "lxml" version = "6.1.1" @@ -441,4 +453,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.8,<4.0" -content-hash = "d54bcac8b306449a805fd6f145f6d910dcbebbccc907b790b028c988c7ef1985" +content-hash = "a7172bce7871b58ba975c81418c1477022b8cd6ce275dcca76bcedcb766764e4" diff --git a/pyproject.toml b/pyproject.toml index 36e6b98..33ceb62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ apsw = ">=3.46" [tool.poetry.group.dev.dependencies] pytest = ">=8.0" +# Backport of the stdlib `cgi` module (removed in Python 3.13) so erdr2pml and +# other modules that still import it can be loaded and tested. +legacy-cgi = ">=2.6" [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/tests/test_erdr2pml.py b/tests/test_erdr2pml.py index 984a452..3b6c4f8 100644 --- a/tests/test_erdr2pml.py +++ b/tests/test_erdr2pml.py @@ -1,8 +1,9 @@ -"""Tests for erdr2pml helpers. +"""Tests for erdr2pml (eReader) helpers. erdr2pml imports the stdlib ``cgi`` module, which was removed in Python 3.13. -On such interpreters the whole module is unimportable, so these tests are -skipped (calibre currently ships Python 3.8-3.11, where they run). +The ``legacy-cgi`` backport is a dev dependency so the module imports on modern +interpreters; the skip guard below keeps the suite green if it is ever run +without that backport (e.g. a bare Python 3.13+ with no dev extras). """ import pytest @@ -12,7 +13,7 @@ import dedrm_test_utils as U try: erdr2pml = U.load("erdr2pml", package="dedrm") _skip_reason = None -except Exception as exc: # pragma: no cover - depends on interpreter version +except Exception as exc: # pragma: no cover - depends on interpreter / deps erdr2pml = None _skip_reason = "erdr2pml not importable: {0}".format(exc) @@ -27,7 +28,32 @@ def test_dexor_is_involutive(): assert erdr2pml.deXOR(once, 0, table) == text -def test_sanitize_filename_strips_separators(): - name = erdr2pml.sanitizeFileName("a/b\\c") - assert "/" not in name - assert "\\" not in name +def test_fix_key_known_vector(): + assert erdr2pml.fixKey(bytes([0, 1, 2, 3, 255, 128, 64])) == bytes( + [0x80, 0x01, 0x02, 0x83, 0x7F, 0x80, 0x40] + ) + + +def test_fix_key_preserves_length(): + assert len(erdr2pml.fixKey(b"\x00" * 16)) == 16 + + +def test_clean_pml_escapes_high_ascii(): + # bytes 128..255 become \aNNN PML codes; bytes < 128 are left untouched. + assert erdr2pml.cleanPML(b"abc\x80\xff\x7f") == b"abc\\a128\\a255\x7f" + + +def test_sanitize_filename_replaces_separators(): + assert erdr2pml.sanitizeFileName("a/b\\c|d") == "a_b_c_d" + + +def test_sanitize_filename_removes_colon(): + assert ":" not in erdr2pml.sanitizeFileName("foo: bar") + + +def test_sanitize_filename_strips_control_chars_and_dots(): + assert erdr2pml.sanitizeFileName("...\x01na\tme.") == "name" + + +def test_sanitize_filename_replaces_angle_brackets(): + assert erdr2pml.sanitizeFileName("") == "[tag]"