"""Unit tests for data collectors""" import pytest from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch from app.services.collectors.top500 import TOP500Collector from app.services.collectors.base import BaseCollector, HTTPCollector from app.models.task import CollectionTask class TestBaseCollector: """Tests for BaseCollector""" def test_base_collector_attributes(self): """Test base collector has correct default attributes via concrete class""" collector = TOP500Collector() assert collector.name == "top500" assert collector.priority == "P0" assert collector.module == "L1" assert collector.frequency_hours == 4 @pytest.mark.asyncio async def test_update_phase_progress_tracks_phase_fields(self, mock_db_session): """Test phase-level progress updates independently from record totals""" collector = TOP500Collector() task = CollectionTask(datasource_id=1, status="running", phase="fetching") collector._current_task = task collector._db_session = mock_db_session with patch.object(collector, "_publish_task_update", new=AsyncMock()) as publish: await collector.update_phase_progress( current=512, total=1024, unit="bytes", message="Downloading dataset", commit=True, ) assert task.phase_progress == 50.0 assert task.phase_current == 512 assert task.phase_total == 1024 assert task.phase_unit == "bytes" assert task.phase_message == "Downloading dataset" mock_db_session.commit.assert_awaited_once() publish.assert_awaited_once() class TestTOP500Collector: """Tests for TOP500Collector""" def test_parse_coordinate_valid_float(self): """Test parsing valid float coordinate""" collector = TOP500Collector() assert collector._parse_coordinate(45.5) == 45.5 def test_parse_coordinate_valid_string(self): """Test parsing valid string coordinate""" collector = TOP500Collector() assert collector._parse_coordinate("45.5") == 45.5 def test_parse_coordinate_invalid_string(self): """Test parsing invalid string coordinate""" collector = TOP500Collector() assert collector._parse_coordinate("invalid") == 0.0 def test_parse_coordinate_none(self): """Test parsing None coordinate""" collector = TOP500Collector() assert collector._parse_coordinate(None) == 0.0 def test_parse_response_empty(self): """Test parsing empty response""" collector = TOP500Collector() result = collector.parse_response("
") assert len(result) > 0 def test_parse_response_single_item(self): """Test parsing single item response""" collector = TOP500Collector() response = """
RankSystemCoresRmaxRpeakPower
1 Test Supercomputer, Test Corp\nTest Site\nUSA 100000 100 PFLOP/s 150 PFLOP/s 5000
""" result = collector.parse_response(response) assert len(result) == 1 assert result[0]["source_id"] == "top500_1" assert result[0]["name"] == "Test Supercomputer" assert result[0]["country"] == "USA" assert result[0]["metadata"]["rank"] == 1 assert "Test Corp" in result[0]["metadata"]["manufacturer"] def test_parse_response_skips_invalid_item(self): """Test parsing skips items with missing data""" collector = TOP500Collector() response = """
RankSystemCoresRmaxRpeakPower
1 Valid\nVendor\nSite\nUSA 1000 10 PFLOP/s 12 PFLOP/s 100
- Invalid 1000 10 PFLOP/s 12 PFLOP/s 100
""" result = collector.parse_response(response) assert len(result) == 1 assert result[0]["name"] == "Valid" class TestHTTPCollector: """Tests for HTTPCollector""" def test_http_collector_attributes(self): """Test HTTP collector has correct default attributes via concrete class""" collector = TOP500Collector() assert collector.name == "top500" assert collector.priority == "P0" assert hasattr(collector, "fetch") def test_collector_has_required_methods(self): """Test HTTP collector has required methods""" collector = TOP500Collector() assert hasattr(collector, "fetch") assert hasattr(collector, "parse_response") assert callable(collector.fetch) assert callable(collector.parse_response)