""" Unit tests for Dashboard REST API endpoints. """ import pytest import tempfile import os import sys import json # Add project root to path project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, project_root) from flask import Flask from src.database import Database # Import api module directly to avoid circular imports via dashboard/__init__.py import importlib.util spec = importlib.util.spec_from_file_location( "dashboard_api", os.path.join(project_root, "dashboard", "api.py") ) dashboard_api = importlib.util.module_from_spec(spec) sys.modules["dashboard_api"] = dashboard_api spec.loader.exec_module(dashboard_api) api_bp = dashboard_api.api_bp @pytest.fixture def app(): """Create test Flask app""" app = Flask(__name__) app.config['TESTING'] = True app.register_blueprint(api_bp, url_prefix='/api') # Use temp database with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f: db_path = f.name # Monkey-patch the database import src.database as db_module db_module._db = Database(db_path) # Also reset the favorites manager so it picks up the new database import src.favorites as fav_module fav_module._favorites = None # Reset so it gets recreated with new db yield app # Cleanup os.unlink(db_path) @pytest.fixture def client(app): """Create test client""" return app.test_client() class TestStatsEndpoints: """Tests for stats and health endpoints""" def test_get_stats(self, client): """Test /api/stats endpoint""" response = client.get('/api/stats') assert response.status_code == 200 data = json.loads(response.data) assert 'total_products' in data assert 'in_stock_count' in data assert 'new_drops_today' in data def test_get_health(self, client): """Test /api/health endpoint""" response = client.get('/api/health') assert response.status_code == 200 data = json.loads(response.data) assert data['status'] == 'ok' class TestProductsEndpoints: """Tests for product endpoints""" def test_get_products_empty(self, client): """Test getting products when empty""" response = client.get('/api/products') assert response.status_code == 200 data = json.loads(response.data) assert 'products' in data assert 'total' in data def test_get_products_with_filters(self, client): """Test product filtering""" response = client.get('/api/products?site=pokemoncenter&in_stock=true') assert response.status_code == 200 def test_get_product_not_found(self, client): """Test getting non-existent product""" response = client.get('/api/products/99999') assert response.status_code == 404 class TestEventsEndpoints: """Tests for events endpoints""" def test_get_events(self, client): """Test /api/events endpoint""" response = client.get('/api/events') assert response.status_code == 200 data = json.loads(response.data) assert 'events' in data def test_get_events_with_type_filter(self, client): """Test filtering events by type""" response = client.get('/api/events?type=restock&type=new_drop') assert response.status_code == 200 class TestAnalyticsEndpoints: """Tests for analytics endpoints""" def test_get_drop_timing(self, client): """Test /api/analytics/drops endpoint""" response = client.get('/api/analytics/drops') assert response.status_code == 200 data = json.loads(response.data) assert 'drop_timing' in data def test_get_stock_duration(self, client): """Test /api/analytics/stock endpoint""" response = client.get('/api/analytics/stock') assert response.status_code == 200 data = json.loads(response.data) assert 'stock_duration' in data def test_get_selling_rates(self, client): """Test /api/analytics/selling-rates endpoint""" response = client.get('/api/analytics/selling-rates') assert response.status_code == 200 data = json.loads(response.data) assert 'products' in data def test_get_site_stats(self, client): """Test /api/analytics/sites endpoint""" response = client.get('/api/analytics/sites') assert response.status_code == 200 data = json.loads(response.data) assert 'sites' in data class TestExtensionSyncEndpoint: """Tests for Chrome extension sync endpoint""" def test_sync_empty_data(self, client): """Test syncing with no data returns error""" response = client.post('/api/extension/sync', content_type='application/json') assert response.status_code == 400 def test_sync_skus(self, client): """Test syncing SKUs from extension""" data = { 'skus': ['699-17113', '191-85953', '100-12345'], 'products': [], 'events': [] } response = client.post('/api/extension/sync', data=json.dumps(data), content_type='application/json') assert response.status_code == 200 result = json.loads(response.data) assert result['success'] is True assert result['total_skus'] == 3 assert result['new_skus_added'] == 3 def test_sync_products(self, client): """Test syncing products from extension""" data = { 'skus': [], 'products': [ { 'url': 'https://www.pokemoncenter.com/product/699-17113', 'name': 'Pokemon ETB Prismatic Evolutions', 'price': '$49.99', 'inStock': True, 'imageUrl': 'https://example.com/image.jpg', 'productId': '699-17113' } ], 'events': [] } response = client.post('/api/extension/sync', data=json.dumps(data), content_type='application/json') assert response.status_code == 200 result = json.loads(response.data) assert result['success'] is True assert result['total_products'] >= 1 def test_sync_events(self, client): """Test syncing events (restocks, drops) from extension""" # First sync a product product_data = { 'skus': [], 'products': [ { 'url': 'https://www.pokemoncenter.com/product/test-123', 'name': 'Test Product', 'price': '$29.99', 'inStock': True } ], 'events': [] } client.post('/api/extension/sync', data=json.dumps(product_data), content_type='application/json') # Now sync events event_data = { 'skus': [], 'products': [], 'events': [ { 'type': 'restock', 'url': 'https://www.pokemoncenter.com/product/test-123', 'name': 'Test Product', 'price': '$29.99', 'timestamp': '2024-01-15T10:00:00Z' }, { 'type': 'out_of_stock', 'url': 'https://www.pokemoncenter.com/product/test-123', 'name': 'Test Product', 'timestamp': '2024-01-15T10:30:00Z' } ] } response = client.post('/api/extension/sync', data=json.dumps(event_data), content_type='application/json') assert response.status_code == 200 result = json.loads(response.data) assert result['success'] is True assert result['events_processed'] == 2 def test_sync_price_change_event(self, client): """Test syncing price change events""" # First create the product client.post('/api/extension/sync', data=json.dumps({ 'products': [{ 'url': 'https://www.pokemoncenter.com/product/price-test', 'name': 'Price Test Product', 'price': '$39.99', 'inStock': True }] }), content_type='application/json') # Now send price change event event_data = { 'events': [ { 'type': 'price_change', 'url': 'https://www.pokemoncenter.com/product/price-test', 'oldPrice': '$39.99', 'newPrice': '$34.99', 'timestamp': '2024-01-15T12:00:00Z' } ] } response = client.post('/api/extension/sync', data=json.dumps(event_data), content_type='application/json') assert response.status_code == 200 result = json.loads(response.data) assert result['events_processed'] == 1 class TestExtensionDataEndpoints: """Tests for extension data retrieval endpoints""" def test_get_extension_skus(self, client): """Test getting extension SKUs""" response = client.get('/api/extension/skus') assert response.status_code == 200 data = json.loads(response.data) assert 'skus' in data assert 'total' in data def test_get_extension_products(self, client): """Test getting extension products""" response = client.get('/api/extension/products') assert response.status_code == 200 data = json.loads(response.data) assert 'products' in data assert 'total' in data def test_get_extension_stats(self, client): """Test getting extension stats""" response = client.get('/api/extension/stats') assert response.status_code == 200 data = json.loads(response.data) assert 'total_skus' in data assert 'total_products' in data assert 'source' in data assert data['source'] == 'chrome_extension' def test_clear_extension_data(self, client): """Test clearing extension data""" response = client.post('/api/extension/clear') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True class TestFavoritesEndpoints: """Tests for favorites CRUD endpoints""" def test_add_favorite(self, client): """Test adding a favorite""" data = { 'type': 'product', 'value': 'https://example.com/fav-product', 'display_name': 'My Favorite ETB', 'priority': 'high' } response = client.post('/api/favorites', data=json.dumps(data), content_type='application/json') assert response.status_code == 201 result = json.loads(response.data) assert result['success'] is True assert 'id' in result def test_get_favorites(self, client): """Test getting all favorites""" response = client.get('/api/favorites') assert response.status_code == 200 data = json.loads(response.data) assert 'favorites' in data def test_delete_favorite(self, client): """Test deleting a favorite""" # First add one add_response = client.post('/api/favorites', data=json.dumps({ 'type': 'category', 'value': 'ETB' }), content_type='application/json') fav_id = json.loads(add_response.data)['id'] # Now delete it response = client.delete(f'/api/favorites/{fav_id}') assert response.status_code == 200 if __name__ == "__main__": pytest.main([__file__, "-v"])