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
100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
"""Shared fixtures for the REST server test suite.
|
|
|
|
The canonical contributor install omits the ``server`` extra (no fastapi), and CI
|
|
runs the full pytest after that install — so importing fastapi at collection time
|
|
would break the whole run. ``pytest.importorskip("fastapi")`` skips this entire
|
|
directory cleanly when the extra is absent; the server suite runs under
|
|
``--extra server``.
|
|
|
|
Every test drives the app through a FastAPI ``TestClient`` with an injected FAKE
|
|
client (``client_factory``) — no real auth, no network. The shared
|
|
``authed_client`` fixture sets the ``Authorization: Bearer`` header and a loopback
|
|
``Host`` on every request, satisfying both the bearer-token gate and the
|
|
DNS-rebinding (loopback-Host) guard.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator, Callable, Iterator
|
|
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip("fastapi")
|
|
|
|
from fastapi.testclient import TestClient # noqa: E402 - after importorskip guard
|
|
|
|
# Import the server package only after the importorskip guard.
|
|
from notebooklm.server._auth import SERVER_TOKEN_ENV # noqa: E402
|
|
from notebooklm.server.app import create_app # noqa: E402
|
|
|
|
from .fakes import FakeClient # noqa: E402
|
|
|
|
#: The bearer token the test server validates against.
|
|
TEST_TOKEN = "test-secret-token"
|
|
|
|
STALE_AUTH_MESSAGE = (
|
|
"Authentication expired or invalid. Redirected to: https://accounts.google.com/signin. "
|
|
"Run 'notebooklm login' to re-authenticate."
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_client() -> FakeClient:
|
|
"""A fresh in-memory fake client with no notebooks/sources/artifacts."""
|
|
return FakeClient()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _server_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Configure the bearer token for every server test."""
|
|
monkeypatch.setenv(SERVER_TOKEN_ENV, TEST_TOKEN)
|
|
|
|
|
|
def _factory_for(client: FakeClient) -> Any:
|
|
@asynccontextmanager
|
|
async def factory() -> Any:
|
|
yield client
|
|
|
|
return factory
|
|
|
|
|
|
def stale_auth_factory() -> Callable[[], AbstractAsyncContextManager[FakeClient]]:
|
|
@asynccontextmanager
|
|
async def factory() -> AsyncIterator[FakeClient]:
|
|
raise ValueError(STALE_AUTH_MESSAGE)
|
|
yield FakeClient() # pragma: no cover - makes this an async context manager
|
|
|
|
return factory
|
|
|
|
|
|
@pytest.fixture
|
|
def app(fake_client: FakeClient) -> Any:
|
|
"""A FastAPI app bound to the fake client."""
|
|
return create_app(client_factory=_factory_for(fake_client))
|
|
|
|
|
|
@pytest.fixture
|
|
def raw_client(app: Any) -> Iterator[TestClient]:
|
|
"""A TestClient WITHOUT auth headers (for auth-rejection tests).
|
|
|
|
``client=("127.0.0.1", …)`` gives requests a loopback PEER address so the
|
|
unspoofable peer-loopback guard passes — leaving the Host-header /
|
|
bearer-token gates as the thing under test.
|
|
"""
|
|
# raise_server_exceptions=False so a projected 500 envelope (the UNEXPECTED
|
|
# bug path) is returned rather than re-raised into the test.
|
|
with TestClient(app, client=("127.0.0.1", 5555), raise_server_exceptions=False) as client:
|
|
yield client
|
|
|
|
|
|
@pytest.fixture
|
|
def authed_client(app: Any) -> Iterator[TestClient]:
|
|
"""A TestClient with a valid bearer token + loopback Host + loopback peer."""
|
|
headers = {"Authorization": f"Bearer {TEST_TOKEN}", "Host": "127.0.0.1"}
|
|
with TestClient(
|
|
app, headers=headers, client=("127.0.0.1", 5555), raise_server_exceptions=False
|
|
) as client:
|
|
yield client
|