You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
5.9 KiB
Python
164 lines
5.9 KiB
Python
import os
|
|
import unittest
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
os.environ.setdefault("DEBUG", "false")
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from app.db.mock_models import Vehicle
|
|
from app.services.domain import order_service
|
|
|
|
|
|
class LockingQuery:
|
|
def __init__(self, result):
|
|
self.result = result
|
|
self.with_for_update_called = False
|
|
|
|
def filter(self, *args, **kwargs):
|
|
return self
|
|
|
|
def with_for_update(self):
|
|
self.with_for_update_called = True
|
|
return self
|
|
|
|
def first(self):
|
|
return self.result
|
|
|
|
|
|
class LockingSession:
|
|
def __init__(self, result):
|
|
self.query_instance = LockingQuery(result)
|
|
|
|
def query(self, model):
|
|
return self.query_instance
|
|
|
|
|
|
class FakeSession:
|
|
def __init__(self, vehicle=None, user=None):
|
|
self.vehicle = vehicle
|
|
self.user = user
|
|
self.added = []
|
|
self.committed = False
|
|
self.closed = False
|
|
self.rolled_back = False
|
|
self.refreshed = []
|
|
|
|
def query(self, model):
|
|
if model is order_service.Vehicle:
|
|
return LockingQuery(self.vehicle)
|
|
if model is order_service.User:
|
|
return LockingQuery(self.user)
|
|
raise AssertionError(f"unexpected model query: {model}")
|
|
|
|
def execute(self, statement, params=None):
|
|
sql_text = str(statement)
|
|
if "GET_LOCK" in sql_text:
|
|
return SimpleNamespace(scalar=lambda: 1)
|
|
if "RELEASE_LOCK" in sql_text:
|
|
return SimpleNamespace(scalar=lambda: 1)
|
|
raise AssertionError(f"unexpected execute call: {sql_text}")
|
|
|
|
def add(self, item):
|
|
self.added.append(item)
|
|
|
|
def commit(self):
|
|
self.committed = True
|
|
|
|
def rollback(self):
|
|
self.rolled_back = True
|
|
|
|
def refresh(self, item):
|
|
self.refreshed.append(item)
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
class OrderServiceReservationTests(unittest.IsolatedAsyncioTestCase):
|
|
def test_get_vehicle_for_update_uses_row_lock(self):
|
|
vehicle = Vehicle(id=8, modelo="Toyota Corolla 2024", categoria="suv", preco=76087.0)
|
|
session = LockingSession(vehicle)
|
|
|
|
locked_vehicle = order_service._get_vehicle_for_update(session, 8)
|
|
|
|
self.assertIs(locked_vehicle, vehicle)
|
|
self.assertTrue(session.query_instance.with_for_update_called)
|
|
|
|
async def test_realizar_pedido_raises_conflict_when_locked_vehicle_is_already_reserved(self):
|
|
vehicle = Vehicle(id=8, modelo="Toyota Corolla 2024", categoria="suv", preco=76087.0)
|
|
session = FakeSession(vehicle=vehicle)
|
|
existing_order = SimpleNamespace(numero_pedido="PED-RESERVA-001", status="Ativo")
|
|
|
|
async def fake_hydrate_mock_customer_from_cpf(cpf: str, user_id: int | None = None):
|
|
return {"cpf": cpf, "user_id": user_id}
|
|
|
|
async def fake_validar_cliente_venda(cpf: str, valor_veiculo: float):
|
|
return {"aprovado": True}
|
|
|
|
with patch.object(order_service, "SessionMockLocal", return_value=session), patch.object(
|
|
order_service,
|
|
"hydrate_mock_customer_from_cpf",
|
|
new=fake_hydrate_mock_customer_from_cpf,
|
|
), patch.object(
|
|
order_service,
|
|
"validar_cliente_venda",
|
|
new=fake_validar_cliente_venda,
|
|
), patch.object(order_service, "_get_vehicle_for_update", return_value=vehicle) as locked_vehicle, patch.object(
|
|
order_service,
|
|
"_get_active_order_for_vehicle",
|
|
return_value=existing_order,
|
|
):
|
|
with self.assertRaises(HTTPException) as ctx:
|
|
await order_service.realizar_pedido(cpf="123.456.789-09", vehicle_id=8)
|
|
|
|
self.assertEqual(ctx.exception.status_code, 409)
|
|
self.assertEqual(ctx.exception.detail["code"], "vehicle_already_reserved")
|
|
locked_vehicle.assert_called_once_with(session, 8)
|
|
self.assertEqual(session.added, [])
|
|
self.assertTrue(session.closed)
|
|
|
|
async def test_realizar_pedido_uses_locked_vehicle_before_persisting_order(self):
|
|
vehicle = Vehicle(id=8, modelo="Toyota Corolla 2024", categoria="suv", preco=76087.0)
|
|
session = FakeSession(vehicle=vehicle)
|
|
fake_uuid = SimpleNamespace(hex="abc123def456")
|
|
fixed_now = datetime(2026, 3, 16, 17, 30, 0)
|
|
|
|
async def fake_hydrate_mock_customer_from_cpf(cpf: str, user_id: int | None = None):
|
|
return {"cpf": cpf, "user_id": user_id}
|
|
|
|
async def fake_validar_cliente_venda(cpf: str, valor_veiculo: float):
|
|
return {"aprovado": True}
|
|
|
|
with patch.object(order_service, "SessionMockLocal", return_value=session), patch.object(
|
|
order_service,
|
|
"hydrate_mock_customer_from_cpf",
|
|
new=fake_hydrate_mock_customer_from_cpf,
|
|
), patch.object(
|
|
order_service,
|
|
"validar_cliente_venda",
|
|
new=fake_validar_cliente_venda,
|
|
), patch.object(order_service, "_get_vehicle_for_update", return_value=vehicle) as locked_vehicle, patch.object(
|
|
order_service,
|
|
"_get_active_order_for_vehicle",
|
|
return_value=None,
|
|
), patch.object(order_service, "uuid4", return_value=fake_uuid), patch.object(order_service, "utc_now", return_value=fixed_now):
|
|
result = await order_service.realizar_pedido(cpf="123.456.789-09", vehicle_id=8)
|
|
|
|
locked_vehicle.assert_called_once_with(session, 8)
|
|
self.assertTrue(session.committed)
|
|
self.assertEqual(len(session.added), 1)
|
|
created_order = session.added[0]
|
|
self.assertEqual(created_order.vehicle_id, 8)
|
|
self.assertEqual(created_order.cpf, "12345678909")
|
|
self.assertEqual(result["numero_pedido"], "PED-20260316173000-ABC123")
|
|
self.assertEqual(result["vehicle_id"], 8)
|
|
self.assertEqual(result["status_veiculo"], "Reservado")
|
|
self.assertTrue(session.closed)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|