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

77 lines
3.4 KiB
Python

"""Unit tests for the transport-neutral source batch-add policy (``_app.source_batch``).
``batch_item_is_fatal`` decides whether a per-item add failure aborts the batch. It
is the shared classifier both the REST route and the MCP tool consult, so a
mis-bucketed category would silently change REST's already-green batch behavior with
no other failing test — hence this exhaustive all-14-category gate with an
INDEPENDENT expected table (not a re-import of ``_FATAL_CATEGORIES``).
"""
from __future__ import annotations
import pytest
from notebooklm import exceptions as exc
from notebooklm._app import SourceMutationError
from notebooklm._app.errors import ErrorCategory
from notebooklm._app.source_batch import MAX_BATCH_URLS, batch_item_is_fatal
# One exemplar exception per ErrorCategory (mirrors the classify-consistency gate).
_EXEMPLARS: list[tuple[ErrorCategory, BaseException]] = [
(ErrorCategory.NOT_FOUND, exc.SourceNotFoundError("src_456")),
(ErrorCategory.AUTH, exc.AuthError("auth failed")),
(ErrorCategory.RATE_LIMITED, exc.RateLimitError("slow down", retry_after=5)),
(ErrorCategory.VALIDATION, exc.ValidationError("bad input")),
(ErrorCategory.CONFIG, exc.ConfigurationError("missing config")),
(ErrorCategory.NETWORK, exc.NetworkError("connection refused")),
(ErrorCategory.NOTEBOOK_LIMIT, exc.NotebookLimitError(499, limit=500)),
(ErrorCategory.ARTIFACT_TIMEOUT, exc.ArtifactTimeoutError("nb-1", "task-1", 30.0)),
(ErrorCategory.TIMEOUT, exc.WaitTimeoutError("generic wait timed out")),
(ErrorCategory.SERVER, exc.ServerError("upstream 503")),
(ErrorCategory.RPC, exc.RPCError("decode failed", method_id="abc123")),
(ErrorCategory.SOURCE_MUTATION, SourceMutationError("ambiguous", "AMBIGUOUS_ID")),
(ErrorCategory.LIBRARY, exc.NotebookLMError("some library error")),
(ErrorCategory.UNEXPECTED, RuntimeError("boom")),
]
# INDEPENDENT oracle: fatal = the categories whose REST HTTP status is 401 / 429 /
# >=500 (a service/infra failure, not specific to one URL). Hand-written on purpose —
# do NOT derive this from `_FATAL_CATEGORIES` (that would make the test tautological).
_EXPECTED_FATAL: dict[ErrorCategory, bool] = {
ErrorCategory.NOT_FOUND: False, # 404
ErrorCategory.AUTH: True, # 401
ErrorCategory.RATE_LIMITED: True, # 429
ErrorCategory.VALIDATION: False, # 400
ErrorCategory.CONFIG: True, # 500
ErrorCategory.NETWORK: True, # 502
ErrorCategory.NOTEBOOK_LIMIT: False, # 409
ErrorCategory.ARTIFACT_TIMEOUT: True, # 504
ErrorCategory.TIMEOUT: True, # 504
ErrorCategory.SERVER: True, # 502
ErrorCategory.RPC: True, # 502
ErrorCategory.SOURCE_MUTATION: False, # 422
ErrorCategory.LIBRARY: True, # 500
ErrorCategory.UNEXPECTED: True, # 500
}
def test_expected_table_covers_every_category() -> None:
"""A new ErrorCategory without an expected verdict fails here (no silent gap)."""
assert set(_EXPECTED_FATAL) == set(ErrorCategory)
assert {category for category, _ in _EXEMPLARS} == set(ErrorCategory)
@pytest.mark.parametrize(
("category", "exception"),
_EXEMPLARS,
ids=[category.name for category, _ in _EXEMPLARS],
)
def test_batch_item_is_fatal_matches_expected(
category: ErrorCategory, exception: BaseException
) -> None:
assert batch_item_is_fatal(exception) is _EXPECTED_FATAL[category]
def test_max_batch_urls_is_positive() -> None:
assert isinstance(MAX_BATCH_URLS, int) and MAX_BATCH_URLS > 0