Spaces:
Sleeping
Sleeping
| """ | |
| Regression tests for API endpoints | |
| """ | |
| import pytest | |
| import json | |
| from fastapi.testclient import TestClient | |
| from unittest.mock import patch, AsyncMock | |
| from httpx import AsyncClient | |
| class TestHealthEndpoints: | |
| """Test health check endpoints""" | |
| def test_health_check(self, client: TestClient): | |
| """Test basic health check endpoint""" | |
| response = client.get("/health") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "healthy" | |
| assert "timestamp" in data | |
| assert data["service"] == "merchant-api" | |
| assert data["version"] == "1.0.0" | |
| def test_readiness_check_healthy(self, mock_redis, mock_mongo, client: TestClient): | |
| """Test readiness check when databases are healthy""" | |
| mock_mongo.return_value = True | |
| mock_redis.return_value = True | |
| response = client.get("/ready") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "ready" | |
| assert data["databases"]["mongodb"] == "healthy" | |
| assert data["databases"]["redis"] == "healthy" | |
| def test_readiness_check_unhealthy(self, mock_redis, mock_mongo, client: TestClient): | |
| """Test readiness check when databases are unhealthy""" | |
| mock_mongo.return_value = False | |
| mock_redis.return_value = True | |
| response = client.get("/ready") | |
| assert response.status_code == 503 | |
| data = response.json() | |
| assert "not_ready" in data["detail"]["status"] | |
| class TestMerchantEndpoints: | |
| """Test merchant-related endpoints""" | |
| def test_get_merchants_success(self, mock_get_merchants, client: TestClient, sample_merchant_data): | |
| """Test successful merchant retrieval""" | |
| mock_get_merchants.return_value = [sample_merchant_data] | |
| response = client.get("/api/v1/merchants/") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data) == 1 | |
| assert data[0]["name"] == "Test Hair Salon" | |
| assert data[0]["category"] == "salon" | |
| def test_get_merchant_by_id_success(self, mock_get_merchant, client: TestClient, sample_merchant_data): | |
| """Test successful merchant retrieval by ID""" | |
| mock_get_merchant.return_value = sample_merchant_data | |
| response = client.get("/api/v1/merchants/test_merchant_123") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["_id"] == "test_merchant_123" | |
| assert data["name"] == "Test Hair Salon" | |
| def test_get_merchant_by_id_not_found(self, mock_get_merchant, client: TestClient): | |
| """Test merchant not found scenario""" | |
| mock_get_merchant.return_value = None | |
| response = client.get("/api/v1/merchants/nonexistent_id") | |
| assert response.status_code == 404 | |
| def test_search_merchants_with_location(self, mock_search, client: TestClient, sample_merchant_data): | |
| """Test merchant search with location parameters""" | |
| mock_search.return_value = [sample_merchant_data] | |
| response = client.get("/api/v1/merchants/search", params={ | |
| "latitude": 40.7128, | |
| "longitude": -74.0060, | |
| "radius": 5000, | |
| "category": "salon" | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert len(data) == 1 | |
| assert data[0]["category"] == "salon" | |
| def test_search_merchants_invalid_coordinates(self, client: TestClient): | |
| """Test merchant search with invalid coordinates""" | |
| response = client.get("/api/v1/merchants/search", params={ | |
| "latitude": 200, # Invalid latitude | |
| "longitude": -74.0060, | |
| "radius": 5000 | |
| }) | |
| assert response.status_code == 400 | |
| class TestHelperEndpoints: | |
| """Test helper service endpoints""" | |
| def test_process_free_text_success(self, mock_process, client: TestClient): | |
| """Test successful free text processing""" | |
| mock_process.return_value = { | |
| "query": "find a hair salon", | |
| "extracted_keywords": ["hair", "salon"], | |
| "suggested_category": "salon", | |
| "search_parameters": {"category": "salon"} | |
| } | |
| response = client.post("/api/v1/helpers/process-text", json={ | |
| "text": "find a hair salon", | |
| "latitude": 40.7128, | |
| "longitude": -74.0060 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["suggested_category"] == "salon" | |
| def test_process_free_text_empty_input(self, client: TestClient): | |
| """Test free text processing with empty input""" | |
| response = client.post("/api/v1/helpers/process-text", json={ | |
| "text": "", | |
| "latitude": 40.7128, | |
| "longitude": -74.0060 | |
| }) | |
| assert response.status_code == 400 | |
| def test_process_free_text_too_long(self, client: TestClient): | |
| """Test free text processing with input too long""" | |
| long_text = "a" * 1001 # Assuming 1000 char limit | |
| response = client.post("/api/v1/helpers/process-text", json={ | |
| "text": long_text, | |
| "latitude": 40.7128, | |
| "longitude": -74.0060 | |
| }) | |
| assert response.status_code == 400 | |
| class TestNLPEndpoints: | |
| """Test NLP demo endpoints""" | |
| def test_analyze_query_success(self, mock_pipeline, client: TestClient, mock_nlp_pipeline): | |
| """Test successful query analysis""" | |
| mock_pipeline.process_query = mock_nlp_pipeline.process_query | |
| response = client.post("/api/v1/nlp/analyze-query", params={ | |
| "query": "find the best hair salon near me", | |
| "latitude": 40.7128, | |
| "longitude": -74.0060 | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "success" | |
| assert "analysis" in data | |
| def test_analyze_query_empty_input(self, client: TestClient): | |
| """Test query analysis with empty input""" | |
| response = client.post("/api/v1/nlp/analyze-query", params={ | |
| "query": "" | |
| }) | |
| assert response.status_code == 400 | |
| def test_get_supported_intents(self, client: TestClient): | |
| """Test getting supported intents""" | |
| response = client.get("/api/v1/nlp/supported-intents") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "success" | |
| assert "supported_intents" in data | |
| assert "SEARCH_SERVICE" in data["supported_intents"] | |
| assert "FILTER_QUALITY" in data["supported_intents"] | |
| def test_get_supported_entities(self, client: TestClient): | |
| """Test getting supported entities""" | |
| response = client.get("/api/v1/nlp/supported-entities") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "success" | |
| assert "supported_entities" in data | |
| assert "services" in data["supported_entities"] | |
| assert "amenities" in data["supported_entities"] | |
| class TestPerformanceEndpoints: | |
| """Test performance monitoring endpoints""" | |
| def test_get_performance_report(self, mock_report, client: TestClient): | |
| """Test performance report endpoint""" | |
| mock_report.return_value = { | |
| "metrics": { | |
| "total_queries": 100, | |
| "average_time": 0.5, | |
| "slow_queries": [] | |
| } | |
| } | |
| response = client.get("/api/v1/performance/report") | |
| assert response.status_code == 200 | |
| def test_get_metrics(self, client: TestClient): | |
| """Test metrics endpoint""" | |
| response = client.get("/metrics") | |
| # Should return metrics even if some components fail | |
| assert response.status_code in [200, 500] | |
| class TestSecurityEndpoints: | |
| """Test security-related functionality""" | |
| def test_cors_headers(self, client: TestClient): | |
| """Test CORS headers are properly set""" | |
| response = client.options("/api/v1/merchants/", headers={ | |
| "Origin": "http://localhost:3000", | |
| "Access-Control-Request-Method": "GET" | |
| }) | |
| # Should allow the request | |
| assert response.status_code in [200, 204] | |
| def test_invalid_origin_blocked(self, client: TestClient): | |
| """Test that invalid origins are blocked""" | |
| response = client.get("/api/v1/merchants/", headers={ | |
| "Origin": "http://malicious-site.com" | |
| }) | |
| # Should still work but without CORS headers for invalid origin | |
| assert response.status_code == 200 | |
| def test_request_size_limit(self, client: TestClient): | |
| """Test request size limits""" | |
| large_payload = {"data": "x" * (11 * 1024 * 1024)} # 11MB | |
| response = client.post("/api/v1/helpers/process-text", json=large_payload) | |
| # Should be rejected due to size limit | |
| assert response.status_code in [413, 400] | |
| class TestErrorHandling: | |
| """Test error handling across endpoints""" | |
| def test_404_for_nonexistent_endpoint(self, client: TestClient): | |
| """Test 404 for non-existent endpoints""" | |
| response = client.get("/api/v1/nonexistent") | |
| assert response.status_code == 404 | |
| def test_405_for_wrong_method(self, client: TestClient): | |
| """Test 405 for wrong HTTP method""" | |
| response = client.delete("/api/v1/merchants/") | |
| assert response.status_code == 405 | |
| def test_500_error_handling(self, mock_get_merchants, client: TestClient): | |
| """Test 500 error handling""" | |
| mock_get_merchants.side_effect = Exception("Database error") | |
| response = client.get("/api/v1/merchants/") | |
| assert response.status_code == 500 | |
| def test_malformed_json(self, client: TestClient): | |
| """Test handling of malformed JSON""" | |
| response = client.post( | |
| "/api/v1/helpers/process-text", | |
| data="invalid json", | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| assert response.status_code == 422 | |
| class TestAsyncEndpoints: | |
| """Test async endpoint functionality""" | |
| async def test_async_client_health_check(self, async_client: AsyncClient): | |
| """Test health check with async client""" | |
| response = await async_client.get("/health") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "healthy" | |
| async def test_async_nlp_processing(self, mock_pipeline, async_client: AsyncClient, mock_nlp_pipeline): | |
| """Test async NLP processing""" | |
| mock_pipeline.process_query = mock_nlp_pipeline.process_query | |
| response = await async_client.post("/api/v1/nlp/analyze-query", params={ | |
| "query": "find a spa" | |
| }) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert data["status"] == "success" |