"""Frontend integration tests — backend API calls are fully mocked.""" import os import pytest from unittest.mock import MagicMock, patch os.environ.setdefault("FLASK_SECRET_KEY", "test-secret") os.environ.setdefault("BACKEND_URL", "http://backend-mock") from frontend.app.main import app # noqa: E402 @pytest.fixture def client(): app.config["TESTING"] = True app.config["WTF_CSRF_ENABLED"] = False with app.test_client() as c: yield c def _mock_response(status_code: int, json_data: dict) -> MagicMock: m = MagicMock() m.status_code = status_code m.json.return_value = json_data return m # --------------------------------------------------------------------------- # Index redirect # --------------------------------------------------------------------------- def test_index_redirects_to_login(client): resp = client.get("/") assert resp.status_code == 302 assert "/login" in resp.headers["Location"] def test_index_redirects_to_dashboard_when_logged_in(client): with client.session_transaction() as sess: sess["access_token"] = "tok" sess["refresh_token"] = "ref" resp = client.get("/") assert resp.status_code == 302 assert "/dashboard" in resp.headers["Location"] # --------------------------------------------------------------------------- # Login # --------------------------------------------------------------------------- def test_login_page_renders(client): resp = client.get("/login") assert resp.status_code == 200 assert b"Log in" in resp.data def test_login_success(client): mock = _mock_response(200, {"access_token": "acc", "refresh_token": "ref"}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/login", data={"email": "u@example.com", "password": "secret"}) assert resp.status_code == 302 assert "/dashboard" in resp.headers["Location"] def test_login_failure_shows_error(client): mock = _mock_response(401, {"detail": "Invalid credentials."}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/login", data={"email": "u@example.com", "password": "wrong"}) assert resp.status_code == 200 assert b"Invalid email or password" in resp.data # --------------------------------------------------------------------------- # Register # --------------------------------------------------------------------------- def test_register_page_renders(client): resp = client.get("/register") assert resp.status_code == 200 assert b"Create account" in resp.data def test_register_success_redirects_to_login(client): mock = _mock_response(201, {"id": "abc", "email": "u@example.com", "role": "user"}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/register", data={"email": "u@example.com", "password": "secret123"}) assert resp.status_code == 302 assert "/login" in resp.headers["Location"] def test_register_duplicate_shows_error(client): mock = _mock_response(409, {"detail": "Email already registered."}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/register", data={"email": "dup@example.com", "password": "secret123"}) assert resp.status_code == 200 assert b"Email already registered" in resp.data # --------------------------------------------------------------------------- # Logout # --------------------------------------------------------------------------- def test_logout_clears_session_and_redirects(client): with client.session_transaction() as sess: sess["access_token"] = "tok" sess["refresh_token"] = "ref" mock = _mock_response(204, {}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.get("/logout") assert resp.status_code == 302 assert "/login" in resp.headers["Location"] with client.session_transaction() as sess: assert "access_token" not in sess # --------------------------------------------------------------------------- # Dashboard # --------------------------------------------------------------------------- def test_dashboard_requires_login(client): resp = client.get("/dashboard") assert resp.status_code == 302 assert "/login" in resp.headers["Location"] def test_dashboard_renders_user_info(client): with client.session_transaction() as sess: sess["access_token"] = "tok" mock = _mock_response(200, {"id": "1", "email": "u@example.com", "role": "user"}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.get("/dashboard") assert resp.status_code == 200 assert b"u@example.com" in resp.data # --------------------------------------------------------------------------- # Generate # --------------------------------------------------------------------------- def test_generate_page_requires_login(client): resp = client.get("/generate") assert resp.status_code == 302 assert "/login" in resp.headers["Location"] def test_generate_page_renders(client): with client.session_transaction() as sess: sess["access_token"] = "tok" resp = client.get("/generate") assert resp.status_code == 200 assert b"Generate" in resp.data def test_generate_text_success(client): with client.session_transaction() as sess: sess["access_token"] = "tok" mock = _mock_response(200, {"id": "g1", "model": "openai/gpt-4o", "content": "Hello world", "usage": None}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/generate", data={ "type": "text", "model": "openai/gpt-4o", "prompt": "Say hello" }) assert resp.status_code == 200 assert b"Hello world" in resp.data def test_generate_image_success(client): with client.session_transaction() as sess: sess["access_token"] = "tok" mock = _mock_response(200, { "id": "g2", "model": "openai/dall-e-3", "images": [{"url": "https://example.com/img.png", "revised_prompt": None, "b64_json": None}] }) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/generate", data={ "type": "image", "model": "openai/dall-e-3", "prompt": "A cat" }) assert resp.status_code == 200 assert b"example.com/img.png" in resp.data def test_generate_upstream_error_shows_message(client): with client.session_transaction() as sess: sess["access_token"] = "tok" mock = _mock_response(502, {"detail": "OpenRouter error: timeout"}) with patch("frontend.app.main.httpx.request", return_value=mock): resp = client.post("/generate", data={ "type": "text", "model": "openai/gpt-4o", "prompt": "Hi" }) assert resp.status_code == 200 assert b"OpenRouter error" in resp.data