Initial commit - production deployment
This commit is contained in:
136
services/distribution/tests/test_distribution_cloning.py
Normal file
136
services/distribution/tests/test_distribution_cloning.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import sys
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from datetime import date, datetime, timedelta
|
||||
import uuid
|
||||
import pytest
|
||||
|
||||
# Mock shared.config.base and pydantic_settings
|
||||
mock_base = MagicMock()
|
||||
mock_base.BASE_REFERENCE_DATE = date(2025, 11, 25)
|
||||
sys.modules["shared.config.base"] = mock_base
|
||||
sys.modules["pydantic_settings"] = MagicMock()
|
||||
sys.modules["shared.database.base"] = MagicMock()
|
||||
sys.modules["app.models.distribution"] = MagicMock()
|
||||
sys.modules["shared.clients.tenant_client"] = MagicMock()
|
||||
sys.modules["shared.clients.inventory_client"] = MagicMock()
|
||||
sys.modules["shared.clients.procurement_client"] = MagicMock()
|
||||
sys.modules["httpx"] = MagicMock()
|
||||
|
||||
from app.services.distribution_service import DistributionService
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_demo_enterprise_distribution_clones_history():
|
||||
# Setup mocks
|
||||
route_repo = AsyncMock()
|
||||
shipment_repo = AsyncMock()
|
||||
schedule_repo = AsyncMock()
|
||||
procurement_client = AsyncMock()
|
||||
tenant_client = AsyncMock()
|
||||
inventory_client = AsyncMock()
|
||||
routing_optimizer = AsyncMock()
|
||||
|
||||
service = DistributionService(
|
||||
route_repository=route_repo,
|
||||
shipment_repository=shipment_repo,
|
||||
schedule_repository=schedule_repo,
|
||||
procurement_client=procurement_client,
|
||||
tenant_client=tenant_client,
|
||||
inventory_client=inventory_client,
|
||||
routing_optimizer=routing_optimizer
|
||||
)
|
||||
|
||||
# Mock data
|
||||
parent_tenant_id = str(uuid.uuid4())
|
||||
child_tenant_ids = [str(uuid.uuid4()), str(uuid.uuid4()), str(uuid.uuid4())]
|
||||
session_id = "test-session"
|
||||
|
||||
# Mock tenant client responses
|
||||
async def get_locations(tenant_id):
|
||||
if tenant_id == parent_tenant_id:
|
||||
return [{"location_type": "central_production", "latitude": 40.0, "longitude": -3.0, "name": "Central"}]
|
||||
else:
|
||||
return [{"location_type": "retail_outlet", "latitude": 40.1, "longitude": -3.1, "name": "Outlet"}]
|
||||
|
||||
tenant_client.get_tenant_locations.side_effect = get_locations
|
||||
|
||||
# Mock routing optimizer
|
||||
routing_optimizer.optimize_daily_routes.return_value = {
|
||||
"total_distance_km": 50.0,
|
||||
"estimated_duration_minutes": 60,
|
||||
"routes": [
|
||||
{
|
||||
"vehicle_id": "VEH-NEW",
|
||||
"driver_id": "DRV-NEW",
|
||||
"total_distance_km": 50.0,
|
||||
"estimated_duration_minutes": 60,
|
||||
"route_sequence": [
|
||||
{"stop": 1, "tenant_id": child_tenant_ids[0], "location": "Outlet"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Mock historical data
|
||||
start_date = date(2025, 10, 26)
|
||||
end_date = date(2025, 11, 25)
|
||||
|
||||
mock_routes = [
|
||||
{
|
||||
"id": "old-route-1",
|
||||
"route_number": "DEMO-20251120-001",
|
||||
"route_date": datetime(2025, 11, 20),
|
||||
"vehicle_id": "VEH-001",
|
||||
"total_distance_km": 100.0,
|
||||
"estimated_duration_minutes": 120,
|
||||
"route_sequence": [
|
||||
{"stop": 1, "tenant_id": "d4e5f6a7-b8c9-40d1-e2f3-a4b5c6d7e8f9"}, # Template child 1
|
||||
{"stop": 2, "tenant_id": "e5f6a7b8-c9d0-41e2-f3a4-b5c6d7e8f9a0"} # Template child 2
|
||||
],
|
||||
"status": "completed"
|
||||
}
|
||||
]
|
||||
|
||||
mock_shipments = [
|
||||
{
|
||||
"id": "old-shipment-1",
|
||||
"child_tenant_id": "d4e5f6a7-b8c9-40d1-e2f3-a4b5c6d7e8f9", # Template child 1
|
||||
"delivery_route_id": "old-route-1",
|
||||
"shipment_number": "DEMOSHP-20251120-001",
|
||||
"shipment_date": datetime(2025, 11, 20),
|
||||
"status": "delivered",
|
||||
"total_weight_kg": 50.0,
|
||||
"total_volume_m3": 0.5
|
||||
}
|
||||
]
|
||||
|
||||
route_repo.get_routes_by_date_range.return_value = mock_routes
|
||||
shipment_repo.get_shipments_by_date_range.return_value = mock_shipments
|
||||
|
||||
route_repo.create_route.return_value = {"id": "new-route-1"}
|
||||
|
||||
# Execute
|
||||
result = await service.setup_demo_enterprise_distribution(
|
||||
parent_tenant_id=parent_tenant_id,
|
||||
child_tenant_ids=child_tenant_ids,
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
# Verify
|
||||
assert result["status"] == "completed"
|
||||
assert result["routes_count"] == 1
|
||||
assert result["shipment_count"] == 1
|
||||
|
||||
# Verify route creation
|
||||
route_repo.create_route.assert_called()
|
||||
call_args = route_repo.create_route.call_args[0][0]
|
||||
assert call_args["tenant_id"] == parent_tenant_id
|
||||
assert call_args["route_number"] == "DEMO-20251120-001"
|
||||
# Verify child ID mapping in sequence
|
||||
assert call_args["route_sequence"][0]["tenant_id"] == child_tenant_ids[0]
|
||||
|
||||
# Verify shipment creation
|
||||
shipment_repo.create_shipment.assert_called()
|
||||
call_args = shipment_repo.create_shipment.call_args[0][0]
|
||||
assert call_args["tenant_id"] == parent_tenant_id
|
||||
assert call_args["child_tenant_id"] == child_tenant_ids[0]
|
||||
assert call_args["delivery_route_id"] == "new-route-1"
|
||||
88
services/distribution/tests/test_routing_optimizer.py
Normal file
88
services/distribution/tests/test_routing_optimizer.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import sys
|
||||
from app.services.routing_optimizer import RoutingOptimizer
|
||||
|
||||
# Mock OR-Tools if not installed in the test environment
|
||||
try:
|
||||
from ortools.constraint_solver import routing_enums_pb2
|
||||
from ortools.constraint_solver import pywrapcp
|
||||
HAS_ORTOOLS = True
|
||||
except ImportError:
|
||||
HAS_ORTOOLS = False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_routing_optimizer_initialization():
|
||||
optimizer = RoutingOptimizer()
|
||||
assert optimizer.has_ortools == HAS_ORTOOLS
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_optimize_daily_routes_fallback():
|
||||
# Force fallback by mocking has_ortools to False
|
||||
optimizer = RoutingOptimizer()
|
||||
optimizer.has_ortools = False
|
||||
|
||||
depot_location = (40.7128, -74.0060) # NYC
|
||||
deliveries = [
|
||||
{
|
||||
'id': 'd1',
|
||||
'location': (40.730610, -73.935242), # Brooklyn
|
||||
'weight_kg': 100
|
||||
},
|
||||
{
|
||||
'id': 'd2',
|
||||
'location': (40.758896, -73.985130), # Times Square
|
||||
'weight_kg': 50
|
||||
}
|
||||
]
|
||||
|
||||
result = await optimizer.optimize_daily_routes(deliveries, depot_location)
|
||||
|
||||
assert result['status'] == 'success'
|
||||
assert result['algorithm_used'] == 'fallback_sequential'
|
||||
assert len(result['routes']) == 1
|
||||
assert len(result['routes'][0]['stops']) == 4 # Start + 2 deliveries + End
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_optimize_daily_routes_vrp():
|
||||
if not HAS_ORTOOLS:
|
||||
pytest.skip("OR-Tools not installed")
|
||||
|
||||
optimizer = RoutingOptimizer()
|
||||
|
||||
depot_location = (40.7128, -74.0060) # NYC
|
||||
deliveries = [
|
||||
{
|
||||
'id': 'd1',
|
||||
'location': (40.730610, -73.935242), # Brooklyn
|
||||
'weight_kg': 100
|
||||
},
|
||||
{
|
||||
'id': 'd2',
|
||||
'location': (40.758896, -73.985130), # Times Square
|
||||
'weight_kg': 50
|
||||
},
|
||||
{
|
||||
'id': 'd3',
|
||||
'location': (40.7829, -73.9654), # Central Park
|
||||
'weight_kg': 200
|
||||
}
|
||||
]
|
||||
|
||||
# Run optimization
|
||||
result = await optimizer.optimize_daily_routes(deliveries, depot_location)
|
||||
|
||||
assert result['status'] == 'success'
|
||||
assert result['algorithm_used'] == 'ortools_vrp'
|
||||
assert len(result['routes']) >= 1
|
||||
|
||||
# Check if all deliveries are covered
|
||||
delivery_ids = []
|
||||
for route in result['routes']:
|
||||
for stop in route['stops']:
|
||||
if 'delivery_id' in stop and not stop.get('location') == 'depot':
|
||||
delivery_ids.append(stop['delivery_id'])
|
||||
|
||||
assert 'd1' in delivery_ids
|
||||
assert 'd2' in delivery_ids
|
||||
assert 'd3' in delivery_ids
|
||||
Reference in New Issue
Block a user