apigateway / tests /test_gemini_router.py
jebin2's picture
ref
a42ab7e
"""
Rigorous Tests for Gemini Router.
Tests cover:
1. Request/Response Models
2. Helper functions (get_queue_position, create_job)
3. Job creation endpoints (5 types)
4. Job status polling
5. Video download
6. Job cancellation
7. Models endpoint
Uses mocked auth, database, and worker pool.
"""
import pytest
import os
import tempfile
from datetime import datetime
from unittest.mock import patch, MagicMock, AsyncMock
from fastapi.testclient import TestClient
# =============================================================================
# 1. Request Model Tests
# =============================================================================
class TestRequestModels:
"""Test request model validation."""
def test_generate_animation_prompt_request(self):
"""GenerateAnimationPromptRequest validates correctly."""
from routers.gemini import GenerateAnimationPromptRequest
req = GenerateAnimationPromptRequest(
base64_image="base64data",
mime_type="image/png",
custom_prompt="Make it dramatic"
)
assert req.base64_image == "base64data"
assert req.mime_type == "image/png"
assert req.custom_prompt == "Make it dramatic"
def test_edit_image_request(self):
"""EditImageRequest validates correctly."""
from routers.gemini import EditImageRequest
req = EditImageRequest(
base64_image="base64data",
mime_type="image/png",
prompt="Add colors"
)
assert req.prompt == "Add colors"
def test_generate_video_request_defaults(self):
"""GenerateVideoRequest has correct defaults."""
from routers.gemini import GenerateVideoRequest
req = GenerateVideoRequest(
base64_image="base64data",
mime_type="image/png",
prompt="Animate"
)
assert req.aspect_ratio == "16:9"
assert req.resolution == "720p"
assert req.number_of_videos == 1
def test_generate_video_request_custom_values(self):
"""GenerateVideoRequest accepts custom values."""
from routers.gemini import GenerateVideoRequest
req = GenerateVideoRequest(
base64_image="base64data",
mime_type="image/png",
prompt="Animate",
aspect_ratio="9:16",
resolution="1080p",
number_of_videos=2
)
assert req.aspect_ratio == "9:16"
assert req.resolution == "1080p"
assert req.number_of_videos == 2
def test_generate_text_request(self):
"""GenerateTextRequest validates correctly."""
from routers.gemini import GenerateTextRequest
req = GenerateTextRequest(prompt="Hello world")
assert req.prompt == "Hello world"
assert req.model is None
def test_analyze_image_request(self):
"""AnalyzeImageRequest validates correctly."""
from routers.gemini import AnalyzeImageRequest
req = AnalyzeImageRequest(
base64_image="base64data",
mime_type="image/jpeg",
prompt="Describe this"
)
assert req.prompt == "Describe this"
# =============================================================================
# 2. Job Creation Endpoints Tests
# =============================================================================
class TestJobCreationEndpoints:
"""Test job creation endpoints."""
def test_generate_animation_prompt_requires_auth(self):
"""generate-animation-prompt requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post(
"/gemini/generate-animation-prompt",
json={"base64_image": "abc", "mime_type": "image/png"}
)
assert response.status_code in [401, 403, 422]
def test_edit_image_requires_auth(self):
"""edit-image requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post(
"/gemini/edit-image",
json={"base64_image": "abc", "mime_type": "image/png", "prompt": "edit"}
)
assert response.status_code in [401, 403, 422]
def test_generate_video_requires_auth(self):
"""generate-video requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post(
"/gemini/generate-video",
json={"base64_image": "abc", "mime_type": "image/png", "prompt": "animate"}
)
assert response.status_code in [401, 403, 422]
def test_generate_text_requires_auth(self):
"""generate-text requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post(
"/gemini/generate-text",
json={"prompt": "Hello"}
)
assert response.status_code in [401, 403, 422]
def test_analyze_image_requires_auth(self):
"""analyze-image requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post(
"/gemini/analyze-image",
json={"base64_image": "abc", "mime_type": "image/png", "prompt": "analyze"}
)
assert response.status_code in [401, 403, 422]
# =============================================================================
# 3. Job Status Endpoint Tests
# =============================================================================
class TestJobStatusEndpoint:
"""Test GET /job/{job_id} endpoint."""
def test_job_status_requires_auth(self):
"""Job status requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/job/job_123")
assert response.status_code in [401, 403, 422]
def test_job_status_not_found(self):
"""Return 404 for non-existent job."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_user.credits = 100
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = None
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/job/job_nonexistent")
assert response.status_code == 404
assert "not found" in response.json()["detail"].lower()
def test_job_status_queued(self):
"""Return queued status with position."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_user.credits = 100
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.job_type = "text"
mock_job.status = "queued"
mock_job.created_at = datetime.utcnow()
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
# For queue position query
mock_position_result = MagicMock()
mock_position_result.scalar.return_value = 2
mock_db.execute.side_effect = [mock_result, mock_position_result]
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/job/job_123")
assert response.status_code == 200
data = response.json()
assert data["status"] == "queued"
assert "position" in data
def test_job_status_completed(self):
"""Return completed status with output."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_user.credits = 100
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.job_type = "text"
mock_job.status = "completed"
mock_job.created_at = datetime.utcnow()
mock_job.completed_at = datetime.utcnow()
mock_job.output_data = {"result": "Generated text"}
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/job/job_123")
assert response.status_code == 200
data = response.json()
assert data["status"] == "completed"
assert "output" in data
def test_job_status_failed(self):
"""Return failed status with error."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_user.credits = 100
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.job_type = "text"
mock_job.status = "failed"
mock_job.created_at = datetime.utcnow()
mock_job.completed_at = datetime.utcnow()
mock_job.error_message = "API rate limited"
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/job/job_123")
assert response.status_code == 200
data = response.json()
assert data["status"] == "failed"
assert "error" in data
# =============================================================================
# 4. Video Download Endpoint Tests
# =============================================================================
class TestDownloadEndpoint:
"""Test GET /download/{job_id} endpoint."""
def test_download_requires_auth(self):
"""Download requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/download/job_123")
assert response.status_code in [401, 403, 422]
def test_download_job_not_found(self):
"""Return 404 for non-existent job."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = None
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/download/job_nonexistent")
assert response.status_code == 404
def test_download_video_not_ready(self):
"""Return 400 if video not ready."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.job_type = "video"
mock_job.status = "processing" # Not completed
mock_job.output_data = None
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/download/job_123")
assert response.status_code == 400
assert "not ready" in response.json()["detail"].lower()
# =============================================================================
# 5. Job Cancellation Endpoint Tests
# =============================================================================
class TestCancelEndpoint:
"""Test POST /job/{job_id}/cancel endpoint."""
def test_cancel_requires_auth(self):
"""Cancel requires authentication."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.post("/gemini/job/job_123/cancel")
assert response.status_code in [401, 403, 422]
def test_cancel_job_not_found(self):
"""Return 404 for non-existent job."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = None
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.post("/gemini/job/job_nonexistent/cancel")
assert response.status_code == 404
def test_cancel_only_queued_jobs(self):
"""Only queued jobs can be cancelled."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.status = "processing" # Cannot cancel
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.post("/gemini/job/job_123/cancel")
assert response.status_code == 400
assert "cannot cancel" in response.json()["detail"].lower()
def test_cancel_queued_job_success(self):
"""Successfully cancel a queued job."""
from routers.gemini import router
from fastapi import FastAPI
from core.dependencies import get_current_user
from core.database import get_db
app = FastAPI()
mock_user = MagicMock()
mock_user.user_id = "test-user"
mock_job = MagicMock()
mock_job.job_id = "job_123"
mock_job.status = "queued"
async def mock_get_db():
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = mock_job
mock_db.execute.return_value = mock_result
yield mock_db
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = mock_get_db
app.include_router(router)
client = TestClient(app)
response = client.post("/gemini/job/job_123/cancel")
assert response.status_code == 200
data = response.json()
assert data["success"] == True
assert data["status"] == "cancelled"
# =============================================================================
# 6. Models Endpoint Tests
# =============================================================================
class TestModelsEndpoint:
"""Test GET /models endpoint."""
def test_get_models(self):
"""Get available models."""
from routers.gemini import router
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.get("/gemini/models")
assert response.status_code == 200
data = response.json()
assert "models" in data
assert "text_generation" in data["models"]
assert "video_generation" in data["models"]
if __name__ == "__main__":
pytest.main([__file__, "-v"])