"""Tests for Web Router (T47-T54). TDD: RED → GREEN → REFACTOR """ import pytest from fastapi.testclient import TestClient class TestLoginPage: """Test login page routes (T47).""" def test_login_page_get(self, client): """Test GET /login returns login page.""" response = client.get("/login") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "Login" in response.text or "login" in response.text.lower() def test_login_page_redirects_when_authenticated(self, authorized_client): """Test GET /login redirects to dashboard when already logged in.""" response = authorized_client.get("/login", follow_redirects=False) assert response.status_code == 302 assert "/dashboard" in response.headers.get("location", "") def test_login_post_valid_credentials(self, client, db_session): """Test POST /login with valid credentials.""" # Create a test user first from openrouter_monitor.models import User from openrouter_monitor.services.password import hash_password user = User( email="testlogin@example.com", hashed_password=hash_password("TestPassword123!") ) db_session.add(user) db_session.commit() # Attempt login response = client.post( "/login", data={ "email": "testlogin@example.com", "password": "TestPassword123!" }, follow_redirects=False ) assert response.status_code == 302 assert "access_token" in response.cookies assert "/dashboard" in response.headers.get("location", "") def test_login_post_invalid_credentials(self, client): """Test POST /login with invalid credentials shows error.""" response = client.post( "/login", data={ "email": "nonexistent@example.com", "password": "WrongPassword123!" } ) assert response.status_code == 401 assert "Invalid" in response.text or "error" in response.text.lower() class TestRegisterPage: """Test registration page routes (T48).""" def test_register_page_get(self, client): """Test GET /register returns registration page.""" response = client.get("/register") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "Register" in response.text or "register" in response.text.lower() def test_register_post_valid_data(self, client, db_session): """Test POST /register with valid data creates user.""" from openrouter_monitor.models import User response = client.post( "/register", data={ "email": "newuser@example.com", "password": "NewPassword123!", "password_confirm": "NewPassword123!" }, follow_redirects=False ) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") # Verify user was created user = db_session.query(User).filter(User.email == "newuser@example.com").first() assert user is not None def test_register_post_passwords_mismatch(self, client): """Test POST /register with mismatched passwords shows error.""" response = client.post( "/register", data={ "email": "test@example.com", "password": "Password123!", "password_confirm": "DifferentPassword123!" } ) assert response.status_code == 400 assert "match" in response.text.lower() or "error" in response.text.lower() def test_register_post_duplicate_email(self, client, db_session): """Test POST /register with existing email shows error.""" from openrouter_monitor.models import User from openrouter_monitor.services.password import hash_password # Create existing user existing = User( email="existing@example.com", hashed_password=hash_password("Password123!") ) db_session.add(existing) db_session.commit() response = client.post( "/register", data={ "email": "existing@example.com", "password": "Password123!", "password_confirm": "Password123!" } ) assert response.status_code == 400 assert "already" in response.text.lower() or "registered" in response.text.lower() class TestLogout: """Test logout route (T49).""" def test_logout_clears_cookie(self, authorized_client): """Test POST /logout clears access token cookie.""" response = authorized_client.post("/logout", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") # Cookie should be deleted assert response.cookies.get("access_token") == "" class TestDashboard: """Test dashboard route (T50).""" def test_dashboard_requires_auth(self, client): """Test GET /dashboard redirects to login when not authenticated.""" response = client.get("/dashboard", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") def test_dashboard_renders_for_authenticated_user(self, authorized_client): """Test GET /dashboard renders for authenticated user.""" response = authorized_client.get("/dashboard") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "dashboard" in response.text.lower() or "Dashboard" in response.text class TestApiKeys: """Test API keys management routes (T51).""" def test_keys_page_requires_auth(self, client): """Test GET /keys redirects to login when not authenticated.""" response = client.get("/keys", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") def test_keys_page_renders_for_authenticated_user(self, authorized_client): """Test GET /keys renders for authenticated user.""" response = authorized_client.get("/keys") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "key" in response.text.lower() or "API" in response.text class TestStats: """Test stats page routes (T52).""" def test_stats_page_requires_auth(self, client): """Test GET /stats redirects to login when not authenticated.""" response = client.get("/stats", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") def test_stats_page_renders_for_authenticated_user(self, authorized_client): """Test GET /stats renders for authenticated user.""" response = authorized_client.get("/stats") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "stat" in response.text.lower() or "Stats" in response.text class TestTokens: """Test API tokens management routes (T53).""" def test_tokens_page_requires_auth(self, client): """Test GET /tokens redirects to login when not authenticated.""" response = client.get("/tokens", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") def test_tokens_page_renders_for_authenticated_user(self, authorized_client): """Test GET /tokens renders for authenticated user.""" response = authorized_client.get("/tokens") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "token" in response.text.lower() or "Token" in response.text class TestProfile: """Test profile page routes (T54).""" def test_profile_page_requires_auth(self, client): """Test GET /profile redirects to login when not authenticated.""" response = client.get("/profile", follow_redirects=False) assert response.status_code == 302 assert "/login" in response.headers.get("location", "") def test_profile_page_renders_for_authenticated_user(self, authorized_client): """Test GET /profile renders for authenticated user.""" response = authorized_client.get("/profile") assert response.status_code == 200 assert "text/html" in response.headers.get("content-type", "") assert "profile" in response.text.lower() or "Profile" in response.text