jebin2 commited on
Commit
afe6505
·
1 Parent(s): 7c8be99

worker test

Browse files
Files changed (1) hide show
  1. tests/test_drive_service.py +571 -0
tests/test_drive_service.py ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rigorous Tests for Drive Service.
3
+
4
+ Tests cover:
5
+ 1. Initialization and credential loading
6
+ 2. OAuth authentication
7
+ 3. Folder operations (find, create)
8
+ 4. Database upload
9
+ 5. Database download
10
+ 6. Error handling
11
+
12
+ Mocks Google API - no real Drive calls.
13
+ """
14
+ import pytest
15
+ import os
16
+ import tempfile
17
+ from unittest.mock import patch, MagicMock, PropertyMock
18
+ from googleapiclient.errors import HttpError
19
+
20
+
21
+ # =============================================================================
22
+ # 1. Initialization Tests
23
+ # =============================================================================
24
+
25
+ class TestDriveServiceInit:
26
+ """Test DriveService initialization."""
27
+
28
+ def test_load_server_credentials(self):
29
+ """Load credentials from SERVER_* env vars."""
30
+ with patch.dict(os.environ, {
31
+ "SERVER_GOOGLE_CLIENT_ID": "server-client-id",
32
+ "SERVER_GOOGLE_CLIENT_SECRET": "server-secret",
33
+ "SERVER_GOOGLE_REFRESH_TOKEN": "server-refresh"
34
+ }):
35
+ from services.drive_service import DriveService
36
+
37
+ service = DriveService()
38
+
39
+ assert service.client_id == "server-client-id"
40
+ assert service.client_secret == "server-secret"
41
+ assert service.refresh_token == "server-refresh"
42
+
43
+ def test_fallback_to_google_credentials(self):
44
+ """Fallback to GOOGLE_* env vars when SERVER_* missing."""
45
+ with patch.dict(os.environ, {
46
+ "GOOGLE_CLIENT_ID": "google-client-id",
47
+ "GOOGLE_CLIENT_SECRET": "google-secret",
48
+ "GOOGLE_REFRESH_TOKEN": "google-refresh"
49
+ }, clear=True):
50
+ # Clear SERVER_* vars
51
+ os.environ.pop("SERVER_GOOGLE_CLIENT_ID", None)
52
+ os.environ.pop("SERVER_GOOGLE_CLIENT_SECRET", None)
53
+ os.environ.pop("SERVER_GOOGLE_REFRESH_TOKEN", None)
54
+
55
+ from services.drive_service import DriveService
56
+
57
+ service = DriveService()
58
+
59
+ assert service.client_id == "google-client-id"
60
+
61
+ def test_init_with_missing_credentials(self):
62
+ """Initialize with missing credentials (None values)."""
63
+ with patch.dict(os.environ, {}, clear=True):
64
+ os.environ.pop("SERVER_GOOGLE_CLIENT_ID", None)
65
+ os.environ.pop("GOOGLE_CLIENT_ID", None)
66
+ os.environ.pop("SERVER_GOOGLE_CLIENT_SECRET", None)
67
+ os.environ.pop("GOOGLE_CLIENT_SECRET", None)
68
+ os.environ.pop("SERVER_GOOGLE_REFRESH_TOKEN", None)
69
+ os.environ.pop("GOOGLE_REFRESH_TOKEN", None)
70
+
71
+ from services.drive_service import DriveService
72
+
73
+ service = DriveService()
74
+
75
+ # Should still initialize, but with None values
76
+ assert service.creds is None
77
+ assert service.service is None
78
+
79
+
80
+ # =============================================================================
81
+ # 2. Authentication Tests
82
+ # =============================================================================
83
+
84
+ class TestAuthentication:
85
+ """Test authenticate method."""
86
+
87
+ def test_authenticate_success(self):
88
+ """Successful authentication with valid refresh token."""
89
+ with patch('services.drive_service.Credentials') as mock_creds:
90
+ with patch('services.drive_service.build') as mock_build:
91
+ mock_cred_instance = MagicMock()
92
+ mock_cred_instance.expired = False
93
+ mock_creds.return_value = mock_cred_instance
94
+ mock_build.return_value = MagicMock()
95
+
96
+ from services.drive_service import DriveService
97
+
98
+ service = DriveService()
99
+ service.client_id = "test-id"
100
+ service.client_secret = "test-secret"
101
+ service.refresh_token = "test-token"
102
+
103
+ result = service.authenticate()
104
+
105
+ assert result == True
106
+ assert service.creds is not None
107
+ assert service.service is not None
108
+
109
+ def test_authenticate_returns_false_when_missing_credentials(self):
110
+ """Return False when credentials are missing."""
111
+ from services.drive_service import DriveService
112
+
113
+ service = DriveService()
114
+ service.client_id = None
115
+ service.client_secret = "secret"
116
+ service.refresh_token = "token"
117
+
118
+ result = service.authenticate()
119
+
120
+ assert result == False
121
+
122
+ def test_authenticate_handles_exception(self):
123
+ """Handle authentication exception."""
124
+ with patch('services.drive_service.Credentials') as mock_creds:
125
+ mock_creds.side_effect = Exception("Auth failed")
126
+
127
+ from services.drive_service import DriveService
128
+
129
+ service = DriveService()
130
+ service.client_id = "test-id"
131
+ service.client_secret = "test-secret"
132
+ service.refresh_token = "test-token"
133
+
134
+ result = service.authenticate()
135
+
136
+ assert result == False
137
+
138
+ def test_authenticate_refreshes_expired_token(self):
139
+ """Refresh expired token when needed."""
140
+ with patch('services.drive_service.Credentials') as mock_creds:
141
+ with patch('services.drive_service.build') as mock_build:
142
+ with patch('services.drive_service.Request') as mock_request:
143
+ mock_cred_instance = MagicMock()
144
+ mock_cred_instance.expired = True
145
+ mock_cred_instance.refresh_token = "has-refresh"
146
+ mock_creds.return_value = mock_cred_instance
147
+ mock_build.return_value = MagicMock()
148
+
149
+ from services.drive_service import DriveService
150
+
151
+ service = DriveService()
152
+ service.client_id = "test-id"
153
+ service.client_secret = "test-secret"
154
+ service.refresh_token = "test-token"
155
+
156
+ result = service.authenticate()
157
+
158
+ assert result == True
159
+ mock_cred_instance.refresh.assert_called_once()
160
+
161
+
162
+ # =============================================================================
163
+ # 3. Folder Operations Tests
164
+ # =============================================================================
165
+
166
+ class TestFolderOperations:
167
+ """Test folder find/create operations."""
168
+
169
+ def test_find_folder_returns_id_when_found(self):
170
+ """Find existing folder returns its ID."""
171
+ from services.drive_service import DriveService
172
+
173
+ service = DriveService()
174
+ service.service = MagicMock()
175
+ service.service.files.return_value.list.return_value.execute.return_value = {
176
+ 'files': [{'id': 'folder-123', 'name': 'apigateway'}]
177
+ }
178
+
179
+ result = service._find_folder()
180
+
181
+ assert result == 'folder-123'
182
+
183
+ def test_find_folder_returns_none_when_not_found(self):
184
+ """Return None when folder not found."""
185
+ from services.drive_service import DriveService
186
+
187
+ service = DriveService()
188
+ service.service = MagicMock()
189
+ service.service.files.return_value.list.return_value.execute.return_value = {
190
+ 'files': []
191
+ }
192
+
193
+ result = service._find_folder()
194
+
195
+ assert result is None
196
+
197
+ def test_find_folder_handles_http_error(self):
198
+ """Handle HTTP error in folder search."""
199
+ from services.drive_service import DriveService
200
+
201
+ service = DriveService()
202
+ service.service = MagicMock()
203
+
204
+ # Create mock HttpError
205
+ mock_resp = MagicMock()
206
+ mock_resp.status = 500
207
+ service.service.files.return_value.list.return_value.execute.side_effect = HttpError(
208
+ mock_resp, b"Server error"
209
+ )
210
+
211
+ result = service._find_folder()
212
+
213
+ assert result is None
214
+
215
+ def test_create_folder_returns_id(self):
216
+ """Create folder returns new folder ID."""
217
+ from services.drive_service import DriveService
218
+
219
+ service = DriveService()
220
+ service.service = MagicMock()
221
+ service.service.files.return_value.create.return_value.execute.return_value = {
222
+ 'id': 'new-folder-456'
223
+ }
224
+
225
+ result = service._create_folder()
226
+
227
+ assert result == 'new-folder-456'
228
+
229
+ def test_create_folder_handles_error(self):
230
+ """Handle error in folder creation."""
231
+ from services.drive_service import DriveService
232
+
233
+ service = DriveService()
234
+ service.service = MagicMock()
235
+
236
+ mock_resp = MagicMock()
237
+ mock_resp.status = 403
238
+ service.service.files.return_value.create.return_value.execute.side_effect = HttpError(
239
+ mock_resp, b"Permission denied"
240
+ )
241
+
242
+ result = service._create_folder()
243
+
244
+ assert result is None
245
+
246
+ def test_get_folder_id_creates_if_not_found(self):
247
+ """Get folder creates if not found."""
248
+ from services.drive_service import DriveService
249
+
250
+ service = DriveService()
251
+ service._find_folder = MagicMock(return_value=None)
252
+ service._create_folder = MagicMock(return_value='created-folder-789')
253
+
254
+ result = service._get_folder_id()
255
+
256
+ assert result == 'created-folder-789'
257
+ service._create_folder.assert_called_once()
258
+
259
+ def test_get_folder_id_returns_existing(self):
260
+ """Get folder returns existing folder ID."""
261
+ from services.drive_service import DriveService
262
+
263
+ service = DriveService()
264
+ service._find_folder = MagicMock(return_value='existing-folder-111')
265
+ service._create_folder = MagicMock()
266
+
267
+ result = service._get_folder_id()
268
+
269
+ assert result == 'existing-folder-111'
270
+ service._create_folder.assert_not_called()
271
+
272
+
273
+ # =============================================================================
274
+ # 4. Upload Database Tests
275
+ # =============================================================================
276
+
277
+ class TestUploadDb:
278
+ """Test upload_db method."""
279
+
280
+ def test_upload_new_file(self):
281
+ """Upload creates new file when not exists."""
282
+ from services.drive_service import DriveService
283
+
284
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
285
+ f.write(b"test db content")
286
+ temp_path = f.name
287
+
288
+ try:
289
+ service = DriveService()
290
+ service.DB_FILENAME = temp_path
291
+ service.service = MagicMock()
292
+ service._get_folder_id = MagicMock(return_value='folder-id')
293
+
294
+ # File doesn't exist in Drive
295
+ service.service.files.return_value.list.return_value.execute.return_value = {
296
+ 'files': []
297
+ }
298
+ service.service.files.return_value.create.return_value.execute.return_value = {
299
+ 'id': 'new-file-id'
300
+ }
301
+
302
+ with patch('services.drive_service.MediaFileUpload') as mock_upload:
303
+ mock_upload.return_value = MagicMock()
304
+ result = service.upload_db()
305
+
306
+ assert result == True
307
+ service.service.files.return_value.create.assert_called()
308
+ finally:
309
+ os.unlink(temp_path)
310
+
311
+ def test_upload_updates_existing_file(self):
312
+ """Upload updates existing file."""
313
+ from services.drive_service import DriveService
314
+
315
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
316
+ f.write(b"updated db content")
317
+ temp_path = f.name
318
+
319
+ try:
320
+ service = DriveService()
321
+ service.DB_FILENAME = temp_path
322
+ service.service = MagicMock()
323
+ service._get_folder_id = MagicMock(return_value='folder-id')
324
+
325
+ # File exists in Drive
326
+ service.service.files.return_value.list.return_value.execute.return_value = {
327
+ 'files': [{'id': 'existing-file-id'}]
328
+ }
329
+
330
+ with patch('services.drive_service.MediaFileUpload') as mock_upload:
331
+ mock_upload.return_value = MagicMock()
332
+ result = service.upload_db()
333
+
334
+ assert result == True
335
+ service.service.files.return_value.update.assert_called()
336
+ finally:
337
+ os.unlink(temp_path)
338
+
339
+ def test_upload_returns_false_if_db_missing(self):
340
+ """Return False if database file doesn't exist."""
341
+ from services.drive_service import DriveService
342
+
343
+ service = DriveService()
344
+ service.DB_FILENAME = '/nonexistent/path/db.sqlite'
345
+ service.service = MagicMock()
346
+
347
+ result = service.upload_db()
348
+
349
+ assert result == False
350
+
351
+ def test_upload_returns_false_if_folder_fails(self):
352
+ """Return False if folder creation fails."""
353
+ from services.drive_service import DriveService
354
+
355
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
356
+ f.write(b"test")
357
+ temp_path = f.name
358
+
359
+ try:
360
+ service = DriveService()
361
+ service.DB_FILENAME = temp_path
362
+ service.service = MagicMock()
363
+ service._get_folder_id = MagicMock(return_value=None)
364
+
365
+ result = service.upload_db()
366
+
367
+ assert result == False
368
+ finally:
369
+ os.unlink(temp_path)
370
+
371
+ def test_upload_handles_http_error(self):
372
+ """Handle HTTP error during upload."""
373
+ from services.drive_service import DriveService
374
+
375
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
376
+ f.write(b"test")
377
+ temp_path = f.name
378
+
379
+ try:
380
+ service = DriveService()
381
+ service.DB_FILENAME = temp_path
382
+ service.service = MagicMock()
383
+ service._get_folder_id = MagicMock(return_value='folder-id')
384
+
385
+ service.service.files.return_value.list.return_value.execute.return_value = {
386
+ 'files': []
387
+ }
388
+
389
+ mock_resp = MagicMock()
390
+ mock_resp.status = 500
391
+ service.service.files.return_value.create.return_value.execute.side_effect = HttpError(
392
+ mock_resp, b"Server error"
393
+ )
394
+
395
+ with patch('services.drive_service.MediaFileUpload'):
396
+ result = service.upload_db()
397
+
398
+ assert result == False
399
+ finally:
400
+ os.unlink(temp_path)
401
+
402
+ def test_upload_auto_authenticates(self):
403
+ """Auto-authenticate if not authenticated."""
404
+ from services.drive_service import DriveService
405
+
406
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
407
+ f.write(b"test")
408
+ temp_path = f.name
409
+
410
+ try:
411
+ service = DriveService()
412
+ service.DB_FILENAME = temp_path
413
+ service.service = None
414
+ service.authenticate = MagicMock(return_value=False)
415
+
416
+ result = service.upload_db()
417
+
418
+ assert result == False
419
+ service.authenticate.assert_called_once()
420
+ finally:
421
+ os.unlink(temp_path)
422
+
423
+
424
+ # =============================================================================
425
+ # 5. Download Database Tests
426
+ # =============================================================================
427
+
428
+ class TestDownloadDb:
429
+ """Test download_db method."""
430
+
431
+ def test_download_existing_file(self):
432
+ """Download existing database file."""
433
+ from services.drive_service import DriveService
434
+
435
+ with tempfile.TemporaryDirectory() as temp_dir:
436
+ db_path = os.path.join(temp_dir, 'test.db')
437
+
438
+ service = DriveService()
439
+ service.DB_FILENAME = db_path
440
+ service.service = MagicMock()
441
+ service._find_folder = MagicMock(return_value='folder-id')
442
+
443
+ service.service.files.return_value.list.return_value.execute.return_value = {
444
+ 'files': [{'id': 'file-id'}]
445
+ }
446
+
447
+ # Mock downloader
448
+ with patch('services.drive_service.MediaIoBaseDownload') as mock_downloader:
449
+ mock_instance = MagicMock()
450
+ mock_instance.next_chunk.return_value = (MagicMock(), True) # Done immediately
451
+ mock_downloader.return_value = mock_instance
452
+
453
+ with patch('io.FileIO'):
454
+ result = service.download_db()
455
+
456
+ assert result == True
457
+
458
+ def test_download_returns_false_if_folder_not_found(self):
459
+ """Return False if folder not found."""
460
+ from services.drive_service import DriveService
461
+
462
+ service = DriveService()
463
+ service.service = MagicMock()
464
+ service._find_folder = MagicMock(return_value=None)
465
+
466
+ result = service.download_db()
467
+
468
+ assert result == False
469
+
470
+ def test_download_returns_false_if_file_not_found(self):
471
+ """Return False if database file not found in Drive."""
472
+ from services.drive_service import DriveService
473
+
474
+ service = DriveService()
475
+ service.service = MagicMock()
476
+ service._find_folder = MagicMock(return_value='folder-id')
477
+
478
+ service.service.files.return_value.list.return_value.execute.return_value = {
479
+ 'files': []
480
+ }
481
+
482
+ result = service.download_db()
483
+
484
+ assert result == False
485
+
486
+ def test_download_handles_http_error(self):
487
+ """Handle HTTP error during download."""
488
+ from services.drive_service import DriveService
489
+
490
+ service = DriveService()
491
+ service.service = MagicMock()
492
+ service._find_folder = MagicMock(return_value='folder-id')
493
+
494
+ service.service.files.return_value.list.return_value.execute.return_value = {
495
+ 'files': [{'id': 'file-id'}]
496
+ }
497
+
498
+ mock_resp = MagicMock()
499
+ mock_resp.status = 500
500
+ service.service.files.return_value.get_media.side_effect = HttpError(
501
+ mock_resp, b"Server error"
502
+ )
503
+
504
+ result = service.download_db()
505
+
506
+ assert result == False
507
+
508
+ def test_download_auto_authenticates(self):
509
+ """Auto-authenticate if not authenticated."""
510
+ from services.drive_service import DriveService
511
+
512
+ service = DriveService()
513
+ service.service = None
514
+ service.authenticate = MagicMock(return_value=False)
515
+
516
+ result = service.download_db()
517
+
518
+ assert result == False
519
+ service.authenticate.assert_called_once()
520
+
521
+
522
+ # =============================================================================
523
+ # 6. Integration-like Tests
524
+ # =============================================================================
525
+
526
+ class TestDriveServiceIntegration:
527
+ """Test complete flows with mocked API."""
528
+
529
+ def test_full_upload_flow(self):
530
+ """Test complete upload flow from auth to upload."""
531
+ from services.drive_service import DriveService
532
+
533
+ with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
534
+ f.write(b"test db content")
535
+ temp_path = f.name
536
+
537
+ try:
538
+ with patch('services.drive_service.Credentials') as mock_creds:
539
+ with patch('services.drive_service.build') as mock_build:
540
+ mock_cred_instance = MagicMock()
541
+ mock_cred_instance.expired = False
542
+ mock_creds.return_value = mock_cred_instance
543
+
544
+ mock_service = MagicMock()
545
+ mock_build.return_value = mock_service
546
+
547
+ # Folder exists
548
+ mock_service.files.return_value.list.return_value.execute.side_effect = [
549
+ {'files': [{'id': 'folder-id', 'name': 'apigateway'}]}, # find folder
550
+ {'files': []} # file not in folder
551
+ ]
552
+ mock_service.files.return_value.create.return_value.execute.return_value = {
553
+ 'id': 'new-file-id'
554
+ }
555
+
556
+ service = DriveService()
557
+ service.DB_FILENAME = temp_path
558
+ service.client_id = "test"
559
+ service.client_secret = "test"
560
+ service.refresh_token = "test"
561
+
562
+ with patch('services.drive_service.MediaFileUpload'):
563
+ result = service.upload_db()
564
+
565
+ assert result == True
566
+ finally:
567
+ os.unlink(temp_path)
568
+
569
+
570
+ if __name__ == "__main__":
571
+ pytest.main([__file__, "-v"])