""" Unit tests for Nexus API service. Tests cover: - Singleton pattern - Entity type enumeration - Search methods (items, mobs, all, by type) - Item details retrieval - Market data retrieval - Caching mechanism - Rate limiting - Error handling """ import pytest from unittest.mock import MagicMock, patch, mock_open from datetime import datetime from core.nexus_api import ( NexusAPI, get_nexus_api, NexusAPIError, RateLimitError, EntityType, SearchResult, ItemDetails, MarketData, search_items, search_mobs, search_all, get_item_details, get_market_data ) @pytest.mark.unit class TestNexusAPISingleton: """Test NexusAPI singleton behavior.""" def test_singleton_instance(self, reset_singletons): """Test that NexusAPI is a proper singleton.""" api1 = get_nexus_api() api2 = get_nexus_api() assert api1 is api2 assert isinstance(api1, NexusAPI) def test_singleton_reset(self, reset_singletons): """Test that singleton can be reset.""" api1 = get_nexus_api() NexusAPI._instance = None api2 = get_nexus_api() assert api1 is not api2 @pytest.mark.unit class TestEntityType: """Test EntityType enumeration.""" def test_entity_type_values(self): """Test entity type values.""" assert EntityType.ITEM.value == "items" assert EntityType.MOB.value == "mobs" assert EntityType.WEAPON.value == "weapons" assert EntityType.ARMOR.value == "armors" assert EntityType.BLUEPRINT.value == "blueprints" def test_get_all_types(self): """Test getting all entity types.""" types = EntityType.get_all_types() assert "items" in types assert "mobs" in types assert "all" not in types # ALL is excluded def test_from_string_valid(self): """Test getting EntityType from valid string.""" assert EntityType.from_string("items") == EntityType.ITEM assert EntityType.from_string("ITEMS") == EntityType.ITEM assert EntityType.from_string("mobs") == EntityType.MOB def test_from_string_invalid(self): """Test getting EntityType from invalid string.""" assert EntityType.from_string("invalid") is None assert EntityType.from_string("") is None @pytest.mark.unit class TestNexusAPIConfiguration: """Test NexusAPI configuration constants.""" def test_base_configuration(self, reset_singletons): """Test base configuration values.""" api = get_nexus_api() assert api.BASE_URL == "https://api.entropianexus.com" assert api.API_VERSION == "v1" assert api.MAX_REQUESTS_PER_SECOND == 5 assert api.MIN_REQUEST_INTERVAL == 0.2 # 1.0 / 5 assert api.MAX_RETRIES == 3 assert api.RETRY_DELAY == 1.0 def test_initialization(self, reset_singletons): """Test API initialization.""" api = get_nexus_api() assert api._initialized is True assert api._available is True assert api._cache_ttl == 300 # 5 minutes @pytest.mark.unit class TestCaching: """Test caching functionality.""" def test_cache_storage(self, reset_singletons): """Test that responses are cached.""" api = get_nexus_api() # Manually add to cache api._cache["test_key"] = {"data": "test"} api._cache_timestamps["test_key"] = datetime.now().timestamp() assert "test_key" in api._cache def test_cache_validity(self, reset_singletons): """Test cache validity checking.""" api = get_nexus_api() # Valid cache entry api._cache["valid"] = {"data": "test"} api._cache_timestamps["valid"] = datetime.now().timestamp() assert api._is_cache_valid("valid") is True # Expired cache entry api._cache["expired"] = {"data": "test"} api._cache_timestamps["expired"] = datetime.now().timestamp() - 400 # > 300s TTL assert api._is_cache_valid("expired") is False # Non-existent key assert api._is_cache_valid("nonexistent") is False def test_clear_cache(self, reset_singletons): """Test clearing cache.""" api = get_nexus_api() api._cache["key1"] = {"data": "test1"} api._cache["key2"] = {"data": "test2"} api.clear_cache() assert len(api._cache) == 0 assert len(api._cache_timestamps) == 0 @pytest.mark.unit class TestSearchMethods: """Test search methods.""" @patch('core.nexus_api.NexusAPI._make_request') def test_search_items(self, mock_request, reset_singletons): """Test searching for items.""" mock_request.return_value = { "results": [ {"id": "item1", "name": "Test Item 1", "type": "weapon"}, {"id": "item2", "name": "Test Item 2", "type": "armor"} ] } api = get_nexus_api() results = api.search_items("test", limit=10) assert len(results) == 2 assert results[0].id == "item1" assert results[0].name == "Test Item 1" mock_request.assert_called_once_with('search', {'q': 'test', 'limit': 10, 'type': 'item'}) @patch('core.nexus_api.NexusAPI._make_request') def test_search_mobs(self, mock_request, reset_singletons): """Test searching for mobs.""" mock_request.return_value = { "results": [ {"id": "mob1", "name": "Atrox", "type": "mob"} ] } api = get_nexus_api() results = api.search_mobs("atrox", limit=5) assert len(results) == 1 assert results[0].name == "Atrox" mock_request.assert_called_once_with('search', {'q': 'atrox', 'limit': 5, 'type': 'mob'}) @patch('core.nexus_api.NexusAPI._make_request') def test_search_all(self, mock_request, reset_singletons): """Test searching across all types.""" mock_request.return_value = { "results": [ {"id": "item1", "name": "Test Item", "type": "item"}, {"id": "mob1", "name": "Test Mob", "type": "mob"} ] } api = get_nexus_api() results = api.search_all("test", limit=20) assert len(results) == 2 mock_request.assert_called_once_with('search', {'q': 'test', 'limit': 20}) @patch('core.nexus_api.NexusAPI._make_request') def test_search_by_type_weapons(self, mock_request, reset_singletons): """Test searching by specific type (weapons).""" mock_request.return_value = [ {"id": "wp1", "name": "ArMatrix LP-35", "type": "weapon"} ] api = get_nexus_api() results = api.search_by_type("armatrix", "weapons", limit=10) assert len(results) == 1 assert results[0].name == "ArMatrix LP-35" @patch('core.nexus_api.NexusAPI._make_request') def test_search_by_type_mobs(self, mock_request, reset_singletons): """Test searching by specific type (mobs).""" mock_request.return_value = [ {"id": "mob1", "name": "Atrox", "type": "mob"} ] api = get_nexus_api() results = api.search_by_type("atrox", "mobs", limit=10) assert len(results) == 1 assert results[0].name == "Atrox" @patch('core.nexus_api.NexusAPI._make_request') def test_search_error_handling(self, mock_request, reset_singletons): """Test search error handling.""" mock_request.side_effect = Exception("Network error") api = get_nexus_api() results = api.search_items("test") # Should return empty list on error, not raise assert results == [] def test_search_limit_capping(self, reset_singletons): """Test that search limit is capped at 100.""" api = get_nexus_api() with patch.object(api, '_make_request') as mock_request: mock_request.return_value = {"results": []} api.search_items("test", limit=200) # Try to request 200 # Should be capped at 100 call_args = mock_request.call_args assert call_args[0][1]['limit'] == 100 @pytest.mark.unit class TestItemDetails: """Test item details retrieval.""" @patch('core.nexus_api.NexusAPI._make_request') def test_get_item_details_success(self, mock_request, reset_singletons): """Test successful item details retrieval.""" mock_request.return_value = { "id": "armatrix_lp-35", "name": "ArMatrix LP-35", "description": "A laser pistol", "category": "weapon", "weight": 2.5, "tt_value": 40.0, "decay": 0.015, "ammo_consumption": 131, "damage": 35.0, "range": 51.0, "accuracy": 20.0, "durability": 3600 } api = get_nexus_api() details = api.get_item_details("armatrix_lp-35") assert details is not None assert details.id == "armatrix_lp-35" assert details.name == "ArMatrix LP-35" assert details.tt_value == 40.0 assert details.damage == 35.0 @patch('core.nexus_api.NexusAPI._make_request') def test_get_item_details_not_found(self, mock_request, reset_singletons): """Test item details when not found.""" mock_request.return_value = {"error": "Item not found"} api = get_nexus_api() details = api.get_item_details("nonexistent") assert details is None @patch('core.nexus_api.NexusAPI._make_request') def test_get_item_details_error(self, mock_request, reset_singletons): """Test item details error handling.""" mock_request.side_effect = Exception("Network error") api = get_nexus_api() details = api.get_item_details("item1") # Should return None on error assert details is None @pytest.mark.unit class TestMarketData: """Test market data retrieval.""" @patch('core.nexus_api.NexusAPI._make_request') def test_get_market_data_success(self, mock_request, reset_singletons): """Test successful market data retrieval.""" mock_request.return_value = { "item_id": "armatrix_lp-35", "item_name": "ArMatrix LP-35", "current_markup": 110.5, "avg_markup_7d": 109.0, "avg_markup_30d": 108.5, "volume_24h": 50, "volume_7d": 350, "buy_orders": [{"price": 44.0, "quantity": 10}], "sell_orders": [{"price": 45.0, "quantity": 5}], "last_updated": "2024-01-01T12:00:00Z" } api = get_nexus_api() market = api.get_market_data("armatrix_lp-35") assert market is not None assert market.item_id == "armatrix_lp-35" assert market.current_markup == 110.5 assert market.volume_24h == 50 assert len(market.buy_orders) == 1 assert len(market.sell_orders) == 1 @patch('core.nexus_api.NexusAPI._make_request') def test_get_market_data_not_found(self, mock_request, reset_singletons): """Test market data when not found.""" mock_request.return_value = {"error": "Market data not found"} api = get_nexus_api() market = api.get_market_data("nonexistent") assert market is None @patch('core.nexus_api.NexusAPI._make_request') def test_get_market_data_timestamp_parsing(self, mock_request, reset_singletons): """Test market data timestamp parsing.""" mock_request.return_value = { "item_id": "item1", "last_updated": "2024-01-15T10:30:00Z" } api = get_nexus_api() market = api.get_market_data("item1") assert market.last_updated is not None assert isinstance(market.last_updated, datetime) @pytest.mark.unit class TestBatchMethods: """Test batch retrieval methods.""" @patch('core.nexus_api.NexusAPI.get_item_details') def test_get_items_batch(self, mock_details, reset_singletons): """Test batch item details retrieval.""" mock_details.side_effect = [ ItemDetails(id="item1", name="Item 1"), ItemDetails(id="item2", name="Item 2"), None # One item not found ] api = get_nexus_api() results = api.get_items_batch(["item1", "item2", "item3"]) assert len(results) == 3 assert results["item1"].name == "Item 1" assert results["item2"].name == "Item 2" assert results["item3"] is None @patch('core.nexus_api.NexusAPI.get_market_data') def test_get_market_batch(self, mock_market, reset_singletons): """Test batch market data retrieval.""" mock_market.side_effect = [ MarketData(item_id="item1", current_markup=110.0), MarketData(item_id="item2", current_markup=120.0) ] api = get_nexus_api() results = api.get_market_batch(["item1", "item2"]) assert len(results) == 2 assert results["item1"].current_markup == 110.0 @pytest.mark.unit class TestEntityDetails: """Test generic entity details retrieval.""" @patch('core.nexus_api.NexusAPI._make_request') def test_get_entity_details_mob(self, mock_request, reset_singletons): """Test getting mob entity details.""" mock_request.return_value = { "id": "atrox", "name": "Atrox", "level": 25, "threat": "medium" } api = get_nexus_api() details = api.get_entity_details("atrox", "mobs") assert details is not None assert details["name"] == "Atrox" @patch('core.nexus_api.NexusAPI._make_request') def test_get_entity_details_location(self, mock_request, reset_singletons): """Test getting location entity details.""" mock_request.return_value = { "id": "fort-izzuk", "name": "Fort Izzuk", "planet": "Calypso" } api = get_nexus_api() details = api.get_entity_details("fort-izzuk", "locations") assert details is not None assert details["name"] == "Fort Izzuk" @patch('core.nexus_api.NexusAPI._make_request') def test_get_entity_details_not_found(self, mock_request, reset_singletons): """Test entity details when not found.""" mock_request.return_value = {"error": "Not found"} api = get_nexus_api() details = api.get_entity_details("nonexistent", "mobs") assert details is None @pytest.mark.unit class TestConvenienceFunctions: """Test module-level convenience functions.""" @patch('core.nexus_api.NexusAPI.search_items') def test_search_items_convenience(self, mock_search, reset_singletons): """Test search_items convenience function.""" mock_search.return_value = [SearchResult(id="item1", name="Test", type="item")] results = search_items("test", limit=10) assert len(results) == 1 mock_search.assert_called_once_with("test", 10) @patch('core.nexus_api.NexusAPI.search_mobs') def test_search_mobs_convenience(self, mock_search, reset_singletons): """Test search_mobs convenience function.""" mock_search.return_value = [SearchResult(id="mob1", name="Atrox", type="mob")] results = search_mobs("atrox", limit=5) assert len(results) == 1 mock_search.assert_called_once_with("atrox", 5) @patch('core.nexus_api.NexusAPI.search_all') def test_search_all_convenience(self, mock_search, reset_singletons): """Test search_all convenience function.""" mock_search.return_value = [] results = search_all("test", limit=20) mock_search.assert_called_once_with("test", 20) @patch('core.nexus_api.NexusAPI.get_item_details') def test_get_item_details_convenience(self, mock_details, reset_singletons): """Test get_item_details convenience function.""" mock_details.return_value = ItemDetails(id="item1", name="Test") result = get_item_details("item1") assert result.name == "Test" mock_details.assert_called_once_with("item1") @patch('core.nexus_api.NexusAPI.get_market_data') def test_get_market_data_convenience(self, mock_market, reset_singletons): """Test get_market_data convenience function.""" mock_market.return_value = MarketData(item_id="item1", current_markup=110.0) result = get_market_data("item1") assert result.current_markup == 110.0 mock_market.assert_called_once_with("item1") @pytest.mark.unit class TestAvailability: """Test API availability checking.""" def test_is_available(self, reset_singletons): """Test availability check.""" api = get_nexus_api() assert api.is_available() is True # Simulate unavailable api._available = False assert api.is_available() is False