fix(tests): a failing database test no longer prints the password (T-12)

pytest prints a failing test's fixture arguments via repr() as part of the
default traceback header — no -v or --showlocals needed — so any test
taking the test_database_url fixture and failing printed the real
TEST_DATABASE_URL straight into the terminal, password included.

test_database_url now returns a MaskedURL: a str subclass identical to a
plain string for every functional use (passed to .replace(), urlsplit(),
a subprocess environment) but whose repr() masks the password while still
showing host and database name. __str__ is untouched, so every caller
still connects with the real value.

tests/test_conftest.py proves it, both for the fixture itself and for
MaskedURL's string behaviour in isolation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 00:28:34 +02:00
co-authored by Claude Sonnet 5
parent a81b9a071b
commit 2f53722078
3 changed files with 65 additions and 1 deletions
+18 -1
View File
@@ -49,6 +49,23 @@ def run_alembic(url: str, *args: str) -> subprocess.CompletedProcess:
return result
class MaskedURL(str):
"""A database URL, fully usable as a plain string (passed to `.replace()`,
`urlsplit()`, a subprocess environment, ...) but never through `repr()`
(core-api T-12): pytest prints a failing test's fixture arguments using
`repr()`, with no `-v`/`--showlocals` needed to trigger it — that is how
a failing database test used to print this fixture's password straight
into the terminal. `__str__` is deliberately left alone, so the real
value is still what every caller actually connects with."""
def __repr__(self) -> str:
parts = urlsplit(self)
netloc = parts.netloc
if parts.password:
netloc = netloc.replace(f":{parts.password}@", ":***@")
return repr(parts._replace(netloc=netloc).geturl())
@pytest.fixture(scope="session")
def test_database_url() -> str:
"""The core_api_test database URL, or skip/refuse per the rules above."""
@@ -62,7 +79,7 @@ def test_database_url() -> str:
"TEST_DATABASE_URL resolves to 'core_api' (production) — refusing to use it for tests"
)
return url
return MaskedURL(url)
def _grant_test_office_gemini(url: str) -> None: