@ -182,6 +182,14 @@ class OrderFlowHarness(OrderFlowMixin):
def _normalize_text ( self , text : str ) - > str :
return self . normalizer . normalize_text ( text )
def _is_affirmative_message ( self , text : str ) - > bool :
normalized = self . normalizer . normalize_text ( text ) . strip ( ) . rstrip ( " .!?,;: " )
return normalized in { " sim " , " pode " , " ok " , " confirmo " , " aceito " , " fechado " , " pode sim " , " continuar " }
def _is_negative_message ( self , text : str ) - > bool :
normalized = self . normalizer . normalize_text ( text ) . strip ( ) . rstrip ( " .!?,;: " )
return normalized in { " nao " , " não " , " negativo " , " cancelar " , " outro " , " outra opcao " , " outra opção " }
def _normalize_positive_number ( self , value ) :
return self . normalizer . normalize_positive_number ( value )
@ -800,6 +808,86 @@ class CreateOrderFlowWithVehicleTests(unittest.IsolatedAsyncioTestCase):
self . assertIn ( " Honda Civic 2021 " , response )
self . assertEqual ( registry . calls , [ ] )
async def test_order_flow_single_stock_result_requires_explicit_confirmation ( self ) :
state = FakeState (
entries = {
" pending_order_drafts " : {
10 : {
" payload " : { " cpf " : " 12345678909 " } ,
" expires_at " : datetime . utcnow ( ) + timedelta ( minutes = 30 ) ,
}
}
} ,
contexts = {
10 : {
" generic_memory " : { " cpf " : " 12345678909 " , " orcamento_max " : 70000 } ,
" shared_memory " : { " orcamento_max " : 70000 } ,
" last_stock_results " : [ ] ,
" selected_vehicle " : None ,
}
} ,
)
registry = FakeRegistry ( )
async def single_result_execute ( tool_name : str , arguments : dict , user_id : int | None = None ) :
registry . calls . append ( ( tool_name , arguments , user_id ) )
if tool_name == " consultar_estoque " :
return [ { " id " : 7 , " modelo " : " Fiat Argo 2020 " , " categoria " : " suv " , " preco " : 61857.0 } ]
if tool_name == " realizar_pedido " :
return {
" numero_pedido " : " PED-TESTE-123 " ,
" status " : " Ativo " ,
" modelo_veiculo " : " Fiat Argo 2020 " ,
" valor_veiculo " : 61857.0 ,
}
raise AssertionError ( f " Tool inesperada no teste: { tool_name } " )
registry . execute = single_result_execute
flow = OrderFlowHarness ( state = state , registry = registry )
async def fake_hydrate_mock_customer_from_cpf ( cpf : str , user_id : int | None = None ) :
return { " cpf " : cpf , " user_id " : user_id }
with patch (
" app.services.flows.order_flow.hydrate_mock_customer_from_cpf " ,
new = fake_hydrate_mock_customer_from_cpf ,
) :
response = await flow . _try_collect_and_create_order (
message = " quero comprar um carro de ate 70 mil " ,
user_id = 10 ,
extracted_fields = { } ,
intents = { " order_create " : True } ,
)
self . assertIn ( " Encontrei 1 opcao " , response )
self . assertIn ( " Fiat Argo 2020 " , response )
self . assertEqual ( len ( registry . calls ) , 1 )
self . assertEqual ( registry . calls [ 0 ] [ 0 ] , " consultar_estoque " )
self . assertEqual ( state . get_user_context ( 10 ) [ " selected_vehicle " ] , None )
self . assertIsNotNone ( state . get_user_context ( 10 ) [ " pending_single_vehicle_confirmation " ] )
follow_up_response = await flow . _try_collect_and_create_order (
message = " 12345678909 " ,
user_id = 10 ,
extracted_fields = { } ,
intents = { } ,
)
self . assertIn ( " Posso seguir com essa opcao " , follow_up_response )
self . assertEqual ( len ( registry . calls ) , 1 )
confirmation_response = await flow . _try_collect_and_create_order (
message = " sim " ,
user_id = 10 ,
extracted_fields = { } ,
intents = { } ,
)
self . assertEqual ( len ( registry . calls ) , 2 )
self . assertEqual ( registry . calls [ 1 ] [ 0 ] , " realizar_pedido " )
self . assertEqual ( registry . calls [ 1 ] [ 1 ] [ " vehicle_id " ] , 7 )
self . assertIn ( " Pedido criado com sucesso. " , confirmation_response )
async def test_order_flow_creates_order_with_selected_vehicle_from_list_index ( self ) :
state = FakeState (
entries = {
@ -1357,6 +1445,62 @@ class ReviewFlowDraftTests(unittest.IsolatedAsyncioTestCase):
self . assertEqual ( registry . calls [ 0 ] [ 1 ] [ " data_hora " ] , f " { today_text } 14:00 " )
self . assertIn ( " REV-TESTE-123 " , final_response )
async def test_review_flow_reuse_confirmation_accepts_relative_date_then_time_in_follow_up ( self ) :
state = FakeState (
entries = {
" pending_review_reuse_confirmations " : {
21 : {
" payload " : {
" placa " : " ABC1263 " ,
" modelo " : " Onix " ,
" ano " : 2024 ,
" km " : 50000 ,
" revisao_previa_concessionaria " : False ,
} ,
" expires_at " : datetime . utcnow ( ) + timedelta ( minutes = 30 ) ,
}
}
}
)
registry = FakeRegistry ( )
flow = ReviewFlowHarness ( state = state , registry = registry )
today_text = datetime . now ( ) . strftime ( " %d / % m/ % Y " )
first_response = await flow . _try_collect_and_schedule_review (
message = " sim " ,
user_id = 21 ,
extracted_fields = { } ,
intents = { } ,
turn_decision = { " intent " : " review_schedule " , " domain " : " review " , " action " : " collect_review_schedule " } ,
)
self . assertIn ( " Me informe apenas a data e hora desejada " , first_response )
second_response = await flow . _try_collect_and_schedule_review (
message = " quero marcar para hoje " ,
user_id = 21 ,
extracted_fields = { } ,
intents = { } ,
turn_decision = { " intent " : " review_schedule " , " domain " : " review " , " action " : " collect_review_schedule " } ,
)
draft = state . get_entry ( " pending_review_drafts " , 21 )
self . assertIsNotNone ( draft )
self . assertEqual ( draft [ " payload " ] . get ( " data_hora_base " ) , today_text )
self . assertIn ( " Agora me informe o horario desejado " , second_response )
final_response = await flow . _try_collect_and_schedule_review (
message = " as 14 horas " ,
user_id = 21 ,
extracted_fields = { } ,
intents = { } ,
turn_decision = { " intent " : " review_schedule " , " domain " : " review " , " action " : " collect_review_schedule " } ,
)
self . assertEqual ( registry . calls [ 0 ] [ 0 ] , " agendar_revisao " )
self . assertEqual ( registry . calls [ 0 ] [ 1 ] [ " data_hora " ] , f " { today_text } 14:00 " )
self . assertIn ( " REV-TESTE-123 " , final_response )
async def test_review_flow_clears_stale_pending_confirmation_when_user_starts_new_schedule ( self ) :
state = FakeState (
entries = {