jebin2 commited on
Commit
2a0f64d
·
1 Parent(s): 5ecbd0f

gemini test

Browse files
Files changed (1) hide show
  1. tests/test_gemini_router.py +598 -0
tests/test_gemini_router.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rigorous Tests for Gemini Router.
3
+
4
+ Tests cover:
5
+ 1. Request/Response Models
6
+ 2. Helper functions (get_queue_position, create_job)
7
+ 3. Job creation endpoints (5 types)
8
+ 4. Job status polling
9
+ 5. Video download
10
+ 6. Job cancellation
11
+ 7. Models endpoint
12
+
13
+ Uses mocked auth, database, and worker pool.
14
+ """
15
+ import pytest
16
+ import os
17
+ import tempfile
18
+ from datetime import datetime
19
+ from unittest.mock import patch, MagicMock, AsyncMock
20
+ from fastapi.testclient import TestClient
21
+
22
+
23
+ # =============================================================================
24
+ # 1. Request Model Tests
25
+ # =============================================================================
26
+
27
+ class TestRequestModels:
28
+ """Test request model validation."""
29
+
30
+ def test_generate_animation_prompt_request(self):
31
+ """GenerateAnimationPromptRequest validates correctly."""
32
+ from routers.gemini import GenerateAnimationPromptRequest
33
+
34
+ req = GenerateAnimationPromptRequest(
35
+ base64_image="base64data",
36
+ mime_type="image/png",
37
+ custom_prompt="Make it dramatic"
38
+ )
39
+
40
+ assert req.base64_image == "base64data"
41
+ assert req.mime_type == "image/png"
42
+ assert req.custom_prompt == "Make it dramatic"
43
+
44
+ def test_edit_image_request(self):
45
+ """EditImageRequest validates correctly."""
46
+ from routers.gemini import EditImageRequest
47
+
48
+ req = EditImageRequest(
49
+ base64_image="base64data",
50
+ mime_type="image/png",
51
+ prompt="Add colors"
52
+ )
53
+
54
+ assert req.prompt == "Add colors"
55
+
56
+ def test_generate_video_request_defaults(self):
57
+ """GenerateVideoRequest has correct defaults."""
58
+ from routers.gemini import GenerateVideoRequest
59
+
60
+ req = GenerateVideoRequest(
61
+ base64_image="base64data",
62
+ mime_type="image/png",
63
+ prompt="Animate"
64
+ )
65
+
66
+ assert req.aspect_ratio == "16:9"
67
+ assert req.resolution == "720p"
68
+ assert req.number_of_videos == 1
69
+
70
+ def test_generate_video_request_custom_values(self):
71
+ """GenerateVideoRequest accepts custom values."""
72
+ from routers.gemini import GenerateVideoRequest
73
+
74
+ req = GenerateVideoRequest(
75
+ base64_image="base64data",
76
+ mime_type="image/png",
77
+ prompt="Animate",
78
+ aspect_ratio="9:16",
79
+ resolution="1080p",
80
+ number_of_videos=2
81
+ )
82
+
83
+ assert req.aspect_ratio == "9:16"
84
+ assert req.resolution == "1080p"
85
+ assert req.number_of_videos == 2
86
+
87
+ def test_generate_text_request(self):
88
+ """GenerateTextRequest validates correctly."""
89
+ from routers.gemini import GenerateTextRequest
90
+
91
+ req = GenerateTextRequest(prompt="Hello world")
92
+
93
+ assert req.prompt == "Hello world"
94
+ assert req.model is None
95
+
96
+ def test_analyze_image_request(self):
97
+ """AnalyzeImageRequest validates correctly."""
98
+ from routers.gemini import AnalyzeImageRequest
99
+
100
+ req = AnalyzeImageRequest(
101
+ base64_image="base64data",
102
+ mime_type="image/jpeg",
103
+ prompt="Describe this"
104
+ )
105
+
106
+ assert req.prompt == "Describe this"
107
+
108
+
109
+ # =============================================================================
110
+ # 2. Job Creation Endpoints Tests
111
+ # =============================================================================
112
+
113
+ class TestJobCreationEndpoints:
114
+ """Test job creation endpoints."""
115
+
116
+ def test_generate_animation_prompt_requires_auth(self):
117
+ """generate-animation-prompt requires authentication."""
118
+ from routers.gemini import router
119
+ from fastapi import FastAPI
120
+
121
+ app = FastAPI()
122
+ app.include_router(router)
123
+ client = TestClient(app)
124
+
125
+ response = client.post(
126
+ "/gemini/generate-animation-prompt",
127
+ json={"base64_image": "abc", "mime_type": "image/png"}
128
+ )
129
+
130
+ assert response.status_code in [401, 403, 422]
131
+
132
+ def test_edit_image_requires_auth(self):
133
+ """edit-image requires authentication."""
134
+ from routers.gemini import router
135
+ from fastapi import FastAPI
136
+
137
+ app = FastAPI()
138
+ app.include_router(router)
139
+ client = TestClient(app)
140
+
141
+ response = client.post(
142
+ "/gemini/edit-image",
143
+ json={"base64_image": "abc", "mime_type": "image/png", "prompt": "edit"}
144
+ )
145
+
146
+ assert response.status_code in [401, 403, 422]
147
+
148
+ def test_generate_video_requires_auth(self):
149
+ """generate-video requires authentication."""
150
+ from routers.gemini import router
151
+ from fastapi import FastAPI
152
+
153
+ app = FastAPI()
154
+ app.include_router(router)
155
+ client = TestClient(app)
156
+
157
+ response = client.post(
158
+ "/gemini/generate-video",
159
+ json={"base64_image": "abc", "mime_type": "image/png", "prompt": "animate"}
160
+ )
161
+
162
+ assert response.status_code in [401, 403, 422]
163
+
164
+ def test_generate_text_requires_auth(self):
165
+ """generate-text requires authentication."""
166
+ from routers.gemini import router
167
+ from fastapi import FastAPI
168
+
169
+ app = FastAPI()
170
+ app.include_router(router)
171
+ client = TestClient(app)
172
+
173
+ response = client.post(
174
+ "/gemini/generate-text",
175
+ json={"prompt": "Hello"}
176
+ )
177
+
178
+ assert response.status_code in [401, 403, 422]
179
+
180
+ def test_analyze_image_requires_auth(self):
181
+ """analyze-image requires authentication."""
182
+ from routers.gemini import router
183
+ from fastapi import FastAPI
184
+
185
+ app = FastAPI()
186
+ app.include_router(router)
187
+ client = TestClient(app)
188
+
189
+ response = client.post(
190
+ "/gemini/analyze-image",
191
+ json={"base64_image": "abc", "mime_type": "image/png", "prompt": "analyze"}
192
+ )
193
+
194
+ assert response.status_code in [401, 403, 422]
195
+
196
+
197
+ # =============================================================================
198
+ # 3. Job Status Endpoint Tests
199
+ # =============================================================================
200
+
201
+ class TestJobStatusEndpoint:
202
+ """Test GET /job/{job_id} endpoint."""
203
+
204
+ def test_job_status_requires_auth(self):
205
+ """Job status requires authentication."""
206
+ from routers.gemini import router
207
+ from fastapi import FastAPI
208
+
209
+ app = FastAPI()
210
+ app.include_router(router)
211
+ client = TestClient(app)
212
+
213
+ response = client.get("/gemini/job/job_123")
214
+
215
+ assert response.status_code in [401, 403, 422]
216
+
217
+ def test_job_status_not_found(self):
218
+ """Return 404 for non-existent job."""
219
+ from routers.gemini import router
220
+ from fastapi import FastAPI
221
+ from dependencies import get_current_user
222
+ from core.database import get_db
223
+
224
+ app = FastAPI()
225
+
226
+ mock_user = MagicMock()
227
+ mock_user.user_id = "test-user"
228
+ mock_user.credits = 100
229
+
230
+ async def mock_get_db():
231
+ mock_db = AsyncMock()
232
+ mock_result = MagicMock()
233
+ mock_result.scalar_one_or_none.return_value = None
234
+ mock_db.execute.return_value = mock_result
235
+ yield mock_db
236
+
237
+ app.dependency_overrides[get_current_user] = lambda: mock_user
238
+ app.dependency_overrides[get_db] = mock_get_db
239
+ app.include_router(router)
240
+ client = TestClient(app)
241
+
242
+ response = client.get("/gemini/job/job_nonexistent")
243
+
244
+ assert response.status_code == 404
245
+ assert "not found" in response.json()["detail"].lower()
246
+
247
+ def test_job_status_queued(self):
248
+ """Return queued status with position."""
249
+ from routers.gemini import router
250
+ from fastapi import FastAPI
251
+ from dependencies import get_current_user
252
+ from core.database import get_db
253
+
254
+ app = FastAPI()
255
+
256
+ mock_user = MagicMock()
257
+ mock_user.user_id = "test-user"
258
+ mock_user.credits = 100
259
+
260
+ mock_job = MagicMock()
261
+ mock_job.job_id = "job_123"
262
+ mock_job.job_type = "text"
263
+ mock_job.status = "queued"
264
+ mock_job.created_at = datetime.utcnow()
265
+
266
+ async def mock_get_db():
267
+ mock_db = AsyncMock()
268
+ mock_result = MagicMock()
269
+ mock_result.scalar_one_or_none.return_value = mock_job
270
+
271
+ # For queue position query
272
+ mock_position_result = MagicMock()
273
+ mock_position_result.scalar.return_value = 2
274
+
275
+ mock_db.execute.side_effect = [mock_result, mock_position_result]
276
+ yield mock_db
277
+
278
+ app.dependency_overrides[get_current_user] = lambda: mock_user
279
+ app.dependency_overrides[get_db] = mock_get_db
280
+ app.include_router(router)
281
+ client = TestClient(app)
282
+
283
+ response = client.get("/gemini/job/job_123")
284
+
285
+ assert response.status_code == 200
286
+ data = response.json()
287
+ assert data["status"] == "queued"
288
+ assert "position" in data
289
+
290
+ def test_job_status_completed(self):
291
+ """Return completed status with output."""
292
+ from routers.gemini import router
293
+ from fastapi import FastAPI
294
+ from dependencies import get_current_user
295
+ from core.database import get_db
296
+
297
+ app = FastAPI()
298
+
299
+ mock_user = MagicMock()
300
+ mock_user.user_id = "test-user"
301
+ mock_user.credits = 100
302
+
303
+ mock_job = MagicMock()
304
+ mock_job.job_id = "job_123"
305
+ mock_job.job_type = "text"
306
+ mock_job.status = "completed"
307
+ mock_job.created_at = datetime.utcnow()
308
+ mock_job.completed_at = datetime.utcnow()
309
+ mock_job.output_data = {"result": "Generated text"}
310
+
311
+ async def mock_get_db():
312
+ mock_db = AsyncMock()
313
+ mock_result = MagicMock()
314
+ mock_result.scalar_one_or_none.return_value = mock_job
315
+ mock_db.execute.return_value = mock_result
316
+ yield mock_db
317
+
318
+ app.dependency_overrides[get_current_user] = lambda: mock_user
319
+ app.dependency_overrides[get_db] = mock_get_db
320
+ app.include_router(router)
321
+ client = TestClient(app)
322
+
323
+ response = client.get("/gemini/job/job_123")
324
+
325
+ assert response.status_code == 200
326
+ data = response.json()
327
+ assert data["status"] == "completed"
328
+ assert "output" in data
329
+
330
+ def test_job_status_failed(self):
331
+ """Return failed status with error."""
332
+ from routers.gemini import router
333
+ from fastapi import FastAPI
334
+ from dependencies import get_current_user
335
+ from core.database import get_db
336
+
337
+ app = FastAPI()
338
+
339
+ mock_user = MagicMock()
340
+ mock_user.user_id = "test-user"
341
+ mock_user.credits = 100
342
+
343
+ mock_job = MagicMock()
344
+ mock_job.job_id = "job_123"
345
+ mock_job.job_type = "text"
346
+ mock_job.status = "failed"
347
+ mock_job.created_at = datetime.utcnow()
348
+ mock_job.completed_at = datetime.utcnow()
349
+ mock_job.error_message = "API rate limited"
350
+
351
+ async def mock_get_db():
352
+ mock_db = AsyncMock()
353
+ mock_result = MagicMock()
354
+ mock_result.scalar_one_or_none.return_value = mock_job
355
+ mock_db.execute.return_value = mock_result
356
+ yield mock_db
357
+
358
+ app.dependency_overrides[get_current_user] = lambda: mock_user
359
+ app.dependency_overrides[get_db] = mock_get_db
360
+ app.include_router(router)
361
+ client = TestClient(app)
362
+
363
+ response = client.get("/gemini/job/job_123")
364
+
365
+ assert response.status_code == 200
366
+ data = response.json()
367
+ assert data["status"] == "failed"
368
+ assert "error" in data
369
+
370
+
371
+ # =============================================================================
372
+ # 4. Video Download Endpoint Tests
373
+ # =============================================================================
374
+
375
+ class TestDownloadEndpoint:
376
+ """Test GET /download/{job_id} endpoint."""
377
+
378
+ def test_download_requires_auth(self):
379
+ """Download requires authentication."""
380
+ from routers.gemini import router
381
+ from fastapi import FastAPI
382
+
383
+ app = FastAPI()
384
+ app.include_router(router)
385
+ client = TestClient(app)
386
+
387
+ response = client.get("/gemini/download/job_123")
388
+
389
+ assert response.status_code in [401, 403, 422]
390
+
391
+ def test_download_job_not_found(self):
392
+ """Return 404 for non-existent job."""
393
+ from routers.gemini import router
394
+ from fastapi import FastAPI
395
+ from dependencies import get_current_user
396
+ from core.database import get_db
397
+
398
+ app = FastAPI()
399
+
400
+ mock_user = MagicMock()
401
+ mock_user.user_id = "test-user"
402
+
403
+ async def mock_get_db():
404
+ mock_db = AsyncMock()
405
+ mock_result = MagicMock()
406
+ mock_result.scalar_one_or_none.return_value = None
407
+ mock_db.execute.return_value = mock_result
408
+ yield mock_db
409
+
410
+ app.dependency_overrides[get_current_user] = lambda: mock_user
411
+ app.dependency_overrides[get_db] = mock_get_db
412
+ app.include_router(router)
413
+ client = TestClient(app)
414
+
415
+ response = client.get("/gemini/download/job_nonexistent")
416
+
417
+ assert response.status_code == 404
418
+
419
+ def test_download_video_not_ready(self):
420
+ """Return 400 if video not ready."""
421
+ from routers.gemini import router
422
+ from fastapi import FastAPI
423
+ from dependencies import get_current_user
424
+ from core.database import get_db
425
+
426
+ app = FastAPI()
427
+
428
+ mock_user = MagicMock()
429
+ mock_user.user_id = "test-user"
430
+
431
+ mock_job = MagicMock()
432
+ mock_job.job_id = "job_123"
433
+ mock_job.job_type = "video"
434
+ mock_job.status = "processing" # Not completed
435
+ mock_job.output_data = None
436
+
437
+ async def mock_get_db():
438
+ mock_db = AsyncMock()
439
+ mock_result = MagicMock()
440
+ mock_result.scalar_one_or_none.return_value = mock_job
441
+ mock_db.execute.return_value = mock_result
442
+ yield mock_db
443
+
444
+ app.dependency_overrides[get_current_user] = lambda: mock_user
445
+ app.dependency_overrides[get_db] = mock_get_db
446
+ app.include_router(router)
447
+ client = TestClient(app)
448
+
449
+ response = client.get("/gemini/download/job_123")
450
+
451
+ assert response.status_code == 400
452
+ assert "not ready" in response.json()["detail"].lower()
453
+
454
+
455
+ # =============================================================================
456
+ # 5. Job Cancellation Endpoint Tests
457
+ # =============================================================================
458
+
459
+ class TestCancelEndpoint:
460
+ """Test POST /job/{job_id}/cancel endpoint."""
461
+
462
+ def test_cancel_requires_auth(self):
463
+ """Cancel requires authentication."""
464
+ from routers.gemini import router
465
+ from fastapi import FastAPI
466
+
467
+ app = FastAPI()
468
+ app.include_router(router)
469
+ client = TestClient(app)
470
+
471
+ response = client.post("/gemini/job/job_123/cancel")
472
+
473
+ assert response.status_code in [401, 403, 422]
474
+
475
+ def test_cancel_job_not_found(self):
476
+ """Return 404 for non-existent job."""
477
+ from routers.gemini import router
478
+ from fastapi import FastAPI
479
+ from dependencies import get_current_user
480
+ from core.database import get_db
481
+
482
+ app = FastAPI()
483
+
484
+ mock_user = MagicMock()
485
+ mock_user.user_id = "test-user"
486
+
487
+ async def mock_get_db():
488
+ mock_db = AsyncMock()
489
+ mock_result = MagicMock()
490
+ mock_result.scalar_one_or_none.return_value = None
491
+ mock_db.execute.return_value = mock_result
492
+ yield mock_db
493
+
494
+ app.dependency_overrides[get_current_user] = lambda: mock_user
495
+ app.dependency_overrides[get_db] = mock_get_db
496
+ app.include_router(router)
497
+ client = TestClient(app)
498
+
499
+ response = client.post("/gemini/job/job_nonexistent/cancel")
500
+
501
+ assert response.status_code == 404
502
+
503
+ def test_cancel_only_queued_jobs(self):
504
+ """Only queued jobs can be cancelled."""
505
+ from routers.gemini import router
506
+ from fastapi import FastAPI
507
+ from dependencies import get_current_user
508
+ from core.database import get_db
509
+
510
+ app = FastAPI()
511
+
512
+ mock_user = MagicMock()
513
+ mock_user.user_id = "test-user"
514
+
515
+ mock_job = MagicMock()
516
+ mock_job.job_id = "job_123"
517
+ mock_job.status = "processing" # Cannot cancel
518
+
519
+ async def mock_get_db():
520
+ mock_db = AsyncMock()
521
+ mock_result = MagicMock()
522
+ mock_result.scalar_one_or_none.return_value = mock_job
523
+ mock_db.execute.return_value = mock_result
524
+ yield mock_db
525
+
526
+ app.dependency_overrides[get_current_user] = lambda: mock_user
527
+ app.dependency_overrides[get_db] = mock_get_db
528
+ app.include_router(router)
529
+ client = TestClient(app)
530
+
531
+ response = client.post("/gemini/job/job_123/cancel")
532
+
533
+ assert response.status_code == 400
534
+ assert "cannot cancel" in response.json()["detail"].lower()
535
+
536
+ def test_cancel_queued_job_success(self):
537
+ """Successfully cancel a queued job."""
538
+ from routers.gemini import router
539
+ from fastapi import FastAPI
540
+ from dependencies import get_current_user
541
+ from core.database import get_db
542
+
543
+ app = FastAPI()
544
+
545
+ mock_user = MagicMock()
546
+ mock_user.user_id = "test-user"
547
+
548
+ mock_job = MagicMock()
549
+ mock_job.job_id = "job_123"
550
+ mock_job.status = "queued"
551
+
552
+ async def mock_get_db():
553
+ mock_db = AsyncMock()
554
+ mock_result = MagicMock()
555
+ mock_result.scalar_one_or_none.return_value = mock_job
556
+ mock_db.execute.return_value = mock_result
557
+ yield mock_db
558
+
559
+ app.dependency_overrides[get_current_user] = lambda: mock_user
560
+ app.dependency_overrides[get_db] = mock_get_db
561
+ app.include_router(router)
562
+ client = TestClient(app)
563
+
564
+ response = client.post("/gemini/job/job_123/cancel")
565
+
566
+ assert response.status_code == 200
567
+ data = response.json()
568
+ assert data["success"] == True
569
+ assert data["status"] == "cancelled"
570
+
571
+
572
+ # =============================================================================
573
+ # 6. Models Endpoint Tests
574
+ # =============================================================================
575
+
576
+ class TestModelsEndpoint:
577
+ """Test GET /models endpoint."""
578
+
579
+ def test_get_models(self):
580
+ """Get available models."""
581
+ from routers.gemini import router
582
+ from fastapi import FastAPI
583
+
584
+ app = FastAPI()
585
+ app.include_router(router)
586
+ client = TestClient(app)
587
+
588
+ response = client.get("/gemini/models")
589
+
590
+ assert response.status_code == 200
591
+ data = response.json()
592
+ assert "models" in data
593
+ assert "text_generation" in data["models"]
594
+ assert "video_generation" in data["models"]
595
+
596
+
597
+ if __name__ == "__main__":
598
+ pytest.main([__file__, "-v"])