read.markets/tests/test_auth_session.py
Giorgio Gilestro f9f4f25ef7 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.
2026-05-28 13:58:28 +02:00

81 lines
3.2 KiB
Python

"""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