项目文件夹

文件
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

200 行
9.5 KiB
Python

"""``make_fake_core`` factory — constructor-injection substrate for sub-clients.
This module provides a single entry point — :func:`make_fake_core` — that
returns a ``FakeSession`` instance shaped to satisfy the **shared
capability Protocols** in :mod:`notebooklm._runtime.contracts`
(``RpcCaller``, ``LoopGuard``, ``Kernel``) plus the single-consumer
Protocols inlined into their owning feature modules in issue #1327
(``AuthMetadata`` in ``notebooklm._source.upload``,
``OperationScopeProvider`` in ``notebooklm._artifact.polling``). Feature APIs that
need more than one capability take their direct collaborators by
keyword-only constructor argument (``ChatAPI`` in ``notebooklm._chat.api``,
``ArtifactsAPI`` in ``_artifacts.py``, ``SourceUploadPipeline`` in
``notebooklm._source.upload``); the feature-local composite Protocols
``ArtifactsRuntime`` and ``UploadRuntime`` (and their adapter
dataclasses) were retired once it was clear they only hid three stable
collaborators with one production satisfier. (``ChatRuntime`` was
deleted earlier on the same grounds — ADR-0014 Rule 2 Corollary.) The
``RpcCaller`` surface is exposed two ways: directly as
``fake.rpc_call`` (legacy single-attribute access path that some tests
still use) AND as ``fake.rpc_executor.rpc_call`` mirroring the
production composition where ``NotebookLMClient`` stores
``composed.executor`` as ``self._rpc_executor`` and passes it to every
feature API. Both attributes are wired to the same underlying mock so
``fake.rpc_call.assert_awaited`` and
``fake.rpc_executor.rpc_call.assert_awaited`` observe the same calls.
Tests pass the result to a sub-client constructor (e.g.
``NotebooksAPI(fake.rpc_executor)``) instead of constructing a real
client/runtime stack and mutating its attributes after the fact.
Phase 7 (refactor-history.md §Migration Plan step 10) deleted the broad
``Session`` Protocol that this factory's defaults dict previously
mirrored member-for-member. The dict now lists only the attribute slots
features actually exercise — promoting an attribute requires a real
test-site consumer, mirroring the ADR-0013 promotion criterion for
shared Protocols.
See :doc:`docs/adr/0007-test-monkeypatch-policy.md` for the policy that
makes this factory the only sanctioned substitute for the forbidden
``monkeypatch.setattr("notebooklm.…")`` and
``target.rpc_call = AsyncMock(…)`` patterns.
Design choices (documented in ADR-0007 "Alternatives considered"):
- ``FakeSession`` is a plain class with explicit attribute storage
(``types.SimpleNamespace``-shaped). It is *not* a spec-based
``MagicMock`` because spec-based mocks silently auto-vivify
attributes and would tie the factory to a single concrete class
shape rather than the open set of narrow Protocols.
- Async-surface defaults use :class:`unittest.mock.AsyncMock`;
sync-surface defaults use :class:`unittest.mock.MagicMock`. Both are
configured with benign return values so a test that only exercises one
attribute does not have to define the others.
- Overrides are keyword-only — positional arguments would conflict with
the ``**overrides`` extension point if new attributes are added later.
"""
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import httpx
class FakeSession:
"""A duck-typed stand-in for capability-Protocol collaborators in tests.
Named ``FakeSession`` for backward compatibility with the broad-
``Session``-era test sites; the class itself is just an explicit
attribute bag and is not pinned to any single Protocol shape.
Attribute storage is explicit (the constructor only sets what's
passed in) so that accessing an attribute the production code does
not actually use surfaces as a clear ``AttributeError`` rather than
as a silent auto-vivified ``MagicMock``. The canonical schema lives
in :func:`make_fake_core`'s ``defaults`` dict — one source of truth
so the schema cannot drift between two declarations.
Most tests should construct instances via :func:`make_fake_core`,
which fills in benign defaults; direct construction is also
supported when a test wants to assert that no defaults are read.
"""
def __init__(self, **attrs: Any) -> None:
for name, value in attrs.items():
setattr(self, name, value)
def make_fake_core(**overrides: Any) -> FakeSession:
"""Return a :class:`FakeSession` with benign defaults overridden.
All overrides are keyword-only and replace the corresponding default.
Passing an unknown keyword raises ``TypeError`` early so test typos
don't silently no-op.
The historical ``rpc_call=`` keyword is preserved as a convenience —
it is unwrapped into ``rpc_executor=SimpleNamespace(rpc_call=<value>)``
so the live ``RpcCaller`` Protocol surface on the fake matches the
production shape (``NotebookLMClient.__init__`` stores
``composed.executor`` as ``self._rpc_executor`` and passes it to
every feature API).
Example::
fake = make_fake_core(rpc_call=AsyncMock(return_value=[payload]))
api = NotebooksAPI(fake.rpc_executor)
result = await api.list()
fake.rpc_executor.rpc_call.assert_awaited_once()
"""
def _operation_scope(_label: str):
@asynccontextmanager
async def scope() -> AsyncIterator[None]:
yield None
return scope()
live_cookies = httpx.Cookies()
fake_http_client = SimpleNamespace(cookies=live_cookies)
auth = SimpleNamespace(authuser=0, account_email=None)
kernel = SimpleNamespace(
cookies=live_cookies,
get_http_client=MagicMock(return_value=fake_http_client),
)
# Phase 7 (refactor-history.md §Migration Plan step 10) shrunk this dict from
# the broad-Session-era 25+ entries to the minimum set that satisfies
# the post-refactor capability and feature-local runtime Protocols.
# New entries should only be added when a real test site exercises
# the attribute — mirroring the ADR-0013 promotion criterion for
# shared Protocols (≥2 consumers).
# ``rpc_call`` is shared between the direct ``fake.rpc_call`` and the
# ``fake.rpc_executor.rpc_call`` mirror so both attribute paths see
# the same observed calls. Fresh list per call so tests can mutate
# the response without bleeding into siblings.
rpc_call_mock = AsyncMock(side_effect=lambda *a, **kw: [])
defaults: dict[str, Any] = {
# AuthMetadata + Kernel — consumed by SourceUploadPipeline test sites.
"auth": auth,
"kernel": kernel,
# RpcCaller — every feature API uses this. The fake exposes the
# executor as a SimpleNamespace mirror so test sites address it
# the same way production code does (``fake.rpc_executor.rpc_call``
# mirrors ``client._rpc_executor.rpc_call``); the direct
# ``rpc_call`` attribute is kept for legacy single-attribute test sites
# that still treat the fake as a single bag-of-attributes.
"rpc_call": rpc_call_mock,
"rpc_executor": SimpleNamespace(rpc_call=rpc_call_mock),
# LoopGuard + OperationScopeProvider (the latter lives in
# ``notebooklm._artifact.polling`` after #1327) — used by ArtifactsAPI polling
# and SourceUploadPipeline.
"assert_bound_loop": MagicMock(return_value=None),
"operation_scope": MagicMock(side_effect=_operation_scope),
# DrainHookRegistration (local in ``_artifacts.py``) — close-time
# hook the artifacts runtime registers against in
# ``ArtifactsAPI.__init__``. Wave 2 of session-decoupling moved
# the storage onto ``TransportDrainTracker`` (ADR-0014 Rule 1); we
# keep ``_drain_hooks`` as a public attribute on the fake so test
# sites that previously read ``fake._drain_hooks["name"]`` still
# work (the fake doesn't have a real ``_drain_tracker``).
"_drain_hooks": {},
"register_drain_hook": MagicMock(return_value=None),
# Upload-pipeline glue: queue-wait recorder consumed by the
# ``SourceUploadPipeline`` upload metrics path. Kept on the bag
# so test sites that wire a SourcesAPI + uploader pair against a
# single FakeSession can rely on it.
"record_upload_queue_wait": MagicMock(return_value=None),
# NotebookSourceLister stub — exercised by ``test_notebooks.py``
# paths that resolve source IDs through the lister collaborator.
"get_source_ids": AsyncMock(side_effect=lambda *a, **kw: []),
}
def _register_drain_hook(name: str, hook: Any) -> None:
defaults["_drain_hooks"][name] = hook
defaults["register_drain_hook"] = MagicMock(side_effect=_register_drain_hook)
# Convenience: ``rpc_call=AsyncMock(...)`` overrides BOTH the direct
# ``rpc_call`` attribute AND the ``rpc_executor.rpc_call`` mirror with
# the same mock so test idioms using either path observe the same
# interactions.
if "rpc_call" in overrides:
overrides["rpc_executor"] = SimpleNamespace(rpc_call=overrides["rpc_call"])
# Validate overrides early so a typo like ``rpc_cal=`` fails loudly
# rather than landing as an unread attribute.
unknown = set(overrides) - set(defaults)
if unknown:
raise TypeError(
"make_fake_core() got unexpected keyword(s): "
f"{sorted(unknown)!r}. Known attributes: {sorted(defaults)!r}"
)
defaults.update(overrides)
return FakeSession(**defaults)