mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-06-24 19:14:20 +00:00
Cover both arms of the optional psycopg import in postgres.py (#799)
The module-level try/except import is environment-dependent: with psycopg installed the ImportError fallback never runs, and without it (CI's test job) the successful-import arm never completes — so Codecov flags one side or the other no matter where coverage is measured (it flagged the import line on master right after #798 merged). Exercise both arms explicitly: execute the module's source into a fresh, throwaway module object (importlib.util.module_from_spec + exec_module) under a patched sys.modules — a None entry forces ImportError, fake module entries force the success path — and assert on the psycopg / psycopg_json bindings each arm produces. The throwaway-module approach (rather than importlib.reload) leaves the canonical parsedmarc.postgres untouched, so the identity of PostgreSQLError / AlreadySaved held by the rest of the test module is preserved. Verified covered in both environments: with the venv's real psycopg, and with psycopg hidden via a PYTHONPATH shim to simulate CI; the import block reports no missing lines either way. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,9 @@ the bound parameters that a real PostgreSQL server would receive, plus the
|
||||
real-sample round trip, so the tests fail if the dict-key mapping regresses.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from glob import glob
|
||||
from typing import cast
|
||||
@@ -47,6 +49,51 @@ def tearDownModule():
|
||||
_types_patcher.stop()
|
||||
|
||||
|
||||
class TestPsycopgImportFallback(unittest.TestCase):
|
||||
"""The module-level psycopg import has two arms — psycopg installed
|
||||
and psycopg missing — and a normal test run only ever executes the
|
||||
arm that matches the environment. Re-execute the module's source
|
||||
with sys.modules manipulated to force each arm, so both are
|
||||
exercised (and covered) whether or not psycopg is installed."""
|
||||
|
||||
def _exec_fresh_module_with(self, modules):
|
||||
"""Execute parsedmarc.postgres's source into a fresh, throwaway
|
||||
module object under a patched sys.modules, and return the
|
||||
(psycopg, psycopg_json) it bound. The canonical module in
|
||||
sys.modules is untouched, so the identity of its classes
|
||||
(PostgreSQLError, AlreadySaved, ...) that other tests hold
|
||||
references to is preserved."""
|
||||
spec = importlib.util.find_spec("parsedmarc.postgres")
|
||||
assert spec is not None and spec.loader is not None
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
with patch.dict(sys.modules, modules):
|
||||
spec.loader.exec_module(mod)
|
||||
return mod.psycopg, mod.psycopg_json
|
||||
|
||||
def test_missing_psycopg_falls_back_to_none(self):
|
||||
"""A None entry in sys.modules makes ``import psycopg`` raise
|
||||
ImportError, exercising the fallback arm."""
|
||||
psycopg_mod, json_mod = self._exec_fresh_module_with(
|
||||
{"psycopg": None, "psycopg.types": None, "psycopg.types.json": None}
|
||||
)
|
||||
self.assertIsNone(psycopg_mod)
|
||||
self.assertIsNone(json_mod)
|
||||
|
||||
def test_installed_psycopg_binds_module_and_json_submodule(self):
|
||||
fake_json = MagicMock()
|
||||
fake_types = MagicMock(json=fake_json)
|
||||
fake_psycopg = MagicMock(types=fake_types)
|
||||
psycopg_mod, json_mod = self._exec_fresh_module_with(
|
||||
{
|
||||
"psycopg": fake_psycopg,
|
||||
"psycopg.types": fake_types,
|
||||
"psycopg.types.json": fake_json,
|
||||
}
|
||||
)
|
||||
self.assertIs(psycopg_mod, fake_psycopg)
|
||||
self.assertIs(json_mod, fake_json)
|
||||
|
||||
|
||||
class TestPostgreSQLHelpers(unittest.TestCase):
|
||||
"""Unit tests for the pure helper functions in parsedmarc.postgres."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user