""" Tests for frontend authentication integration. These smoke tests verify that the key authentication and API endpoints work correctly with JWT tokens as expected by the frontend. """ import pytest from httpx import ASGITransport, AsyncClient from src.server.fastapi_app import app from src.server.services.auth_service import auth_service @pytest.fixture(autouse=True) def reset_auth(): """Reset authentication state before each test.""" # Reset auth service state original_hash = auth_service._hash auth_service._hash = None auth_service._failed.clear() yield # Restore auth_service._hash = original_hash auth_service._failed.clear() @pytest.fixture async def client(): """Create an async test client.""" transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as ac: yield ac class TestFrontendAuthIntegration: """Test authentication integration matching frontend expectations.""" async def test_setup_returns_ok_status(self, client): """Test setup endpoint returns expected format for frontend.""" response = await client.post( "/api/auth/setup", json={"master_password": "StrongP@ss123"} ) assert response.status_code == 201 data = response.json() # Frontend expects 'status': 'ok' assert data["status"] == "ok" async def test_login_returns_access_token(self, client): """Test login flow and verify JWT token is returned.""" # Setup master password first client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Login with correct password response = client.post( "/api/auth/login", json={"password": "StrongP@ss123"} ) assert response.status_code == 200 data = response.json() # Verify token is returned assert "access_token" in data assert data["token_type"] == "bearer" assert "expires_at" in data # Verify token can be used for authenticated requests token = data["access_token"] headers = {"Authorization": f"Bearer {token}"} response = client.get("/api/auth/status", headers=headers) assert response.status_code == 200 data = response.json() assert data["authenticated"] is True def test_login_with_wrong_password(self, client): """Test login with incorrect password.""" # Setup master password first client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Login with wrong password response = client.post( "/api/auth/login", json={"password": "WrongPassword"} ) assert response.status_code == 401 data = response.json() assert "detail" in data def test_logout_clears_session(self, client): """Test logout functionality.""" # Setup and login client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) login_response = client.post( "/api/auth/login", json={"password": "StrongP@ss123"} ) token = login_response.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} # Logout response = client.post("/api/auth/logout", headers=headers) assert response.status_code == 200 assert response.json()["status"] == "ok" def test_authenticated_request_without_token_returns_401(self, client): """Test that authenticated endpoints reject requests without tokens.""" # Setup master password client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Try to access authenticated endpoint without token response = client.get("/api/v1/anime") assert response.status_code == 401 def test_authenticated_request_with_invalid_token_returns_401(self, client): """Test that authenticated endpoints reject invalid tokens.""" # Setup master password client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Try to access authenticated endpoint with invalid token headers = {"Authorization": "Bearer invalid_token_here"} response = client.get("/api/v1/anime", headers=headers) assert response.status_code == 401 def test_remember_me_extends_token_expiry(self, client): """Test that remember_me flag affects token expiry.""" # Setup master password client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Login without remember me response1 = client.post( "/api/auth/login", json={"password": "StrongP@ss123", "remember": False} ) data1 = response1.json() # Login with remember me response2 = client.post( "/api/auth/login", json={"password": "StrongP@ss123", "remember": True} ) data2 = response2.json() # Both should return tokens with expiry assert "expires_at" in data1 assert "expires_at" in data2 def test_setup_fails_if_already_configured(self, client): """Test that setup fails if master password is already set.""" # Setup once client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # Try to setup again response = client.post( "/api/auth/setup", json={"master_password": "AnotherPassword123!"} ) assert response.status_code == 400 assert "already configured" in response.json()["detail"].lower() def test_weak_password_validation_in_setup(self, client): """Test that setup rejects weak passwords.""" # Try with short password response = client.post( "/api/auth/setup", json={"master_password": "short"} ) assert response.status_code == 400 # Try with all lowercase response = client.post( "/api/auth/setup", json={"master_password": "alllowercase"} ) assert response.status_code == 400 # Try without special characters response = client.post( "/api/auth/setup", json={"master_password": "NoSpecialChars123"} ) assert response.status_code == 400 class TestTokenAuthenticationFlow: """Test JWT token-based authentication workflow.""" def test_full_authentication_workflow(self, client): """Test complete authentication workflow with token management.""" # 1. Check initial status response = client.get("/api/auth/status") assert not response.json()["configured"] # 2. Setup master password client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) # 3. Login and get token response = client.post( "/api/auth/login", json={"password": "StrongP@ss123"} ) token = response.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} # 4. Access authenticated endpoint response = client.get("/api/auth/status", headers=headers) assert response.json()["authenticated"] is True # 5. Logout response = client.post("/api/auth/logout", headers=headers) assert response.json()["status"] == "ok" def test_token_included_in_all_authenticated_requests(self, client): """Test that token must be included in authenticated API requests.""" # Setup and login client.post("/api/auth/setup", json={"master_password": "StrongP@ss123"}) response = client.post( "/api/auth/login", json={"password": "StrongP@ss123"} ) token = response.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} # Test various authenticated endpoints endpoints = [ "/api/v1/anime", "/api/queue/status", "/api/config", ] for endpoint in endpoints: # Without token - should fail response = client.get(endpoint) assert response.status_code == 401, f"Endpoint {endpoint} should require auth" # With token - should work or return expected response response = client.get(endpoint, headers=headers) # Some endpoints may return 503 if services not configured, that's ok assert response.status_code in [200, 503], f"Endpoint {endpoint} failed with token"