Files
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

126 lines
5.1 KiB
Python

"""Regression test for the httpx connection-pool tuning via ConnectionLimits.
Audit item #3 (`thread-safety-concurrency-audit.md` §3, also §19):
Pre-fix, runtime configuration used by ``NotebookLMClient.__init__`` /
``from_storage`` did not pass a ``limits=`` kwarg to ``httpx.AsyncClient(...)``,
defaulting to httpx's `~100 / 20-per-host` pool.
Heavy fan-out workloads (FastAPI services sharing a client across many
concurrent requests, large `wait_for_sources` batches) tripped
`httpx.PoolTimeout`.
Post-fix: a stable `ConnectionLimits` dataclass on
`notebooklm.types` exposes pool tuning, defaults to
`max_connections=100, max_keepalive_connections=50,
keepalive_expiry=30.0`, and is plumbed through runtime configuration used by
``NotebookLMClient.__init__`` / ``from_storage``.
The test asserts the limits passed to `httpx.AsyncClient` match the
configured values. A real-pool stress test (200-way fan-out against
a stdlib http.server) is the audit's preferred verification, but the
deterministic constructor-args check is sufficient and avoids the
infrastructure overhead of an in-process server in the test suite.
"""
from __future__ import annotations
import httpx
import pytest
import notebooklm._runtime.init as _runtime_init
from notebooklm import NotebookLMClient
from notebooklm.types import ConnectionLimits
# pool-config + patch-based tuning tests; no HTTP, no cassette.
# Opt out of the tier-enforcement hook in tests/integration/conftest.py.
pytestmark = pytest.mark.allow_no_vcr
def test_connection_limits_dataclass_defaults() -> None:
"""The defaults match the audit's recommendation for typical fan-out."""
limits = ConnectionLimits()
assert limits.max_connections == 100
assert limits.max_keepalive_connections == 50
assert limits.keepalive_expiry == 30.0
def test_connection_limits_to_httpx_limits_round_trip() -> None:
"""to_httpx_limits() preserves all three fields."""
cl = ConnectionLimits(
max_connections=200,
max_keepalive_connections=80,
keepalive_expiry=15.0,
)
hl = cl.to_httpx_limits()
assert isinstance(hl, httpx.Limits)
assert hl.max_connections == 200
assert hl.max_keepalive_connections == 80
assert hl.keepalive_expiry == 15.0
async def test_default_limits_passed_to_async_client(auth_tokens, monkeypatch) -> None:
"""No explicit ``limits=`` -> runtime config uses ConnectionLimits() defaults."""
captured: dict[str, httpx.Limits | None] = {"limits": None}
calls = {"count": 0}
real_async_client = httpx.AsyncClient
def _capturing_client(**kwargs: object) -> httpx.AsyncClient:
calls["count"] += 1
captured["limits"] = kwargs.get("limits") # type: ignore[assignment]
return real_async_client(**kwargs) # type: ignore[arg-type]
# ADR-0007 Form-2: object-form patch against the locally-imported
# `_runtime.init` seam alias. `_runtime_init.httpx` is the same module
# object the production factory reads `AsyncClient` off of, so patching
# the attribute here intercepts default-path client construction.
monkeypatch.setattr(_runtime_init.httpx, "AsyncClient", _capturing_client)
async with NotebookLMClient(auth_tokens):
pass
# Bite-check: the injected seam was actually exercised.
assert calls["count"] >= 1
captured_limits = captured["limits"]
assert isinstance(captured_limits, httpx.Limits)
assert captured_limits.max_connections == 100
assert captured_limits.max_keepalive_connections == 50
assert captured_limits.keepalive_expiry == 30.0
async def test_custom_limits_passed_to_async_client(auth_tokens, monkeypatch) -> None:
"""Explicit `limits=ConnectionLimits(...)` -> AsyncClient sees those values."""
custom = ConnectionLimits(
max_connections=500,
max_keepalive_connections=100,
keepalive_expiry=10.0,
)
captured: dict[str, httpx.Limits | None] = {"limits": None}
calls = {"count": 0}
real_async_client = httpx.AsyncClient
def _capturing_client(**kwargs: object) -> httpx.AsyncClient:
calls["count"] += 1
captured["limits"] = kwargs.get("limits") # type: ignore[assignment]
return real_async_client(**kwargs) # type: ignore[arg-type]
# ADR-0007 Form-2: object-form patch against the locally-imported
# `_runtime.init` seam alias (see the default-limits test above).
monkeypatch.setattr(_runtime_init.httpx, "AsyncClient", _capturing_client)
async with NotebookLMClient(auth_tokens, limits=custom):
pass
# Bite-check: the injected seam was actually exercised.
assert calls["count"] >= 1
captured_limits = captured["limits"]
assert isinstance(captured_limits, httpx.Limits)
assert captured_limits.max_connections == 500
assert captured_limits.max_keepalive_connections == 100
assert captured_limits.keepalive_expiry == 10.0
def test_connection_limits_is_frozen() -> None:
"""Frozen dataclass — callers can't accidentally mutate after passing in."""
import dataclasses
limits = ConnectionLimits()
with pytest.raises(dataclasses.FrozenInstanceError):
limits.max_connections = 999 # type: ignore[misc]