"""Unit tests for auth: GitHub auth config and access token validation.""" import base64 import json import time import pytest from src.services import auth from src.utils.errors import AuthServiceError def _make_jwt(exp: int | None = None) -> str: """Minimal 3-part JWT with optional exp claim (no signature verification).""" payload = {} if exp is not None: payload["exp"] = exp payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") header_b64 = base64.urlsafe_b64encode(b'{"alg":"HS256"}').decode().rstrip("=") return f"{header_b64}.{payload_b64}.sig" class TestValidateAccessToken: """validate_access_token requires non-empty token and rejects expired JWT.""" def test_raises_when_token_none(self): with pytest.raises(AuthServiceError) as exc_info: auth.validate_access_token(None) assert "required" in exc_info.value.message and "empty" in exc_info.value.message def test_raises_when_token_empty(self): with pytest.raises(AuthServiceError) as exc_info: auth.validate_access_token("") assert "required" in exc_info.value.message def test_raises_when_token_whitespace_only(self): with pytest.raises(AuthServiceError): auth.validate_access_token(" \t ") def test_returns_stripped_token_when_non_jwt(self): # decode_jwt_exp returns None for non-JWT, so no expiry check result = auth.validate_access_token(" opaque-token-xyz ") assert result == "opaque-token-xyz" def test_returns_stripped_token_when_jwt_not_expired(self): future_exp = int(time.time()) + 3600 token = _make_jwt(exp=future_exp) result = auth.validate_access_token(f" {token} ") assert result == token def test_raises_when_jwt_expired(self): # exp in the past; time.time() >= exp - EXPIRY_BUFFER_SECONDS past_exp = int(time.time()) - 120 token = _make_jwt(exp=past_exp) with pytest.raises(AuthServiceError) as exc_info: auth.validate_access_token(token) assert "expired" in exc_info.value.message or "Invalid" in exc_info.value.message def test_raises_when_jwt_exp_at_buffer_boundary(self, monkeypatch): # exp = now - 1; buffer 60 -> expired (time.time() is called as method, receives self) now = 1000000 monkeypatch.setattr(auth, "time", type("Time", (), {"time": lambda self: now})()) exp_just_expired = now - 1 token = _make_jwt(exp=exp_just_expired) with pytest.raises(AuthServiceError): auth.validate_access_token(token) def test_accepts_when_jwt_exp_just_future_of_buffer(self, monkeypatch): # exp = now + 61; exp - 60 = now + 1 > now -> not expired (time.time() receives self) now = 1000000 monkeypatch.setattr(auth, "time", type("Time", (), {"time": lambda self: now})()) exp_future = now + 61 token = _make_jwt(exp=exp_future) result = auth.validate_access_token(token) assert result == token