discovery-by-llm/backend/tests/test_spotify_auth.py

62 lines
2 KiB
Python

"""Tests for Spotify PKCE and token handling."""
import asyncio
import base64
import hashlib
import time
import httpx2
import pytest
from app.adapters.spotify.auth import (
TokenSet,
derive_code_challenge,
refresh_access_token,
)
from app.adapters.spotify.session import PendingLogins
def test_code_challenge_is_unpadded_base64url_sha256() -> None:
verifier = "a-fixed-code-verifier"
expected = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())
challenge = derive_code_challenge(verifier)
assert challenge == expected.rstrip(b"=").decode()
assert "=" not in challenge
def test_refresh_keeps_existing_refresh_token_when_omitted() -> None:
async def run() -> None:
async def handler(request: httpx2.Request) -> httpx2.Response:
assert request.url == "https://accounts.spotify.com/api/token"
return httpx2.Response(200, json={"access_token": "new", "expires_in": 3600})
tokens = TokenSet("old", "keep-me", time.monotonic() + 3600)
async with httpx2.AsyncClient(transport=httpx2.MockTransport(handler)) as http:
refreshed = await refresh_access_token(http, client_id="client", tokens=tokens)
assert refreshed.access_token == "new"
assert refreshed.refresh_token == "keep-me"
asyncio.run(run())
def test_token_expiry_uses_sixty_second_skew() -> None:
assert TokenSet("access", "refresh", time.monotonic() + 59).is_expired
assert not TokenSet("access", "refresh", time.monotonic() + 61).is_expired
def test_pending_login_add_sweeps_expired_entries(monkeypatch: pytest.MonkeyPatch) -> None:
current_time = 0.0
monkeypatch.setattr(
"app.adapters.spotify.session.time.monotonic",
lambda: current_time,
)
pending_logins = PendingLogins()
pending_logins.add("expired", "old-verifier")
current_time = 601.0
pending_logins.add("current", "new-verifier")
assert set(pending_logins._entries) == {"current"}