Files
DeDRM_tools/tests/dedrm_test_utils.py
T
JMR-devandClaude Opus 4.8 ba96df081f Add pytest suite for both plugins
Add a pytest suite covering the pure logic of the DeDRM and Obok plugins.
Because the plugins normally run inside calibre and use intra-package
imports, tests/dedrm_test_utils.py sets up an import shim (plugin dirs on
sys.path, a minimal calibre stub, and a synthetic `dedrm` package) so the
modules can be imported and exercised standalone.

Coverage:
  - alfcrypto: PC1 and Topaz cipher round-trips, PBKDF2 vs hashlib.
  - kgenpids / kindlepid: PID encoding, bit-field extraction, device-PID
    and serial-PID known vectors, CRC32.
  - mobidedrm: PC1 round-trip, trailing-data sizing, bad-key handling.
  - ineptpdf / ineptepub: nunpack and PKCS7 unpad.
  - topazextract: encoded number/string parsing, plus a regression test
    that a malicious header tag ("../../evil") cannot escape the output
    directory (covers the path-traversal fix).
  - obok: unpad, hash-key table, SafeUnbuffered str/bytes handling.
  - utilities / argv_utils: uStrCmp normalisation, unicode_argv.
  - erdr2pml: deXOR/sanitiseFileName, skipped on Python 3.13+ where the
    module's `cgi` import is unavailable.

Run with `poetry install` then `poetry run pytest`. 36 pass, 2 skip on
Python 3.13+.

Note: this surfaced two dead-code modules that are broken on Python 3
(aescbc's pure-Python AES and kgenpids.decode, neither on a live path,
since the real crypto goes through pycryptodome); left as-is here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:08:03 -05:00

62 lines
2.1 KiB
Python

"""Test helpers for importing the calibre plugin modules outside of calibre.
The DeDRM and Obok plugins normally run inside calibre's bundled Python and use
intra-package (``from .x import y``) imports. To unit-test the pure logic we:
* put the plugin source directories on ``sys.path`` (for absolute imports such
as ``import kgenpids`` / ``import aescbc``);
* provide a minimal ``calibre`` stub for the few modules that import it;
* register a synthetic ``dedrm`` package whose ``__path__`` points at the
DeDRM_plugin directory, so modules that use relative imports resolve without
executing the real (calibre-dependent) ``__init__.py``.
Modules with no relative imports (alfcrypto, kgenpids, argv_utils, aescbc, obok)
are loaded top-level; modules with ``from .`` imports are loaded as ``dedrm.X``.
"""
import importlib
import os
import sys
import types
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEDRM = os.path.join(ROOT, "DeDRM_plugin")
OBOK = os.path.join(ROOT, "Obok_plugin", "obok")
def _ensure_on_path(path):
if path not in sys.path:
sys.path.insert(0, path)
def _setup():
_ensure_on_path(DEDRM)
_ensure_on_path(OBOK)
# Minimal calibre stub (only the bits the plugin modules touch at import).
if "calibre" not in sys.modules:
sys.modules["calibre"] = types.ModuleType("calibre")
constants = types.ModuleType("calibre.constants")
constants.iswindows = sys.platform.startswith("win")
sys.modules["calibre.constants"] = constants
# Synthetic package so `from .x import y` works without the real __init__.py.
if "dedrm" not in sys.modules:
pkg = types.ModuleType("dedrm")
pkg.__path__ = [DEDRM]
pkg.__package__ = "dedrm"
sys.modules["dedrm"] = pkg
_setup()
def load(module_name, package=None):
"""Import and return a plugin module.
Pass ``package="dedrm"`` for modules that use relative imports.
"""
if package:
return importlib.import_module("{0}.{1}".format(package, module_name))
return importlib.import_module(module_name)