SolarImageDownloader / tests /test_image_fetcher.py
AK51's picture
Upload 13308 files
b610d23 verified
"""Tests for image fetcher and download manager."""
import pytest
import time
from unittest.mock import Mock, patch, MagicMock
import requests
from datetime import datetime
from pathlib import Path
from src.downloader.image_fetcher import ImageFetcher, DownloadManager
from src.models import DownloadTask, TaskStatus
class TestImageFetcher:
"""Test cases for ImageFetcher class."""
def setup_method(self):
"""Set up test fixtures."""
self.fetcher = ImageFetcher(rate_limit_delay=0.1, max_retries=3)
def test_rate_limiting(self):
"""Test that rate limiting works."""
start_time = time.time()
# Make two requests
self.fetcher._enforce_rate_limit()
self.fetcher._enforce_rate_limit()
elapsed = time.time() - start_time
# Should take at least the rate limit delay
assert elapsed >= 0.1
def test_exponential_backoff(self):
"""Test exponential backoff calculation."""
assert self.fetcher._exponential_backoff(0) == 1
assert self.fetcher._exponential_backoff(1) == 2
assert self.fetcher._exponential_backoff(2) == 4
assert self.fetcher._exponential_backoff(3) == 8
# Should cap at 60 seconds
assert self.fetcher._exponential_backoff(10) == 60
@patch('src.downloader.image_fetcher.requests.Session.get')
def test_download_image_success(self, mock_get):
"""Test successful image download."""
# Mock successful response
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = b"fake image data"
mock_get.return_value = mock_response
success, data, error = self.fetcher.download_image("https://example.com/image.jpg")
assert success is True
assert data == b"fake image data"
assert error is None
@patch('src.downloader.image_fetcher.requests.Session.get')
def test_download_image_404(self, mock_get):
"""Test download when image doesn't exist."""
# Mock 404 response
mock_response = Mock()
mock_response.status_code = 404
mock_get.return_value = mock_response
success, data, error = self.fetcher.download_image("https://example.com/missing.jpg")
assert success is False
assert data is None
assert "404" in error
@patch('src.downloader.image_fetcher.requests.Session.get')
def test_download_image_http_error_with_retry(self, mock_get):
"""Test download with HTTP error that gets retried."""
# Mock server error that eventually succeeds
mock_response_error = Mock()
mock_response_error.status_code = 500
mock_response_success = Mock()
mock_response_success.status_code = 200
mock_response_success.content = b"success data"
mock_get.side_effect = [mock_response_error, mock_response_success]
success, data, error = self.fetcher.download_image("https://example.com/image.jpg")
assert success is True
assert data == b"success data"
assert error is None
assert mock_get.call_count == 2
@patch('src.downloader.image_fetcher.requests.Session.get')
def test_download_image_timeout_retry(self, mock_get):
"""Test download with timeout that gets retried."""
# Mock timeout then success
mock_get.side_effect = [
requests.exceptions.Timeout("Timeout"),
Mock(status_code=200, content=b"success after timeout")
]
success, data, error = self.fetcher.download_image("https://example.com/image.jpg")
assert success is True
assert data == b"success after timeout"
assert error is None
@patch('src.downloader.image_fetcher.requests.Session.get')
def test_download_image_max_retries_exceeded(self, mock_get):
"""Test download when max retries are exceeded."""
# Mock persistent server error
mock_response = Mock()
mock_response.status_code = 500
mock_get.return_value = mock_response
success, data, error = self.fetcher.download_image("https://example.com/image.jpg")
assert success is False
assert data is None
assert "HTTP 500" in error
assert mock_get.call_count == 3 # max_retries
@patch('src.downloader.image_fetcher.requests.Session.head')
def test_check_image_exists_true(self, mock_head):
"""Test checking if image exists when it does."""
mock_response = Mock()
mock_response.status_code = 200
mock_head.return_value = mock_response
exists = self.fetcher.check_image_exists("https://example.com/image.jpg")
assert exists is True
@patch('src.downloader.image_fetcher.requests.Session.head')
def test_check_image_exists_false(self, mock_head):
"""Test checking if image exists when it doesn't."""
mock_response = Mock()
mock_response.status_code = 404
mock_head.return_value = mock_response
exists = self.fetcher.check_image_exists("https://example.com/image.jpg")
assert exists is False
@patch('src.downloader.image_fetcher.requests.Session.head')
def test_get_image_size(self, mock_head):
"""Test getting image size from headers."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.headers = {'content-length': '1024000'}
mock_head.return_value = mock_response
size = self.fetcher.get_image_size("https://example.com/image.jpg")
assert size == 1024000
class TestDownloadManager:
"""Test cases for DownloadManager class."""
def setup_method(self):
"""Set up test fixtures."""
self.mock_fetcher = Mock(spec=ImageFetcher)
self.mock_storage = Mock()
self.manager = DownloadManager(self.mock_fetcher, self.mock_storage)
def test_download_and_save_success(self):
"""Test successful download and save."""
# Set up task
task = DownloadTask(
url="https://sdo.gsfc.nasa.gov/assets/img/browse/2025/12/19/20251219_120000_4096_0211.jpg",
target_path=Path("data/2025/12/19/20251219_120000_4096_0211.jpg")
)
# Mock storage responses
self.mock_storage.file_exists.return_value = False
self.mock_storage.save_image.return_value = task.target_path
self.mock_storage.validate_file_integrity.return_value = True
# Mock fetcher response
self.mock_fetcher.download_image.return_value = (True, b"image data", None)
# Execute
success = self.manager.download_and_save(task)
# Verify
assert success is True
assert task.status == TaskStatus.COMPLETED
assert self.manager.get_download_count() == 1
def test_download_and_save_duplicate_skip(self):
"""Test skipping download when file already exists."""
task = DownloadTask(
url="https://sdo.gsfc.nasa.gov/assets/img/browse/2025/12/19/20251219_120000_4096_0211.jpg",
target_path=Path("data/2025/12/19/20251219_120000_4096_0211.jpg")
)
# Mock file already exists
self.mock_storage.file_exists.return_value = True
success = self.manager.download_and_save(task)
assert success is True
assert task.status == TaskStatus.COMPLETED
# Should not call download since file exists
self.mock_fetcher.download_image.assert_not_called()
def test_download_and_save_download_failure(self):
"""Test handling download failure."""
task = DownloadTask(
url="https://sdo.gsfc.nasa.gov/assets/img/browse/2025/12/19/20251219_120000_4096_0211.jpg",
target_path=Path("data/2025/12/19/20251219_120000_4096_0211.jpg")
)
# Mock storage and fetcher
self.mock_storage.file_exists.return_value = False
self.mock_fetcher.download_image.return_value = (False, None, "Network error")
success = self.manager.download_and_save(task)
assert success is False
assert task.status == TaskStatus.FAILED
assert task.error_message == "Network error"
assert len(self.manager.get_failed_tasks()) == 1
def test_download_and_save_integrity_failure(self):
"""Test handling file integrity failure."""
task = DownloadTask(
url="https://sdo.gsfc.nasa.gov/assets/img/browse/2025/12/19/20251219_120000_4096_0211.jpg",
target_path=Path("data/2025/12/19/20251219_120000_4096_0211.jpg")
)
# Mock successful download but failed integrity check
self.mock_storage.file_exists.return_value = False
self.mock_storage.save_image.return_value = task.target_path
self.mock_storage.validate_file_integrity.return_value = False
self.mock_fetcher.download_image.return_value = (True, b"image data", None)
success = self.manager.download_and_save(task)
assert success is False
assert task.status == TaskStatus.FAILED
assert "integrity check failed" in task.error_message.lower()
def test_reset_counters(self):
"""Test resetting download counters."""
# Add some data
self.manager.download_count = 5
self.manager.failed_tasks = [Mock(), Mock()]
# Reset
self.manager.reset_counters()
assert self.manager.get_download_count() == 0
assert len(self.manager.get_failed_tasks()) == 0