|
1 | 1 | """Tests for download utility functions in the application module.""" |
2 | 2 |
|
| 3 | +from collections.abc import Generator |
3 | 4 | from pathlib import Path |
4 | | -from unittest.mock import Mock, patch |
| 5 | +from unittest.mock import MagicMock, Mock, patch |
5 | 6 |
|
6 | 7 | import pytest |
7 | 8 | import requests |
8 | 9 |
|
9 | | -from aignostics.application._download import download_url_to_file_with_progress, extract_filename_from_url |
| 10 | +from aignostics.application._download import ( |
| 11 | + download_available_items, |
| 12 | + download_item_artifact, |
| 13 | + download_url_to_file_with_progress, |
| 14 | + extract_filename_from_url, |
| 15 | +) |
10 | 16 | from aignostics.application._models import DownloadProgress, DownloadProgressState |
11 | 17 |
|
12 | 18 |
|
@@ -399,168 +405,121 @@ def progress_callback(p: DownloadProgress) -> None: |
399 | 405 | mock_get.assert_called_once_with(https_url, stream=True, timeout=60) |
400 | 406 |
|
401 | 407 |
|
402 | | -@pytest.mark.unit |
403 | | -def test_download_available_items_calls_url_resolver(tmp_path: Path) -> None: |
404 | | - """Test that download_available_items uses the get_artifact_download_url callback. |
| 408 | +@pytest.fixture |
| 409 | +def patched_item_and_run() -> Generator[tuple[MagicMock, MagicMock], None, None]: |
| 410 | + """Fixture providing patched ItemState/ItemOutput enums and a mock run with one configurable artifact. |
405 | 411 |
|
406 | | - Verifies that when a callback is provided, it is called with (run_id, artifact_id) |
407 | | - and the resolved URL is passed to download_item_artifact. |
| 412 | + Yields: |
| 413 | + tuple[MagicMock, MagicMock]: The mock run and its single mock artifact. |
| 414 | + Tests configure artifact attributes (output_artifact_id, download_url, metadata) as needed. |
408 | 415 | """ |
409 | | - from unittest.mock import MagicMock |
410 | | - |
411 | | - from aignostics.application._download import download_available_items |
412 | | - |
413 | | - # Build mock artifact |
414 | 416 | mock_artifact = MagicMock() |
415 | | - mock_artifact.output_artifact_id = "artifact-xyz" |
416 | | - mock_artifact.name = "result" |
417 | | - mock_artifact.metadata = {"checksum_base64_crc32c": "AAAA", "checksum_crc32c": ""} |
418 | | - |
419 | | - # Build mock item |
420 | 417 | mock_item = MagicMock() |
421 | 418 | mock_item.external_id = "slide-1" |
422 | 419 | mock_item.state = "TERMINATED" |
423 | 420 | mock_item.output = "FULL" |
424 | 421 | mock_item.output_artifacts = [mock_artifact] |
425 | 422 |
|
426 | | - # Patch enums for comparison |
427 | 423 | with ( |
428 | 424 | patch("aignostics.application._download.ItemState") as mock_item_state, |
429 | 425 | patch("aignostics.application._download.ItemOutput") as mock_item_output, |
430 | 426 | ): |
431 | 427 | mock_item_state.TERMINATED = "TERMINATED" |
432 | 428 | mock_item_output.FULL = "FULL" |
433 | 429 |
|
434 | | - # Build mock run |
435 | 430 | mock_run = MagicMock() |
436 | 431 | mock_run.run_id = "run-123" |
437 | 432 | mock_run.results.return_value = [mock_item] |
438 | 433 |
|
439 | | - progress = DownloadProgress() |
440 | | - downloaded_items: set[str] = set() |
441 | | - resolved_url = "https://storage.googleapis.com/presigned" |
442 | | - mock_url_resolver = MagicMock(return_value=resolved_url) |
443 | | - |
444 | | - with patch("aignostics.application._download.download_item_artifact") as mock_download: |
445 | | - download_available_items( |
446 | | - progress=progress, |
447 | | - application_run=mock_run, |
448 | | - destination_directory=tmp_path, |
449 | | - downloaded_items=downloaded_items, |
450 | | - get_artifact_download_url=mock_url_resolver, |
451 | | - ) |
| 434 | + yield mock_run, mock_artifact |
| 435 | + |
| 436 | + |
| 437 | +@pytest.mark.unit |
| 438 | +def test_download_available_items_calls_url_resolver( |
| 439 | + tmp_path: Path, patched_item_and_run: tuple[MagicMock, MagicMock] |
| 440 | +) -> None: |
| 441 | + """Test that download_available_items uses the get_artifact_download_url callback. |
| 442 | +
|
| 443 | + Verifies that when a callback is provided, it is called with (run_id, artifact_id) |
| 444 | + and the resolved URL is passed to download_item_artifact. |
| 445 | + """ |
| 446 | + mock_run, mock_artifact = patched_item_and_run |
| 447 | + mock_artifact.output_artifact_id = "artifact-xyz" |
| 448 | + mock_artifact.name = "result" |
| 449 | + mock_artifact.metadata = {"checksum_base64_crc32c": "AAAA", "checksum_crc32c": ""} |
| 450 | + |
| 451 | + resolved_url = "https://storage.googleapis.com/presigned" |
| 452 | + mock_url_resolver = MagicMock(return_value=resolved_url) |
| 453 | + |
| 454 | + with patch("aignostics.application._download.download_item_artifact") as mock_download: |
| 455 | + download_available_items( |
| 456 | + progress=DownloadProgress(), |
| 457 | + application_run=mock_run, |
| 458 | + destination_directory=tmp_path, |
| 459 | + downloaded_items=set(), |
| 460 | + get_artifact_download_url=mock_url_resolver, |
| 461 | + ) |
452 | 462 |
|
453 | | - mock_url_resolver.assert_called_once_with("run-123", "artifact-xyz") |
454 | | - mock_download.assert_called_once() |
455 | | - # artifact_download_url is the 3rd positional arg |
456 | | - assert mock_download.call_args[0][2] == resolved_url |
| 463 | + mock_url_resolver.assert_called_once_with("run-123", "artifact-xyz") |
| 464 | + mock_download.assert_called_once() |
| 465 | + assert mock_download.call_args[0][2] == resolved_url # artifact_download_url is 3rd positional arg |
457 | 466 |
|
458 | 467 |
|
459 | 468 | @pytest.mark.unit |
460 | | -def test_download_available_items_falls_back_to_download_url(tmp_path: Path) -> None: |
| 469 | +def test_download_available_items_falls_back_to_download_url( |
| 470 | + tmp_path: Path, patched_item_and_run: tuple[MagicMock, MagicMock] |
| 471 | +) -> None: |
461 | 472 | """Test that download_available_items falls back to artifact.download_url when no callback. |
462 | 473 |
|
463 | 474 | Verifies the deprecated fallback path when get_artifact_download_url is None. |
464 | 475 | """ |
465 | | - from unittest.mock import MagicMock |
466 | | - |
467 | | - from aignostics.application._download import download_available_items |
468 | | - |
469 | | - mock_artifact = MagicMock() |
| 476 | + mock_run, mock_artifact = patched_item_and_run |
470 | 477 | mock_artifact.output_artifact_id = "artifact-xyz" |
471 | 478 | mock_artifact.name = "result" |
472 | 479 | mock_artifact.download_url = "https://old-signed-url.com/file" |
473 | 480 | mock_artifact.metadata = {"checksum_base64_crc32c": "AAAA"} |
474 | 481 |
|
475 | | - mock_item = MagicMock() |
476 | | - mock_item.external_id = "slide-1" |
477 | | - mock_item.state = "TERMINATED" |
478 | | - mock_item.output = "FULL" |
479 | | - mock_item.output_artifacts = [mock_artifact] |
480 | | - |
481 | | - with ( |
482 | | - patch("aignostics.application._download.ItemState") as mock_item_state, |
483 | | - patch("aignostics.application._download.ItemOutput") as mock_item_output, |
484 | | - ): |
485 | | - mock_item_state.TERMINATED = "TERMINATED" |
486 | | - mock_item_output.FULL = "FULL" |
487 | | - |
488 | | - mock_run = MagicMock() |
489 | | - mock_run.run_id = "run-123" |
490 | | - mock_run.results.return_value = [mock_item] |
491 | | - |
492 | | - progress = DownloadProgress() |
493 | | - downloaded_items: set[str] = set() |
494 | | - |
495 | | - with patch("aignostics.application._download.download_item_artifact") as mock_download: |
496 | | - download_available_items( |
497 | | - progress=progress, |
498 | | - application_run=mock_run, |
499 | | - destination_directory=tmp_path, |
500 | | - downloaded_items=downloaded_items, |
501 | | - get_artifact_download_url=None, |
502 | | - ) |
| 482 | + with patch("aignostics.application._download.download_item_artifact") as mock_download: |
| 483 | + download_available_items( |
| 484 | + progress=DownloadProgress(), |
| 485 | + application_run=mock_run, |
| 486 | + destination_directory=tmp_path, |
| 487 | + downloaded_items=set(), |
| 488 | + get_artifact_download_url=None, |
| 489 | + ) |
503 | 490 |
|
504 | | - mock_download.assert_called_once() |
505 | | - assert mock_download.call_args[0][2] == "https://old-signed-url.com/file" |
| 491 | + mock_download.assert_called_once() |
| 492 | + assert mock_download.call_args[0][2] == "https://old-signed-url.com/file" |
506 | 493 |
|
507 | 494 |
|
508 | 495 | @pytest.mark.unit |
509 | | -def test_download_available_items_skips_when_no_url_available(tmp_path: Path) -> None: |
| 496 | +def test_download_available_items_skips_when_no_url_available( |
| 497 | + tmp_path: Path, patched_item_and_run: tuple[MagicMock, MagicMock] |
| 498 | +) -> None: |
510 | 499 | """Test that download_available_items skips artifacts with no URL available. |
511 | 500 |
|
512 | 501 | When get_artifact_download_url is None and artifact.download_url is also None, |
513 | 502 | the artifact should be skipped. |
514 | 503 | """ |
515 | | - from unittest.mock import MagicMock |
516 | | - |
517 | | - from aignostics.application._download import download_available_items |
518 | | - |
519 | | - mock_artifact = MagicMock() |
| 504 | + mock_run, mock_artifact = patched_item_and_run |
520 | 505 | mock_artifact.output_artifact_id = None |
521 | | - mock_artifact.name = "result" |
522 | 506 | mock_artifact.download_url = None |
523 | | - mock_artifact.metadata = {"checksum_base64_crc32c": "AAAA"} |
524 | | - |
525 | | - mock_item = MagicMock() |
526 | | - mock_item.external_id = "slide-1" |
527 | | - mock_item.state = "TERMINATED" |
528 | | - mock_item.output = "FULL" |
529 | | - mock_item.output_artifacts = [mock_artifact] |
530 | | - |
531 | | - with ( |
532 | | - patch("aignostics.application._download.ItemState") as mock_item_state, |
533 | | - patch("aignostics.application._download.ItemOutput") as mock_item_output, |
534 | | - ): |
535 | | - mock_item_state.TERMINATED = "TERMINATED" |
536 | | - mock_item_output.FULL = "FULL" |
537 | 507 |
|
538 | | - mock_run = MagicMock() |
539 | | - mock_run.run_id = "run-123" |
540 | | - mock_run.results.return_value = [mock_item] |
541 | | - |
542 | | - progress = DownloadProgress() |
543 | | - downloaded_items: set[str] = set() |
544 | | - |
545 | | - with patch("aignostics.application._download.download_item_artifact") as mock_download: |
546 | | - download_available_items( |
547 | | - progress=progress, |
548 | | - application_run=mock_run, |
549 | | - destination_directory=tmp_path, |
550 | | - downloaded_items=downloaded_items, |
551 | | - get_artifact_download_url=None, |
552 | | - ) |
| 508 | + with patch("aignostics.application._download.download_item_artifact") as mock_download: |
| 509 | + download_available_items( |
| 510 | + progress=DownloadProgress(), |
| 511 | + application_run=mock_run, |
| 512 | + destination_directory=tmp_path, |
| 513 | + downloaded_items=set(), |
| 514 | + get_artifact_download_url=None, |
| 515 | + ) |
553 | 516 |
|
554 | | - mock_download.assert_not_called() |
| 517 | + mock_download.assert_not_called() |
555 | 518 |
|
556 | 519 |
|
557 | 520 | @pytest.mark.unit |
558 | 521 | def test_download_item_artifact_uses_provided_url(tmp_path: Path) -> None: |
559 | 522 | """Test that download_item_artifact uses the explicitly provided artifact_download_url.""" |
560 | | - from unittest.mock import MagicMock |
561 | | - |
562 | | - from aignostics.application._download import download_item_artifact |
563 | | - |
564 | 523 | mock_artifact = MagicMock() |
565 | 524 | mock_artifact.name = "cell_classification" |
566 | 525 | mock_artifact.metadata = {"checksum_base64_crc32c": "AAAA"} |
|
0 commit comments