# services/sales/tests/unit/test_services.py """ Unit tests for Sales Service """ import pytest from datetime import datetime, timezone from decimal import Decimal from unittest.mock import AsyncMock, patch from uuid import uuid4 from app.services.sales_service import SalesService from app.schemas.sales import SalesDataCreate, SalesDataUpdate, SalesDataQuery @pytest.mark.asyncio class TestSalesService: """Test Sales Service business logic""" @pytest.fixture def sales_service(self): """Create sales service instance""" return SalesService() async def test_create_sales_record_success(self, sales_service, sample_tenant_id, sample_sales_data): """Test successful sales record creation""" with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_record = AsyncMock() mock_record.id = uuid4() mock_record.inventory_product_id = sample_sales_data.inventory_product_id mock_repository.create_sales_record.return_value = mock_record with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.create_sales_record( sample_sales_data, sample_tenant_id ) assert result is not None assert result.id is not None mock_repository.create_sales_record.assert_called_once_with(sample_sales_data, sample_tenant_id) async def test_create_sales_record_validation_error(self, sales_service, sample_tenant_id): """Test sales record creation with validation error""" # Create invalid sales data (future date) invalid_data = SalesDataCreate( date=datetime(2030, 1, 1, tzinfo=timezone.utc), # Future date inventory_product_id="550e8400-e29b-41d4-a716-446655440000", product_name="Test Product", quantity_sold=1, revenue=Decimal("5.00") ) with pytest.raises(ValueError, match="Sales date cannot be in the future"): await sales_service.create_sales_record(invalid_data, sample_tenant_id) async def test_update_sales_record(self, sales_service, sample_tenant_id): """Test updating a sales record""" record_id = uuid4() update_data = SalesDataUpdate( inventory_product_id="550e8400-e29b-41d4-a716-446655440999", product_name="Updated Product", quantity_sold=10 ) with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() # Mock existing record mock_existing = AsyncMock() mock_existing.tenant_id = sample_tenant_id mock_repository.get_by_id.return_value = mock_existing # Mock updated record mock_updated = AsyncMock() mock_updated.inventory_product_id = "550e8400-e29b-41d4-a716-446655440999" mock_repository.update.return_value = mock_updated with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.update_sales_record( record_id, update_data, sample_tenant_id ) assert result.inventory_product_id == "550e8400-e29b-41d4-a716-446655440999" mock_repository.update.assert_called_once() async def test_update_nonexistent_record(self, sales_service, sample_tenant_id): """Test updating a non-existent record""" record_id = uuid4() update_data = SalesDataUpdate(inventory_product_id="550e8400-e29b-41d4-a716-446655440999", product_name="Updated Product") with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_repository.get_by_id.return_value = None # Record not found with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): with pytest.raises(ValueError, match="not found for tenant"): await sales_service.update_sales_record( record_id, update_data, sample_tenant_id ) async def test_get_sales_records(self, sales_service, sample_tenant_id): """Test getting sales records for tenant""" query_params = SalesDataQuery(limit=10) with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_records = [AsyncMock(), AsyncMock()] mock_repository.get_by_tenant.return_value = mock_records with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.get_sales_records( sample_tenant_id, query_params ) assert len(result) == 2 mock_repository.get_by_tenant.assert_called_once_with(sample_tenant_id, query_params) async def test_get_sales_record_success(self, sales_service, sample_tenant_id): """Test getting a specific sales record""" record_id = uuid4() with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_record = AsyncMock() mock_record.tenant_id = sample_tenant_id mock_repository.get_by_id.return_value = mock_record with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.get_sales_record(record_id, sample_tenant_id) assert result is not None assert result.tenant_id == sample_tenant_id async def test_get_sales_record_wrong_tenant(self, sales_service, sample_tenant_id): """Test getting a record that belongs to different tenant""" record_id = uuid4() wrong_tenant_id = uuid4() with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_record = AsyncMock() mock_record.tenant_id = sample_tenant_id # Different tenant mock_repository.get_by_id.return_value = mock_record with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.get_sales_record(record_id, wrong_tenant_id) assert result is None async def test_delete_sales_record(self, sales_service, sample_tenant_id): """Test deleting a sales record""" record_id = uuid4() with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() # Mock existing record mock_existing = AsyncMock() mock_existing.tenant_id = sample_tenant_id mock_repository.get_by_id.return_value = mock_existing mock_repository.delete.return_value = True with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.delete_sales_record(record_id, sample_tenant_id) assert result is True mock_repository.delete.assert_called_once_with(record_id) async def test_get_product_sales(self, sales_service, sample_tenant_id): """Test getting sales for specific product""" inventory_product_id = "550e8400-e29b-41d4-a716-446655440000" with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_records = [AsyncMock(), AsyncMock()] mock_repository.get_by_product.return_value = mock_records with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.get_product_sales(sample_tenant_id, inventory_product_id) assert len(result) == 2 mock_repository.get_by_product.assert_called_once() async def test_get_sales_analytics(self, sales_service, sample_tenant_id): """Test getting sales analytics""" with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() mock_analytics = { "total_revenue": Decimal("100.00"), "total_quantity": 50, "total_transactions": 10 } mock_repository.get_analytics.return_value = mock_analytics with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.get_sales_analytics(sample_tenant_id) assert result["total_revenue"] == Decimal("100.00") assert result["total_quantity"] == 50 assert result["total_transactions"] == 10 async def test_validate_sales_record(self, sales_service, sample_tenant_id): """Test validating a sales record""" record_id = uuid4() validation_notes = "Validated by manager" with patch('app.services.sales_service.get_db_transaction') as mock_get_db: mock_db = AsyncMock() mock_get_db.return_value.__aenter__.return_value = mock_db mock_repository = AsyncMock() # Mock existing record mock_existing = AsyncMock() mock_existing.tenant_id = sample_tenant_id mock_repository.get_by_id.return_value = mock_existing # Mock validated record mock_validated = AsyncMock() mock_validated.is_validated = True mock_repository.validate_record.return_value = mock_validated with patch('app.services.sales_service.SalesRepository', return_value=mock_repository): result = await sales_service.validate_sales_record( record_id, sample_tenant_id, validation_notes ) assert result.is_validated is True mock_repository.validate_record.assert_called_once_with(record_id, validation_notes) async def test_validate_sales_data_business_rules(self, sales_service, sample_tenant_id): """Test business validation rules""" # Test revenue mismatch detection sales_data = SalesDataCreate( date=datetime.now(timezone.utc), inventory_product_id="550e8400-e29b-41d4-a716-446655440000", product_name="Test Product", quantity_sold=5, unit_price=Decimal("2.00"), revenue=Decimal("15.00"), # Should be 10.00 (5 * 2.00) discount_applied=Decimal("0") ) # This should not raise an error, just log a warning await sales_service._validate_sales_data(sales_data, sample_tenant_id) async def test_post_create_actions(self, sales_service): """Test post-create actions""" mock_record = AsyncMock() mock_record.id = uuid4() # Should not raise any exceptions await sales_service._post_create_actions(mock_record)