Files
teng-lin--notebooklm-py/tests/unit/cli/test_session_patch_surface.py
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

149 lines
6.1 KiB
Python

"""Patch-surface preservation for the ``cli/services/login`` re-export block.
The session command module (``notebooklm.cli.session_cmd``) re-exports a handful of
internal helpers from ``cli/services/login`` so legacy test code can
monkey-patch them through the session module's namespace
(``notebooklm.cli.session_cmd._refresh_from_browser_cookies = …`` and
friends).
P3.T4 split the former ``cli.services.login`` module into a package. The split MUST
preserve every previously re-exported name so the patch sites that
target the session module's namespace keep working byte-for-byte.
#1367 retired the pure patch-surface re-exports (the names that were
*only* re-exported for tests to patch, never called from
``session_cmd``'s body): the six Category-4 login privates
(``_build_google_cookie_domains``, ``_enumerate_one_jar``,
``_login_with_browser_cookies``, ``_resolve_optional_cookie_domains``,
``_select_account``, ``_write_extracted_cookies``) left the baseline
because their tests now import the symbol from its real home module
(``services.login``). The body-used privates that remain (e.g.
``_sync_server_language_to_config``, ``_refresh_from_browser_cookies``)
stay re-exported and stay in the baseline.
This test uses a fixed golden baseline (``tests/_fixtures/
session_reexport_baseline.txt``) as the single source of truth. For each
name in the baseline the test asserts:
1. **Importable**: ``getattr(session_module, name)`` returns a non-None
object.
2. **Correct type**: callable names are still ``callable()``; constant
names retain their stable type identity (str / dict / etc.).
3. **Patchable**: ``monkeypatch.setattr(f"{session_module}.{name}", …)``
succeeds and is observable via ``getattr`` immediately after.
The test does NOT auto-rediscover names from the new re-export block —
that would defeat preservation. The baseline fixture is the source of
truth; if a name is intentionally removed in T4, the executor must
also remove it from the fixture AND justify the removal in the PR
description.
"""
from __future__ import annotations
import importlib
import importlib.util
from pathlib import Path
import pytest
_BASELINE_PATH = (
Path(__file__).resolve().parents[3] / "tests" / "_fixtures" / "session_reexport_baseline.txt"
)
def _active_session_module_name() -> str:
"""Return the active session command module name.
The active module in this tree is ``notebooklm.cli.session_cmd``. Keep the
legacy fallback so the fixture fails with a clear message if a downstream
branch still carries the old name.
"""
if importlib.util.find_spec("notebooklm.cli.session_cmd") is not None:
return "notebooklm.cli.session_cmd"
if importlib.util.find_spec("notebooklm.cli.session") is not None:
return "notebooklm.cli.session"
raise RuntimeError(
"Neither notebooklm.cli.session_cmd nor notebooklm.cli.session is importable."
)
def _load_baseline_names() -> list[str]:
if not _BASELINE_PATH.exists():
raise RuntimeError(
f"Golden baseline fixture missing: {_BASELINE_PATH}. "
"Re-capture it before running this test."
)
return [
line.strip()
for line in _BASELINE_PATH.read_text(encoding="utf-8").splitlines()
if line.strip()
]
# Expected-type map for non-callable names. Functions / classes are checked
# via ``callable()``; everything in this map is checked via ``isinstance()``.
# Keep in sync with the baseline fixture: a name listed here MUST also be
# in the baseline file. If T4 adds a constant to the re-export surface,
# add it both here AND to the baseline.
_EXPECTED_CONSTANT_TYPES: dict[str, type] = {
# Currently empty — the rev-3-era source-plan referenced
# ``_INCLUDE_DOMAINS_ALL`` and ``_ROOKIEPY_BROWSER_ALIASES`` as
# constants in the re-export block, but at T4 dispatch HEAD the
# session_cmd.py re-export block contains functions only (13 names,
# all callable). If a future change adds constants back to the
# re-export surface, list them here with their stable type.
}
@pytest.fixture
def session_module():
"""Return the active session command module (imported fresh)."""
name = _active_session_module_name()
return importlib.import_module(name)
def test_baseline_fixture_is_nonempty() -> None:
names = _load_baseline_names()
assert names, (
f"{_BASELINE_PATH} is empty. The fixture must list every name re-exported "
"from the active session module's ``from .services.login import (...)`` block."
)
@pytest.mark.parametrize("name", _load_baseline_names())
def test_name_is_importable_and_correctly_typed(name: str, session_module) -> None:
obj = getattr(session_module, name, None)
assert obj is not None, (
f"{session_module.__name__}.{name} is missing — the T4 split or session re-export "
"block dropped this name. Restore the re-export (preferred) or justify the removal "
"in the PR description AND remove the name from the golden baseline."
)
expected_type = _EXPECTED_CONSTANT_TYPES.get(name)
if expected_type is None:
# Default: callable (function or class).
assert callable(obj), (
f"{session_module.__name__}.{name} is no longer callable (type={type(obj).__name__}). "
"If this is intentional, add an entry to _EXPECTED_CONSTANT_TYPES."
)
else:
assert isinstance(obj, expected_type), (
f"{session_module.__name__}.{name} is type {type(obj).__name__}, "
f"expected {expected_type.__name__}."
)
@pytest.mark.parametrize("name", _load_baseline_names())
def test_name_is_monkeypatchable(
name: str, session_module, monkeypatch: pytest.MonkeyPatch
) -> None:
target = f"{session_module.__name__}.{name}"
sentinel = object()
monkeypatch.setattr(target, sentinel)
assert getattr(session_module, name) is sentinel, (
f"monkeypatch.setattr({target!r}, …) did not take effect. The name is no longer "
"a direct attribute of the session module — it may have been replaced by a "
"descriptor, a property, or a module-level __getattr__ that ignores writes."
)