Files
bakery-ia/services/sales/tests/unit/test_services.py
2025-08-14 16:47:34 +02:00

290 lines
13 KiB
Python

# 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)