-
-
+
+
{% endblock %}
diff --git a/frontend/tests/test_frontend.py b/frontend/tests/test_frontend.py
index 2b5e76d..b090745 100644
--- a/frontend/tests/test_frontend.py
+++ b/frontend/tests/test_frontend.py
@@ -17,13 +17,21 @@ def client():
yield c
-def _mock_response(status_code: int, json_data: dict) -> MagicMock:
+def _mock_response(status_code: int, json_data) -> MagicMock:
m = MagicMock()
m.status_code = status_code
m.json.return_value = json_data
return m
+def _set_auth(client, role: str = "user"):
+ with client.session_transaction() as sess:
+ sess["access_token"] = "tok"
+ sess["refresh_token"] = "ref"
+ sess["user_role"] = role
+ sess["user_email"] = "u@example.com"
+
+
# ---------------------------------------------------------------------------
# Index redirect
# ---------------------------------------------------------------------------
@@ -35,9 +43,7 @@ def test_index_redirects_to_login(client):
def test_index_redirects_to_dashboard_when_logged_in(client):
- with client.session_transaction() as sess:
- sess["access_token"] = "tok"
- sess["refresh_token"] = "ref"
+ _set_auth(client)
resp = client.get("/")
assert resp.status_code == 302
assert "/dashboard" in resp.headers["Location"]
@@ -54,17 +60,34 @@ def test_login_page_renders(client):
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"})
+ login_mock = _mock_response(
+ 200, {"access_token": "acc", "refresh_token": "ref"})
+ me_mock = _mock_response(
+ 200, {"id": "1", "email": "u@example.com", "role": "user"})
+ with patch("frontend.app.main.httpx.request", side_effect=[login_mock, me_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_stores_role_in_session(client):
+ login_mock = _mock_response(
+ 200, {"access_token": "acc", "refresh_token": "ref"})
+ me_mock = _mock_response(
+ 200, {"id": "1", "email": "admin@example.com", "role": "admin"})
+ with patch("frontend.app.main.httpx.request", side_effect=[login_mock, me_mock]):
+ client.post(
+ "/login", data={"email": "admin@example.com", "password": "secret"})
+ with client.session_transaction() as sess:
+ assert sess["user_role"] == "admin"
+
+
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"})
+ 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
@@ -80,9 +103,11 @@ def test_register_page_renders(client):
def test_register_success_redirects_to_login(client):
- mock = _mock_response(201, {"id": "abc", "email": "u@example.com", "role": "user"})
+ 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"})
+ resp = client.post(
+ "/register", data={"email": "u@example.com", "password": "secret123"})
assert resp.status_code == 302
assert "/login" in resp.headers["Location"]
@@ -90,7 +115,8 @@ def test_register_success_redirects_to_login(client):
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"})
+ resp = client.post(
+ "/register", data={"email": "dup@example.com", "password": "secret123"})
assert resp.status_code == 200
assert b"Email already registered" in resp.data
@@ -100,9 +126,7 @@ def test_register_duplicate_shows_error(client):
# ---------------------------------------------------------------------------
def test_logout_clears_session_and_redirects(client):
- with client.session_transaction() as sess:
- sess["access_token"] = "tok"
- sess["refresh_token"] = "ref"
+ _set_auth(client)
mock = _mock_response(204, {})
with patch("frontend.app.main.httpx.request", return_value=mock):
resp = client.get("/logout")
@@ -123,9 +147,9 @@ def test_dashboard_requires_login(client):
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"})
+ _set_auth(client)
+ 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
@@ -133,57 +157,193 @@ def test_dashboard_renders_user_info(client):
# ---------------------------------------------------------------------------
-# Generate
+# Generate — redirect + separate pages
# ---------------------------------------------------------------------------
-def test_generate_page_requires_login(client):
+def test_generate_redirects_to_text(client):
+ _set_auth(client)
resp = client.get("/generate")
assert resp.status_code == 302
+ assert "/generate/text" in resp.headers["Location"]
+
+
+def test_generate_text_page_renders(client):
+ _set_auth(client)
+ resp = client.get("/generate/text")
+ assert resp.status_code == 200
+ assert b"Text Generation" in resp.data
+
+
+def test_generate_text_requires_login(client):
+ resp = client.get("/generate/text")
+ 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})
+ _set_auth(client)
+ 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"
- })
+ resp = client.post(
+ "/generate/text", data={"model": "openai/gpt-4o", "prompt": "Say hello"})
assert resp.status_code == 200
assert b"Hello world" in resp.data
+def test_generate_image_page_renders(client):
+ _set_auth(client)
+ resp = client.get("/generate/image")
+ assert resp.status_code == 200
+ assert b"Image Generation" in resp.data
+
+
def test_generate_image_success(client):
- with client.session_transaction() as sess:
- sess["access_token"] = "tok"
+ _set_auth(client)
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"
+ resp = client.post("/generate/image", data={
+ "model": "openai/dall-e-3", "prompt": "A cat", "n": "1", "size": "1024x1024"
})
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"})
+def test_generate_video_page_renders(client):
+ _set_auth(client)
+ resp = client.get("/generate/video")
+ assert resp.status_code == 200
+ assert b"Video Generation" in resp.data
+
+
+def test_generate_video_text_mode(client):
+ _set_auth(client)
+ mock = _mock_response(
+ 200, {"id": "v1", "model": "openai/sora-2-pro", "status": "queued"})
with patch("frontend.app.main.httpx.request", return_value=mock):
- resp = client.post("/generate", data={
- "type": "text", "model": "openai/gpt-4o", "prompt": "Hi"
+ resp = client.post("/generate/video", data={
+ "mode": "text", "model": "openai/sora-2-pro",
+ "prompt": "A sunset", "aspect_ratio": "16:9"
})
assert resp.status_code == 200
+ assert b"queued" in resp.data
+
+
+def test_generate_video_image_mode(client):
+ _set_auth(client)
+ mock = _mock_response(
+ 200, {"id": "v2", "model": "openai/sora-2-pro", "status": "processing"})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post("/generate/video", data={
+ "mode": "image", "model": "openai/sora-2-pro",
+ "image_url": "https://example.com/img.png",
+ "prompt": "Pan right", "aspect_ratio": "16:9"
+ })
+ assert resp.status_code == 200
+ assert b"processing" in resp.data
+
+
+def test_generate_upstream_error_shows_message(client):
+ _set_auth(client)
+ mock = _mock_response(502, {"detail": "OpenRouter error: timeout"})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post(
+ "/generate/text", data={"model": "openai/gpt-4o", "prompt": "Hi"})
+ assert resp.status_code == 200
assert b"OpenRouter error" in resp.data
+
+
+# ---------------------------------------------------------------------------
+# Admin
+# ---------------------------------------------------------------------------
+
+def test_admin_requires_login(client):
+ resp = client.get("/admin")
+ assert resp.status_code == 302
+ assert "/login" in resp.headers["Location"]
+
+
+def test_admin_requires_admin_role(client):
+ _set_auth(client, role="user")
+ resp = client.get("/admin")
+ assert resp.status_code == 302
+ assert "/dashboard" in resp.headers["Location"]
+
+
+def test_admin_page_renders(client):
+ _set_auth(client, role="admin")
+ stats_mock = _mock_response(
+ 200, {"total_users": 5, "active_refresh_tokens": 3, "admin_users": 1})
+ users_mock = _mock_response(200, [
+ {"id": "u1", "email": "a@example.com", "role": "admin"},
+ {"id": "u2", "email": "b@example.com", "role": "user"},
+ ])
+ with patch("frontend.app.main.httpx.request", side_effect=[stats_mock, users_mock]):
+ resp = client.get("/admin")
+ assert resp.status_code == 200
+ assert b"a@example.com" in resp.data
+ assert b"b@example.com" in resp.data
+
+
+def test_admin_set_role(client):
+ _set_auth(client, role="admin")
+ mock = _mock_response(
+ 200, {"id": "u2", "email": "b@example.com", "role": "admin"})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post("/admin/users/u2/role", data={"role": "admin"})
+ assert resp.status_code == 302
+ assert "/admin" in resp.headers["Location"]
+
+
+def test_admin_delete_user(client):
+ _set_auth(client, role="admin")
+ mock = _mock_response(204, {})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post("/admin/users/u2/delete")
+ assert resp.status_code == 302
+ assert "/admin" in resp.headers["Location"]
+
+
+# ---------------------------------------------------------------------------
+# Profile
+# ---------------------------------------------------------------------------
+
+def test_profile_requires_login(client):
+ resp = client.get("/users/profile")
+ assert resp.status_code == 302
+ assert "/login" in resp.headers["Location"]
+
+
+def test_profile_page_renders(client):
+ _set_auth(client)
+ 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("/users/profile")
+ assert resp.status_code == 200
+ assert b"Profile" in resp.data
+ assert b"u@example.com" in resp.data
+
+
+def test_profile_update_email(client):
+ _set_auth(client)
+ mock = _mock_response(
+ 200, {"id": "1", "email": "new@example.com", "role": "user"})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post(
+ "/users/profile", data={"email": "new@example.com", "password": ""})
+ assert resp.status_code == 302
+ assert "/users/profile" in resp.headers["Location"]
+
+
+def test_profile_update_failure(client):
+ _set_auth(client)
+ mock = _mock_response(422, {"detail": "Invalid email."})
+ with patch("frontend.app.main.httpx.request", return_value=mock):
+ resp = client.post(
+ "/users/profile", data={"email": "bad", "password": ""})
+ # redirects regardless, flash message shown on next GET
+ assert resp.status_code == 302