712c556032
- Updated `refresh_models_cache` to include output modalities in the models cache. - Added `get_model_output_modalities` function to retrieve output modalities for a specific model. - Modified tests to cover new functionality for output modalities. - Updated OpenRouter video generation functions to support audio generation and improved error handling. - Enhanced dashboard to display generated images and videos. - Refactored frontend templates to accommodate new data structures for generated content. - Adjusted tests to validate changes in model handling and dashboard rendering. Co-authored-by: Copilot <copilot@github.com>
619 lines
23 KiB
Python
619 lines
23 KiB
Python
"""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 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) -> 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
|
|
# ---------------------------------------------------------------------------
|
|
|
|
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):
|
|
_set_auth(client)
|
|
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):
|
|
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"})
|
|
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):
|
|
_set_auth(client)
|
|
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):
|
|
_set_auth(client)
|
|
me_mock = _mock_response(
|
|
200, {"id": "1", "email": "u@example.com", "role": "user"})
|
|
images_mock = _mock_response(200, [])
|
|
gen_images_mock = _mock_response(200, [])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[me_mock, images_mock, gen_images_mock]):
|
|
resp = client.get("/dashboard")
|
|
assert resp.status_code == 200
|
|
assert b"u@example.com" in resp.data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generate — redirect + separate pages
|
|
# ---------------------------------------------------------------------------
|
|
|
|
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 Chat" 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_text_success(client):
|
|
_set_auth(client)
|
|
gen_mock = _mock_response(
|
|
200, {"id": "g1", "model": "openai/gpt-4o", "content": "Hello world", "usage": None})
|
|
models_mock = _mock_response(200, [
|
|
{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}
|
|
])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[gen_mock, models_mock]):
|
|
resp = client.post(
|
|
"/generate/text",
|
|
data={"model": "openai/gpt-4o", "prompt": "Say hello", "action": "send"})
|
|
assert resp.status_code == 200
|
|
assert b"Hello world" in resp.data
|
|
assert b"chat-bubble--assistant" in resp.data
|
|
|
|
|
|
def test_generate_text_page_shows_optional_system_prompt(client):
|
|
_set_auth(client)
|
|
models_mock = _mock_response(200, [])
|
|
with patch("frontend.app.main.httpx.request", return_value=models_mock):
|
|
resp = client.get("/generate/text")
|
|
assert resp.status_code == 200
|
|
assert b"System prompt (optional)" in resp.data
|
|
assert b'name="system_prompt"' in resp.data
|
|
|
|
|
|
def test_generate_text_forwards_system_prompt(client):
|
|
_set_auth(client)
|
|
gen_mock = _mock_response(
|
|
200, {"id": "g1", "model": "openai/gpt-4o", "content": "Hello world", "usage": None})
|
|
models_mock = _mock_response(200, [
|
|
{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}
|
|
])
|
|
|
|
with patch("frontend.app.main.httpx.request", side_effect=[gen_mock, models_mock]) as mock_request:
|
|
resp = client.post(
|
|
"/generate/text",
|
|
data={
|
|
"model": "openai/gpt-4o",
|
|
"prompt": "Say hello",
|
|
"system_prompt": "You are concise.",
|
|
"action": "send",
|
|
},
|
|
)
|
|
|
|
assert resp.status_code == 200
|
|
first_call_kwargs = mock_request.call_args_list[0].kwargs
|
|
assert first_call_kwargs["json"]["system_prompt"] == "You are concise."
|
|
# Messages array sent (not bare prompt)
|
|
assert "messages" in first_call_kwargs["json"]
|
|
|
|
|
|
def test_generate_text_chat_history_accumulates(client):
|
|
"""Second message includes prior user+assistant turns in messages array."""
|
|
_set_auth(client)
|
|
|
|
turn1_gen = _mock_response(
|
|
200, {"id": "g1", "model": "openai/gpt-4o", "content": "Turn 1 reply", "usage": None})
|
|
turn1_models = _mock_response(
|
|
200, [{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}])
|
|
turn2_gen = _mock_response(
|
|
200, {"id": "g2", "model": "openai/gpt-4o", "content": "Turn 2 reply", "usage": None})
|
|
turn2_models = _mock_response(
|
|
200, [{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}])
|
|
|
|
with patch("frontend.app.main.httpx.request", side_effect=[turn1_gen, turn1_models]):
|
|
client.post(
|
|
"/generate/text", data={"model": "openai/gpt-4o", "prompt": "First", "action": "send"})
|
|
|
|
with patch("frontend.app.main.httpx.request", side_effect=[turn2_gen, turn2_models]) as mock_req:
|
|
resp = client.post(
|
|
"/generate/text", data={"model": "openai/gpt-4o", "prompt": "Second", "action": "send"})
|
|
|
|
assert resp.status_code == 200
|
|
assert b"Turn 1 reply" in resp.data
|
|
assert b"Turn 2 reply" in resp.data
|
|
# Backend received 3 messages: First(user), Turn1(assistant), Second(user)
|
|
sent_messages = mock_req.call_args_list[0].kwargs["json"]["messages"]
|
|
assert len(sent_messages) == 3
|
|
assert sent_messages[0]["role"] == "user" and sent_messages[0]["content"] == "First"
|
|
assert sent_messages[1]["role"] == "assistant"
|
|
assert sent_messages[2]["role"] == "user" and sent_messages[2]["content"] == "Second"
|
|
|
|
|
|
def test_generate_text_clear_resets_history(client):
|
|
"""Clear action removes session history and redirects."""
|
|
_set_auth(client)
|
|
|
|
gen_mock = _mock_response(
|
|
200, {"id": "g1", "model": "openai/gpt-4o", "content": "Reply", "usage": None})
|
|
models_mock = _mock_response(
|
|
200, [{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[gen_mock, models_mock]):
|
|
client.post(
|
|
"/generate/text", data={"model": "openai/gpt-4o", "prompt": "Hi", "action": "send"})
|
|
|
|
clear_resp = client.post("/generate/text", data={"action": "clear"})
|
|
assert clear_resp.status_code == 302
|
|
|
|
models_mock2 = _mock_response(
|
|
200, [{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}])
|
|
with patch("frontend.app.main.httpx.request", return_value=models_mock2):
|
|
get_resp = client.get("/generate/text")
|
|
assert b"No messages yet" in get_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
|
|
assert b"reference_image" in resp.data
|
|
|
|
|
|
def test_generate_image_success(client):
|
|
_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/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_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/video", data={
|
|
"mode": "text", "model": "openai/sora-2-pro",
|
|
"prompt": "A sunset", "aspect_ratio": "16:9",
|
|
"duration_seconds": "10", "resolution": "720p",
|
|
})
|
|
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)
|
|
gen_mock = _mock_response(502, {"detail": "OpenRouter error: timeout"})
|
|
models_mock = _mock_response(200, [
|
|
{"id": "openai/gpt-4o", "name": "GPT-4o", "modality": "text"}
|
|
])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[gen_mock, models_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
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /generate/video/status (polling proxy)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_video_status_proxy_completed(client):
|
|
_set_auth(client)
|
|
mock = _mock_response(200, {
|
|
"id": "v1", "model": "", "status": "completed",
|
|
"video_url": "https://example.com/video.mp4",
|
|
"unsigned_urls": ["https://example.com/video.mp4"],
|
|
})
|
|
with patch("frontend.app.main.httpx.request", return_value=mock):
|
|
resp = client.get(
|
|
"/generate/video/status",
|
|
query_string={
|
|
"polling_url": "https://openrouter.ai/api/v1/videos/v1"},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert data["status"] == "completed"
|
|
assert data["video_url"] == "https://example.com/video.mp4"
|
|
|
|
|
|
def test_video_status_proxy_processing(client):
|
|
_set_auth(client)
|
|
mock = _mock_response(
|
|
200, {"id": "v1", "model": "", "status": "processing"})
|
|
with patch("frontend.app.main.httpx.request", return_value=mock):
|
|
resp = client.get(
|
|
"/generate/video/status",
|
|
query_string={
|
|
"polling_url": "https://openrouter.ai/api/v1/videos/v1"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.get_json()["status"] == "processing"
|
|
|
|
|
|
def test_video_status_proxy_requires_login(client):
|
|
resp = client.get(
|
|
"/generate/video/status",
|
|
query_string={"polling_url": "https://openrouter.ai/api/v1/videos/v1"},
|
|
)
|
|
assert resp.status_code == 302
|
|
assert "/login" in resp.headers["Location"]
|
|
|
|
|
|
def test_video_status_proxy_missing_url(client):
|
|
_set_auth(client)
|
|
resp = client.get("/generate/video/status")
|
|
assert resp.status_code == 400
|
|
assert b"polling_url" in resp.data
|
|
|
|
|
|
def test_video_generate_renders_polling_ui(client):
|
|
"""When response has polling_url, template shows polling div."""
|
|
_set_auth(client)
|
|
mock = _mock_response(200, {
|
|
"id": "v1", "model": "openai/sora-2-pro", "status": "queued",
|
|
"polling_url": "https://openrouter.ai/api/v1/videos/v1",
|
|
})
|
|
with patch("frontend.app.main.httpx.request", return_value=mock):
|
|
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"video-poll-status" in resp.data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Image upload — frontend proxy + dashboard
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_dashboard_shows_uploaded_images(client):
|
|
_set_auth(client)
|
|
me_mock = _mock_response(
|
|
200, {"id": "1", "email": "u@example.com", "role": "user"})
|
|
images_mock = _mock_response(200, [
|
|
{"id": "img-1", "filename": "cat.png", "content_type": "image/png",
|
|
"size_bytes": 1024, "created_at": "2026-04-29T10:00:00"},
|
|
])
|
|
gen_images_mock = _mock_response(200, [])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[me_mock, images_mock, gen_images_mock]):
|
|
resp = client.get("/dashboard")
|
|
assert resp.status_code == 200
|
|
assert b"cat.png" in resp.data
|
|
assert b"img-1" in resp.data
|
|
|
|
|
|
def test_dashboard_shows_generated_images(client):
|
|
_set_auth(client)
|
|
me_mock = _mock_response(
|
|
200, {"id": "1", "email": "u@example.com", "role": "user"})
|
|
images_mock = _mock_response(200, [])
|
|
gen_images_mock = _mock_response(200, [
|
|
{
|
|
"id": "gen-1",
|
|
"model_id": "google/gemini-2.5-flash-image",
|
|
"prompt": "A cat on the moon",
|
|
"image_data": "data:image/png;base64,abc123",
|
|
"created_at": "2026-04-29T10:00:00",
|
|
}
|
|
])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[me_mock, images_mock, gen_images_mock]):
|
|
resp = client.get("/dashboard")
|
|
assert resp.status_code == 200
|
|
assert b"Generated images" in resp.data
|
|
assert b"A cat on the moon" in resp.data
|
|
assert b"data:image/png;base64,abc123" in resp.data
|
|
|
|
|
|
def test_dashboard_no_images_section_when_empty(client):
|
|
_set_auth(client)
|
|
me_mock = _mock_response(
|
|
200, {"id": "1", "email": "u@example.com", "role": "user"})
|
|
images_mock = _mock_response(200, [])
|
|
gen_images_mock = _mock_response(200, [])
|
|
with patch("frontend.app.main.httpx.request", side_effect=[me_mock, images_mock, gen_images_mock]):
|
|
resp = client.get("/dashboard")
|
|
assert resp.status_code == 200
|
|
assert b"Uploaded reference images" not in resp.data
|
|
|
|
|
|
def test_serve_uploaded_image_proxy(client):
|
|
_set_auth(client)
|
|
img_bytes = b"\x89PNG\r\n\x1a\n"
|
|
mock = MagicMock()
|
|
mock.status_code = 200
|
|
mock.content = img_bytes
|
|
mock.headers = {"content-type": "image/png"}
|
|
with patch("frontend.app.main.httpx.request", return_value=mock):
|
|
resp = client.get("/images/img-1/file")
|
|
assert resp.status_code == 200
|
|
assert resp.content_type == "image/png"
|
|
assert resp.data == img_bytes
|
|
|
|
|
|
def test_serve_uploaded_image_requires_login(client):
|
|
resp = client.get("/images/img-1/file")
|
|
assert resp.status_code == 302
|
|
assert "/login" in resp.headers["Location"]
|
|
|
|
|
|
def test_serve_uploaded_image_not_found_proxied(client):
|
|
_set_auth(client)
|
|
mock = _mock_response(404, {"detail": "Image not found."})
|
|
mock.content = b""
|
|
with patch("frontend.app.main.httpx.request", return_value=mock):
|
|
resp = client.get("/images/bad-id/file")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_generate_image_uploads_reference_then_generates(client):
|
|
_set_auth(client)
|
|
gen_mock = _mock_response(200, {
|
|
"id": "g2", "model": "openai/dall-e-3",
|
|
"images": [{"url": "https://example.com/out.png", "revised_prompt": None, "b64_json": None}]
|
|
})
|
|
# No file field → upload branch skipped; only generate call is made
|
|
with patch("frontend.app.main.httpx.request", return_value=gen_mock):
|
|
resp = client.post("/generate/image", data={
|
|
"model": "openai/dall-e-3", "prompt": "A cat", "n": "1", "size": "1024x1024",
|
|
}, content_type="multipart/form-data")
|
|
assert resp.status_code == 200
|
|
assert b"example.com/out.png" in resp.data
|