Files
teng-lin--notebooklm-py/tests/unit/test_auth_repr_redaction.py
T
wehub-resource-sync 09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:13 +08:00

153 lines
5.4 KiB
Python

"""Unit tests for ``AuthTokens.__repr__`` secret redaction (P1-1).
Secret values that must never appear in ``repr(AuthTokens(...))``:
* Cookie values (``__Secure-1PSID``, ``__Secure-1PSIDTS``, ``SID``, …).
* ``csrf_token`` (the ``SNlM0e`` value).
* ``session_id`` (the ``FdrFJe`` value).
* Cookie values held inside the ``cookie_jar`` (``httpx.Cookies``).
* Cookie values held inside ``cookie_snapshot``.
Non-secret fields that SHOULD appear (so logs remain debuggable):
* The class name and a redacted summary indicating field presence.
* ``authuser`` index and ``account_email`` — neither is credential-equivalent
and both help identify which profile is involved.
* ``storage_path`` (typically already in user-facing log lines).
"""
from __future__ import annotations
from pathlib import Path
import httpx
import pytest
from notebooklm.auth import AuthTokens
_SECRET_PSID = "secret-psid-value-deadbeefcafe"
_SECRET_PSIDTS = "secret-psidts-cafefeedface"
_SECRET_SID = "secret-sid-value-12345"
_SECRET_CSRF = "secret-csrf-token-SNlM0e-abc"
_SECRET_SESSION = "secret-session-id-FdrFJe-xyz"
def _build_auth_with_secrets() -> AuthTokens:
"""Build an ``AuthTokens`` populated with the canonical secret fixtures."""
cookies = {
("__Secure-1PSID", ".google.com", "/"): _SECRET_PSID,
("__Secure-1PSIDTS", ".google.com", "/"): _SECRET_PSIDTS,
("SID", ".google.com", "/"): _SECRET_SID,
}
jar = httpx.Cookies()
jar.set("__Secure-1PSID", _SECRET_PSID, domain=".google.com")
jar.set("__Secure-1PSIDTS", _SECRET_PSIDTS, domain=".google.com")
jar.set("SID", _SECRET_SID, domain=".google.com")
return AuthTokens(
cookies=cookies,
csrf_token=_SECRET_CSRF,
session_id=_SECRET_SESSION,
storage_path=Path("/tmp/storage.json"),
cookie_jar=jar,
authuser=1,
account_email="alice@example.com",
)
def test_repr_does_not_leak_cookie_values() -> None:
auth = _build_auth_with_secrets()
rendered = repr(auth)
assert _SECRET_PSID not in rendered
assert _SECRET_PSIDTS not in rendered
assert _SECRET_SID not in rendered
def test_repr_does_not_leak_csrf_or_session() -> None:
auth = _build_auth_with_secrets()
rendered = repr(auth)
assert _SECRET_CSRF not in rendered
assert _SECRET_SESSION not in rendered
def test_repr_does_not_leak_cookie_jar_values() -> None:
"""The cookie_jar holds the same secrets in a different container."""
auth = _build_auth_with_secrets()
rendered = repr(auth)
# ``httpx.Cookies`` iterates names; reach into the underlying
# ``http.cookiejar.CookieJar`` for the (name, value) pairs.
for cookie in auth.cookie_jar.jar: # type: ignore[union-attr]
assert cookie.value not in rendered, f"Cookie value for {cookie.name} leaked into repr"
def test_repr_identifies_class_and_safe_metadata() -> None:
"""Redacted repr still names the class and shows non-secret debug info."""
auth = _build_auth_with_secrets()
rendered = repr(auth)
assert rendered.startswith("AuthTokens(")
# authuser is not credential-equivalent — surface it for debuggability.
assert "authuser=1" in rendered
# account_email is identity-equivalent (sub-PII) but already exposed
# in user-facing CLI output (`notebooklm status`), so it stays.
assert "alice@example.com" in rendered
def test_repr_marks_secret_fields_as_redacted() -> None:
"""The redaction is explicit so a reader knows fields exist + are hidden."""
auth = _build_auth_with_secrets()
rendered = repr(auth)
assert "redacted" in rendered.lower()
# The three secret-shaped fields should each show up as redacted.
assert "csrf_token" in rendered
assert "session_id" in rendered
assert "cookies" in rendered
def test_repr_cookie_count_reflects_actual_size() -> None:
"""The placeholder includes the cookie count so reprs aid debugging."""
auth = _build_auth_with_secrets()
rendered = repr(auth)
# 3 cookies in the fixture above.
assert "3" in rendered
def test_repr_handles_empty_cookies_and_no_jar() -> None:
"""Edge case: empty cookies, no jar override — repr should not raise."""
auth = AuthTokens(
cookies={},
csrf_token="",
session_id="",
)
rendered = repr(auth)
assert rendered.startswith("AuthTokens(")
assert "redacted" in rendered.lower()
def test_repr_does_not_leak_cookie_snapshot_values() -> None:
"""``cookie_snapshot`` mirrors the live jar and must also be redacted."""
from notebooklm._auth.storage import snapshot_cookie_jar
auth = _build_auth_with_secrets()
snapshot = snapshot_cookie_jar(auth.cookie_jar) # type: ignore[arg-type]
auth_with_snapshot = AuthTokens(
cookies=auth.cookies,
csrf_token=_SECRET_CSRF,
session_id=_SECRET_SESSION,
cookie_jar=auth.cookie_jar,
cookie_snapshot=snapshot,
)
rendered = repr(auth_with_snapshot)
assert _SECRET_PSID not in rendered
assert _SECRET_PSIDTS not in rendered
assert _SECRET_SID not in rendered
@pytest.mark.parametrize(
"secret",
[_SECRET_PSID, _SECRET_PSIDTS, _SECRET_SID, _SECRET_CSRF, _SECRET_SESSION],
)
def test_repr_redacts_every_canonical_secret(secret: str) -> None:
"""Belt-and-suspenders: parametrized check across all fixture secrets."""
auth = _build_auth_with_secrets()
rendered = repr(auth)
assert secret not in rendered, f"Secret {secret!r} leaked into repr"