tests: backfill coverage for openrouter transport, auth sessions, cadence
Three new test files covering modules the audit flagged as having zero direct coverage: - test_openrouter_transport.py (18 tests): provider chain selection, endpoint resolution, _call_provider parse path (including the reasoning-field fallback and token-based cost estimation), and call_llm's cross-provider failover. Uses httpx.MockTransport so no network. Patches _call_provider for failover tests to bypass tenacity's retry delays. - test_auth_session.py (7 tests): sign/verify round-trip, tampered cookie rejection, expired cookie rejection (via TTL monkeypatch), garbage input handling, salt isolation between session and pending serializers, and rejection of cookies signed with a different secret. - test_cadence_policy.py (16 tests): is_active_window weekday/weekend + half-open interval boundaries, min_gap_hours across bands, should_run gating for first-run / active / off-hours / weekend / naive-datetime cases, and the NEWS_POLICY 20-minute / 3-hour variations. Suite goes from 291 to 336 passing.
This commit is contained in:
parent
83995e96c8
commit
f9f4f25ef7
3 changed files with 500 additions and 0 deletions
81
tests/test_auth_session.py
Normal file
81
tests/test_auth_session.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""Session cookie sign/verify — security-critical edges that the
|
||||
existing test suite uses as a fixture (``sign_session(1)`` for cookies)
|
||||
but doesn't actually probe.
|
||||
|
||||
Covers:
|
||||
- Round-trip: sign(user_id) → verify → user_id
|
||||
- Tampered cookie → None (not raised)
|
||||
- Expired cookie → None (via itsdangerous max_age)
|
||||
- Garbage / non-serializer-format input → None
|
||||
- Wrong-salt isolation: a pending cookie can't be unlocked by the
|
||||
session verifier (and vice versa)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
from app import auth
|
||||
|
||||
|
||||
def test_session_signed_token_round_trips():
|
||||
cookie = auth.sign_session(42)
|
||||
assert auth.verify_session(cookie) == 42
|
||||
|
||||
|
||||
def test_session_token_is_opaque_url_safe():
|
||||
"""Sanity: the serializer produces a URL-safe string with at least
|
||||
two dot-separated segments (payload.timestamp.signature). Not a
|
||||
semantic test, but catches a future swap to an un-encoded format."""
|
||||
cookie = auth.sign_session(7)
|
||||
assert "." in cookie
|
||||
assert " " not in cookie
|
||||
|
||||
|
||||
def test_tampered_session_cookie_returns_none():
|
||||
"""Flip a single character in the signature segment and verify
|
||||
the cookie no longer authenticates — without exceptions leaking."""
|
||||
cookie = auth.sign_session(99)
|
||||
# Flip the last character (signature segment).
|
||||
tampered = cookie[:-1] + ("a" if cookie[-1] != "a" else "b")
|
||||
assert auth.verify_session(tampered) is None
|
||||
|
||||
|
||||
def test_garbage_session_cookie_returns_none():
|
||||
assert auth.verify_session("not-a-real-cookie") is None
|
||||
assert auth.verify_session("") is None
|
||||
assert auth.verify_session("a.b.c") is None
|
||||
|
||||
|
||||
def test_expired_session_cookie_returns_none(monkeypatch):
|
||||
"""Forge a cookie with an ancient timestamp and confirm the TTL
|
||||
check rejects it. We bypass sign_session() so the timestamp is
|
||||
in our control rather than `now`."""
|
||||
s = auth._serializer()
|
||||
# itsdangerous stores the issued-at timestamp in a base62 segment.
|
||||
# Easier than hand-building: monkeypatch the SESSION_TTL_SECONDS
|
||||
# to a negative value so any freshly-signed cookie is "expired"
|
||||
# the moment we verify it.
|
||||
cookie = auth.sign_session(123)
|
||||
monkeypatch.setattr(auth, "SESSION_TTL_SECONDS", -1)
|
||||
assert auth.verify_session(cookie) is None
|
||||
|
||||
|
||||
def test_session_serializer_isolated_from_pending_serializer():
|
||||
"""A pending-verify cookie must not authenticate as a session
|
||||
(different salts), and vice versa — otherwise the half-finished
|
||||
OTP flow becomes a free login."""
|
||||
pending = auth.sign_pending("u@x", 5)
|
||||
session = auth.sign_session(5)
|
||||
assert auth.verify_session(pending) is None
|
||||
assert auth.verify_pending(session) is None
|
||||
|
||||
|
||||
def test_session_cookie_signed_with_different_secret_rejected(monkeypatch):
|
||||
"""Defence-in-depth: signing with a different secret produces a
|
||||
cookie that the live verifier (using the configured secret)
|
||||
rejects. Confirms we're actually checking the HMAC, not just the
|
||||
payload format."""
|
||||
rogue = URLSafeTimedSerializer("totally-different-secret",
|
||||
salt="cassandra-session-v1")
|
||||
rogue_cookie = rogue.dumps({"uid": 1})
|
||||
assert auth.verify_session(rogue_cookie) is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue