""" 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""" @patch('app.nosql.db') @pytest.mark.asyncio 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" @patch('app.nosql.db') @pytest.mark.asyncio 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" @patch('app.nosql.db') @pytest.mark.asyncio 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 @patch('app.nosql.db') @pytest.mark.asyncio 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" @patch('app.nosql.redis_client') @patch('app.nosql.db') @pytest.mark.asyncio 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" @patch('app.services.merchant.execute_query') @pytest.mark.asyncio 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 @patch('app.repositories.db_repository.execute_query') @pytest.mark.asyncio 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"] == [] @patch('app.repositories.db_repository.execute_query') @pytest.mark.asyncio 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""" @pytest.mark.asyncio 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" @pytest.mark.asyncio 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" @pytest.mark.asyncio 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"] == "" @pytest.mark.asyncio 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) @pytest.mark.asyncio 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""" @pytest.mark.asyncio 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 @pytest.mark.asyncio 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 @pytest.mark.asyncio 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""" @pytest.mark.asyncio 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 @pytest.mark.asyncio 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 @pytest.mark.asyncio 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 @pytest.mark.asyncio 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 @pytest.mark.asyncio 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) @pytest.mark.asyncio 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 @pytest.mark.asyncio 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 @pytest.mark.asyncio 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""" @patch('app.services.merchant.search_merchants') @patch('app.services.advanced_nlp.advanced_nlp_pipeline') @pytest.mark.asyncio 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" @patch('app.repositories.cache_repository.cache_search_results') @patch('app.services.merchant.search_merchants') @pytest.mark.asyncio 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 @pytest.mark.asyncio 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""" @pytest.mark.asyncio 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) @pytest.mark.asyncio 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