Spaces:
Running
Running
| """ | |
| Regression tests for service layer components | |
| """ | |
| import pytest | |
| from unittest.mock import AsyncMock, MagicMock, patch | |
| from typing import Dict, Any | |
| class TestMerchantService: | |
| """Test merchant service functionality""" | |
| async def test_get_merchants_success(self, mock_db, sample_merchant_data): | |
| """Test successful merchant retrieval""" | |
| from app.services.merchant import get_merchants | |
| # Mock the MongoDB collection | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find.return_value.limit.return_value.skip.return_value.to_list.return_value = [sample_merchant_data] | |
| result = await get_merchants(limit=10, skip=0) | |
| assert len(result) == 1 | |
| assert result[0]["name"] == "Test Hair Salon" | |
| async def test_get_merchant_by_id_success(self, mock_db, sample_merchant_data): | |
| """Test successful merchant retrieval by ID""" | |
| from app.services.merchant import get_merchant_by_id | |
| # Mock the MongoDB collection | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find_one.return_value = sample_merchant_data | |
| result = await get_merchant_by_id("test_merchant_123") | |
| assert result["_id"] == "test_merchant_123" | |
| assert result["name"] == "Test Hair Salon" | |
| async def test_get_merchant_by_id_not_found(self, mock_db): | |
| """Test merchant not found scenario""" | |
| from app.services.merchant import get_merchant_by_id | |
| # Mock the MongoDB collection | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find_one.return_value = None | |
| result = await get_merchant_by_id("nonexistent_id") | |
| assert result is None | |
| async def test_search_merchants_with_location(self, mock_db, sample_merchant_data): | |
| """Test merchant search with location parameters""" | |
| from app.services.merchant import search_merchants | |
| # Mock the MongoDB collection | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find.return_value.limit.return_value.to_list.return_value = [sample_merchant_data] | |
| result = await search_merchants( | |
| latitude=40.7128, | |
| longitude=-74.0060, | |
| radius=5000, | |
| category="salon" | |
| ) | |
| assert len(result) == 1 | |
| assert result[0]["category"] == "salon" | |
| async def test_merchant_caching(self, mock_db, mock_redis, sample_merchant_data): | |
| """Test merchant data caching""" | |
| from app.services.merchant import get_merchants | |
| # Mock the MongoDB collection | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find.return_value.limit.return_value.skip.return_value.to_list.return_value = [sample_merchant_data] | |
| # Mock Redis | |
| mock_redis.get.return_value = None # Cache miss | |
| mock_redis.set.return_value = True | |
| result = await get_merchants(limit=10, skip=0) | |
| assert len(result) == 1 | |
| assert result[0]["name"] == "Test Hair Salon" | |
| async def test_fetch_favorite_merchants_success(self, mock_execute_query): | |
| """Test successful fetch of favorite merchants""" | |
| from app.services.merchant import fetch_favorite_merchants | |
| # Create merchant data that matches our projection fields | |
| merchant_data = { | |
| "merchant_id": "test_merchant_123", | |
| "business_name": "Test Hair Salon", | |
| "business_url": "test-hair-salon", | |
| "description": "A great hair salon", | |
| "city": "Chennai", | |
| "merchant_category": "salon", | |
| "merchant_subcategory": "hair_salon", | |
| "average_rating": 4.5, | |
| "location_id": "IN-SOUTH" | |
| } | |
| # Mock the favorites query result | |
| mock_execute_query.return_value = [merchant_data] | |
| result = await fetch_favorite_merchants("customer_123", "IN-SOUTH", limit=5) | |
| print(f"Result: {result}") | |
| print(f"Type of result: {type(result)}") | |
| if isinstance(result, dict): | |
| print(f"Keys in result: {result.keys()}") | |
| if "favorite_merchants" in result: | |
| print(f"Favorite merchants count: {len(result['favorite_merchants'])}") | |
| print(f"Favorite merchants: {result['favorite_merchants']}") | |
| assert "favorite_merchants" in result | |
| assert "recent_merchants" in result | |
| assert len(result["favorite_merchants"]) == 1 | |
| assert len(result["recent_merchants"]) == 0 # Currently returns empty | |
| assert result["favorite_merchants"][0]["business_name"] == "Test Hair Salon" | |
| assert result["favorite_merchants"][0]["merchant_id"] == "test_merchant_123" | |
| # Verify the query was called with correct collection | |
| mock_execute_query.assert_called_once() | |
| args, kwargs = mock_execute_query.call_args | |
| assert args[0] == "favourites" # Should query favourites collection | |
| async def test_fetch_favorite_merchants_empty_result(self, mock_execute_query): | |
| """Test fetch favorite merchants with no favorites""" | |
| from app.services.merchant import fetch_favorite_merchants | |
| # Mock empty result | |
| mock_execute_query.return_value = [] | |
| result = await fetch_favorite_merchants("customer_456", "IN-SOUTH", limit=5) | |
| assert result["favorite_merchants"] == [] | |
| assert result["recent_merchants"] == [] | |
| async def test_fetch_favorite_merchants_error_handling(self, mock_execute_query): | |
| """Test error handling in fetch favorite merchants""" | |
| from app.services.merchant import fetch_favorite_merchants | |
| # Mock database error | |
| mock_execute_query.side_effect = Exception("Database connection error") | |
| result = await fetch_favorite_merchants("customer_789", "IN-SOUTH", limit=5) | |
| # Should return empty lists on error | |
| assert result["favorite_merchants"] == [] | |
| assert result["recent_merchants"] == [] | |
| class TestHelperService: | |
| """Test helper service functionality""" | |
| async def test_process_free_text_basic(self): | |
| """Test basic free text processing""" | |
| from app.services.helper import process_free_text | |
| result = await process_free_text("find a hair salon", 40.7128, -74.0060) | |
| assert "query" in result | |
| assert "extracted_keywords" in result | |
| assert result["query"] == "find a hair salon" | |
| async def test_process_free_text_with_location(self): | |
| """Test free text processing with location""" | |
| from app.services.helper import process_free_text | |
| result = await process_free_text("salon near me", 40.7128, -74.0060) | |
| assert "location" in result or "search_parameters" in result | |
| assert result["query"] == "salon near me" | |
| async def test_process_free_text_empty_input(self): | |
| """Test free text processing with empty input""" | |
| from app.services.helper import process_free_text | |
| result = await process_free_text("", 40.7128, -74.0060) | |
| # Should handle empty input gracefully | |
| assert "query" in result | |
| assert result["query"] == "" | |
| async def test_extract_keywords(self): | |
| """Test keyword extraction functionality""" | |
| from app.services.helper import extract_keywords | |
| keywords = extract_keywords("find the best hair salon with parking") | |
| assert isinstance(keywords, list) | |
| assert len(keywords) > 0 | |
| # Should extract relevant keywords | |
| assert any("hair" in kw.lower() or "salon" in kw.lower() for kw in keywords) | |
| async def test_suggest_category(self): | |
| """Test category suggestion functionality""" | |
| from app.services.helper import suggest_category | |
| category = suggest_category("hair salon") | |
| assert category is not None | |
| assert category in ["salon", "beauty", "hair_salon"] | |
| class TestSearchHelpers: | |
| """Test search helper functionality""" | |
| async def test_build_search_query(self): | |
| """Test search query building""" | |
| from app.services.search_helpers import build_search_query | |
| params = { | |
| "category": "salon", | |
| "latitude": 40.7128, | |
| "longitude": -74.0060, | |
| "radius": 5000 | |
| } | |
| query = build_search_query(params) | |
| assert isinstance(query, dict) | |
| assert "location" in query or "category" in query | |
| async def test_apply_filters(self): | |
| """Test filter application""" | |
| from app.services.search_helpers import apply_filters | |
| merchants = [ | |
| {"name": "Salon A", "average_rating": 4.5, "price_range": "$$"}, | |
| {"name": "Salon B", "average_rating": 3.5, "price_range": "$"}, | |
| {"name": "Salon C", "average_rating": 4.8, "price_range": "$$$"} | |
| ] | |
| filters = {"min_rating": 4.0, "max_price_range": "$$"} | |
| filtered = apply_filters(merchants, filters) | |
| assert len(filtered) <= len(merchants) | |
| # Should only include merchants meeting criteria | |
| for merchant in filtered: | |
| assert merchant["average_rating"] >= 4.0 | |
| async def test_sort_results(self): | |
| """Test result sorting""" | |
| from app.services.search_helpers import sort_results | |
| merchants = [ | |
| {"name": "Salon A", "average_rating": 4.5, "distance": 1000}, | |
| {"name": "Salon B", "average_rating": 4.8, "distance": 2000}, | |
| {"name": "Salon C", "average_rating": 4.2, "distance": 500} | |
| ] | |
| # Sort by rating | |
| sorted_by_rating = sort_results(merchants, "rating") | |
| assert sorted_by_rating[0]["average_rating"] >= sorted_by_rating[1]["average_rating"] | |
| # Sort by distance | |
| sorted_by_distance = sort_results(merchants, "distance") | |
| assert sorted_by_distance[0]["distance"] <= sorted_by_distance[1]["distance"] | |
| class TestAdvancedNLPService: | |
| """Test advanced NLP service functionality""" | |
| async def test_nlp_pipeline_initialization(self): | |
| """Test NLP pipeline initialization""" | |
| from app.services.advanced_nlp import AdvancedNLPPipeline | |
| pipeline = AdvancedNLPPipeline() | |
| assert pipeline.intent_classifier is not None | |
| assert pipeline.entity_extractor is not None | |
| assert pipeline.semantic_matcher is not None | |
| assert pipeline.context_processor is not None | |
| async def test_intent_classification(self): | |
| """Test intent classification""" | |
| from app.services.advanced_nlp import IntentClassifier | |
| classifier = IntentClassifier() | |
| # Test search intent | |
| intent, confidence = classifier.get_primary_intent("find a hair salon") | |
| assert intent == "SEARCH_SERVICE" | |
| assert confidence > 0.0 | |
| # Test quality filter intent | |
| intent, confidence = classifier.get_primary_intent("best salon in town") | |
| assert intent == "FILTER_QUALITY" | |
| assert confidence > 0.0 | |
| async def test_entity_extraction(self): | |
| """Test entity extraction""" | |
| from app.services.advanced_nlp import BusinessEntityExtractor | |
| extractor = BusinessEntityExtractor() | |
| entities = extractor.extract_entities("hair salon with parking near me") | |
| assert isinstance(entities, dict) | |
| assert "service_types" in entities or "amenities" in entities | |
| async def test_semantic_matching(self): | |
| """Test semantic matching""" | |
| from app.services.advanced_nlp import SemanticMatcher | |
| matcher = SemanticMatcher() | |
| matches = matcher.find_similar_services("hair salon") | |
| assert isinstance(matches, list) | |
| assert len(matches) > 0 | |
| # Should return tuples of (service, similarity_score) | |
| for match in matches: | |
| assert len(match) == 2 | |
| assert isinstance(match[1], float) | |
| assert 0.0 <= match[1] <= 1.0 | |
| async def test_context_processing(self): | |
| """Test context-aware processing""" | |
| from app.services.advanced_nlp import ContextAwareProcessor | |
| processor = ContextAwareProcessor() | |
| result = await processor.process_with_context( | |
| "spa treatment", | |
| {"service_categories": ["spa"]}, | |
| [("spa", 0.9)] | |
| ) | |
| assert isinstance(result, dict) | |
| async def test_complete_nlp_pipeline(self): | |
| """Test complete NLP pipeline processing""" | |
| from app.services.advanced_nlp import AdvancedNLPPipeline | |
| pipeline = AdvancedNLPPipeline() | |
| result = await pipeline.process_query("find the best hair salon near me") | |
| assert "query" in result | |
| assert "primary_intent" in result | |
| assert "entities" in result | |
| assert "similar_services" in result | |
| assert "search_parameters" in result | |
| assert "processing_time" in result | |
| async def test_nlp_caching(self): | |
| """Test NLP result caching""" | |
| from app.services.advanced_nlp import AsyncNLPProcessor | |
| processor = AsyncNLPProcessor() | |
| def dummy_processor(text): | |
| return {"processed": text.upper()} | |
| # First call | |
| result1 = await processor.process_async("test", dummy_processor) | |
| # Second call should use cache | |
| result2 = await processor.process_async("test", dummy_processor) | |
| assert result1 == result2 | |
| async def test_nlp_error_handling(self): | |
| """Test NLP error handling""" | |
| from app.services.advanced_nlp import AdvancedNLPPipeline | |
| pipeline = AdvancedNLPPipeline() | |
| # Test with empty query | |
| result = await pipeline.process_query("") | |
| assert "query" in result | |
| assert result["query"] == "" | |
| # Should handle gracefully without crashing | |
| class TestServiceIntegration: | |
| """Test service integration scenarios""" | |
| async def test_nlp_to_merchant_search_integration(self, mock_nlp, mock_search, sample_merchant_data): | |
| """Test integration between NLP processing and merchant search""" | |
| # Mock NLP pipeline response | |
| mock_nlp.process_query.return_value = { | |
| "search_parameters": { | |
| "merchant_category": "salon", | |
| "radius": 5000, | |
| "amenities": ["parking"] | |
| } | |
| } | |
| # Mock merchant search response | |
| mock_search.return_value = [sample_merchant_data] | |
| # Simulate the integration | |
| nlp_result = await mock_nlp.process_query("salon with parking") | |
| search_params = nlp_result["search_parameters"] | |
| merchants = await mock_search(**search_params) | |
| assert len(merchants) == 1 | |
| assert merchants[0]["category"] == "salon" | |
| async def test_search_result_caching(self, mock_search, mock_cache, sample_merchant_data): | |
| """Test search result caching integration""" | |
| mock_search.return_value = [sample_merchant_data] | |
| # Perform search | |
| results = await mock_search(category="salon") | |
| # Cache should be called | |
| mock_cache.assert_called_once() | |
| assert len(results) == 1 | |
| async def test_error_propagation(self): | |
| """Test error propagation between services""" | |
| from app.services.merchant import get_merchant_by_id | |
| with patch('app.nosql.db') as mock_db: | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find_one.side_effect = Exception("Database connection failed") | |
| with pytest.raises(Exception) as exc_info: | |
| await get_merchant_by_id("test_id") | |
| assert "Database connection failed" in str(exc_info.value) | |
| class TestServicePerformance: | |
| """Test service performance characteristics""" | |
| async def test_concurrent_merchant_requests(self, sample_merchant_data): | |
| """Test concurrent merchant service requests""" | |
| import asyncio | |
| from app.services.merchant import get_merchant_by_id | |
| with patch('app.nosql.db') as mock_db: | |
| mock_collection = AsyncMock() | |
| mock_db.__getitem__.return_value = mock_collection | |
| mock_collection.find_one.return_value = sample_merchant_data | |
| # Create multiple concurrent requests | |
| tasks = [ | |
| get_merchant_by_id(f"merchant_{i}") | |
| for i in range(10) | |
| ] | |
| results = await asyncio.gather(*tasks) | |
| assert len(results) == 10 | |
| assert all(result["name"] == "Test Hair Salon" for result in results) | |
| async def test_nlp_processing_performance(self): | |
| """Test NLP processing performance""" | |
| import time | |
| from app.services.advanced_nlp import AdvancedNLPPipeline | |
| pipeline = AdvancedNLPPipeline() | |
| start_time = time.time() | |
| result = await pipeline.process_query("find a hair salon") | |
| processing_time = time.time() - start_time | |
| # Should process within reasonable time | |
| assert processing_time < 2.0 # 2 seconds max | |
| assert "processing_time" in result | |
| assert result["processing_time"] > 0 |