Files
agent_contas/tests/migration/test_cancel_backend_wrapper_parity.py

274 lines
13 KiB
Python

from __future__ import annotations
import pytest
def _result(name: str, status: str, output: dict, *, error: str | None = None, error_details=None):
return {
"execution_id": f"exec-{name}",
"workflow_name": name,
"workflow_version": 1,
"status": status,
"output": output,
"state": {},
"trace": [],
"error": error,
"error_details": error_details,
}
class Service:
def consultar_faturas(self, *, msisdn: str):
return {
"billingProfile": {"customer": {"document": "12345678901", "customerId": "344575482"}},
"paymentItems": [{"invoiceId": "5536806781", "dueDate": "2025-08-11"}],
}
@pytest.mark.asyncio
async def test_cancelamento_item_ja_contestado_preserva_sucesso_do_cancelamento(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime:
def __init__(self): self.calls=[]
async def arun(self, name, payload, execution_id=None):
self.calls.append((name,payload,execution_id))
if name == "cancelamento_vas_avulso":
return _result(name, "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"]}],
"contestation_candidates": [{"success": True, "subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"]}],
"protocolos_por_linha": [{"msisdn": payload["msisdn"], "protocolo_id": "PRT-CANCEL-1"}],
}})
return _result(name, "FAILED", {"registrar_protocolo": {"protocolo_id": "PRT-102"}},
error="Itens já contestados ou conflito de itens da fatura",
error_details={"status_code": 409, "body": {"provider": {"errorCode": "ITEM_JA_CONTESTADO"}}})
runtime=Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", Service())
result = await main._run_cancelamento_com_contestacao({"msisdn":"5511999999999","subject":"TIM Fashion Mensal","valor":"14,99"})
assert result["success"] is True
assert result["erro_sistemico"] is False
assert result["auto_finalize_on_failure"] is False
assert result["protocol_closed"] is False
assert result["contestacao_protocol"] == "PRT-102"
assert result["cancelamento_vas_protocol"] == "PRT-CANCEL-1"
assert result["protocols_for_response"] == ["PRT-CANCEL-1", "PRT-102"]
assert "já contestados" in result["mensagem"]
@pytest.mark.asyncio
async def test_cancelamento_falha_tecnica_na_contestacao_recomenda_auto_finalizacao(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime:
async def arun(self, name, payload, execution_id=None):
if name == "cancelamento_vas_avulso":
return _result(name, "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"]}],
"contestation_candidates": [{"success": True, "subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"]}],
}})
return _result(name, "FAILED", {}, error="timeout no provider", error_details={"status_code": 504})
monkeypatch.setattr(main, "get_workflow_runtime", lambda: Runtime())
monkeypatch.setattr(main, "service", Service())
result = await main._run_cancelamento_com_contestacao({"msisdn":"5511999999999","subject":"TIM Fashion Mensal","valor":"14,99"})
assert result["success"] is False
assert result["erro_sistemico"] is True
assert result["auto_finalize_on_failure"] is True
assert result["mensagem"] == "timeout no provider"
@pytest.mark.asyncio
async def test_cancelamento_sem_contestacao_expoe_protocolo_de_cancelamento(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime:
async def arun(self, name, payload, execution_id=None):
return _result(name, "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"]}],
"contestation_candidates": [],
"protocolos_por_linha": [{"msisdn": payload["msisdn"], "protocolo_id": "PRT-CANCEL-1"}],
}})
monkeypatch.setattr(main, "get_workflow_runtime", lambda: Runtime())
result = await main._run_cancelamento_com_contestacao({"msisdn":"5511999999999","subject":"TIM Fashion Mensal"})
assert result["cancelamento_vas_protocol"] == "PRT-CANCEL-1"
assert result["protocols_for_response"] == ["PRT-CANCEL-1"]
assert result["requires_protocol_in_response"] is True
@pytest.mark.asyncio
async def test_familia_contesta_titular_e_dependente_em_uma_unica_chamada(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime:
def __init__(self): self.calls=[]
async def arun(self, name, payload, execution_id=None):
self.calls.append((name,payload,execution_id))
if name == "cancelamento_vas_avulso":
return _result(name, "COMPLETED", {"cancelar_vas_avulso": {
"results": [
{"success": True, "subject": "TIM Fashion Mensal", "msisdn": "5511888880000"},
{"success": True, "subject": "TIM Games", "msisdn": "5511999999999"},
],
"contestation_candidates": [
{"success": True, "subject": "TIM Fashion Mensal", "msisdn": "5511888880000"},
{"success": True, "subject": "TIM Games", "msisdn": "5511999999999"},
],
}})
return _result(name, "COMPLETED", {"registrar_protocolo":{"protocolo_id":"PRT-FAM"}, "abrir_contestacao_cliente":{"success":True}})
runtime=Runtime()
monkeypatch.setattr(main,"get_workflow_runtime",lambda:runtime)
monkeypatch.setattr(main,"service",Service())
await main._run_cancelamento_com_contestacao({
"msisdn":"5511888880000",
"items":[
{"msisdn":"5511888880000","name":"TIM Fashion Mensal","value":"14,99"},
{"msisdn":"5511999999999","name":"TIM Games","value":"9,90"},
],
})
assert [x[0] for x in runtime.calls] == ["cancelamento_vas_avulso","contestacao_tool"]
contest=runtime.calls[1][1]
assert contest["msisdn"] == "5511888880000"
assert contest["item_msisdn"] == "5511888880000"
assert contest["dependent_invoice_item"] is True
assert {x["itemName"] for x in contest["items"]} == {"TIM Fashion Mensal","TIM Games"}
@pytest.mark.asyncio
async def test_lote_trinta_contesta_somente_unico_candidato(monkeypatch):
import contas_mcp.servers.contas_mcp_server.main as main
class Runtime:
def __init__(self): self.calls=[]
async def arun(self,name,payload,execution_id=None):
self.calls.append((name,payload,execution_id))
if name == "cancelamento_vas_avulso":
failures=[{"success":False,"subject":f"VAS não encontrado {i}","msisdn":payload["msisdn"],"reason":"service_not_found","error":"não encontrado"} for i in range(1,30)]
ok={"success":True,"subject":"TIM Fashion Mensal","msisdn":payload["msisdn"]}
return _result(name,"COMPLETED",{"cancelar_vas_avulso":{"results":[ok,*failures],"contestation_candidates":[ok]}})
return _result(name,"COMPLETED",{"registrar_protocolo":{"protocolo_id":"PRT-PARCIAL"},"abrir_contestacao_cliente":{"success":True}})
runtime=Runtime()
monkeypatch.setattr(main,"get_workflow_runtime",lambda:runtime)
monkeypatch.setattr(main,"service",Service())
items=[{"msisdn":"5511999999999","name":"TIM Fashion Mensal","value":"14,99"}, *[
{"msisdn":"5511999999999","name":f"VAS não encontrado {i}","value":"22,00"} for i in range(1,30)
]]
result=await main._run_cancelamento_com_contestacao({"msisdn":"5511999999999","items":items})
assert len(runtime.calls)==2
assert [x["itemName"] for x in runtime.calls[1][1]["items"]] == ["TIM Fashion Mensal"]
assert result["success"] is True
assert result["contestacao_protocol"] == "PRT-PARCIAL"
@pytest.mark.asyncio
async def test_wrapper_itens_ja_contestados_preserva_mensagem_deterministica(monkeypatch):
from contas_mcp.servers.contas_mcp_server import main
cancel = _result("cancelamento_vas_avulso", "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
"contestation_candidates": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
}})
contest = _result("contestacao_tool", "COMPLETED", {
"registrar_protocolo": {"protocolo_id": "PRT-100"},
"abrir_contestacao_cliente": {
"success": True,
"itens_ja_contestados": ["TIM Fashion Mensal"],
"items_response": [],
"contested_invoice_amount_open": "0",
"contested_invoice_amount": "0",
},
})
class Runtime:
def __init__(self): self.i=0
async def arun(self, name, payload, execution_id=None):
self.i += 1
return cancel if self.i == 1 else contest
runtime = Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", Service())
result = await main._run_cancelamento_com_contestacao({
"msisdn": "11999999999",
"subject": "TIM Fashion Mensal",
"valor": 14.99,
"items": [{"msisdn": "11999999999", "name": "TIM Fashion Mensal", "value": 14.99}],
})
assert result["success"] is True
assert result["mensagem"] == (
"Identifiquei que o item TIM Fashion Mensal já havia sido contestado anteriormente e, "
"por esse motivo, não é possível registrar uma nova contestação para ele. "
"Seu número de protocolo é PRT-100."
)
@pytest.mark.asyncio
async def test_wrapper_contestacao_zerada_usa_total_cancelado_na_mensagem(monkeypatch):
from contas_mcp.servers.contas_mcp_server import main
cancel = _result("cancelamento_vas_avulso", "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
"contestation_candidates": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
}})
contest = _result("contestacao_tool", "COMPLETED", {
"registrar_protocolo": {"protocolo_id": "PRT-100"},
"abrir_contestacao_cliente": {
"success": True,
"items_response": [{"status": "CRIAR", "itemName": "TIM Fashion Mensal"}],
"contested_invoice_amount_open": "0",
"contested_invoice_amount": "0",
},
})
class Runtime:
def __init__(self): self.i=0
async def arun(self, name, payload, execution_id=None):
self.i += 1
return cancel if self.i == 1 else contest
runtime = Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", Service())
result = await main._run_cancelamento_com_contestacao({
"msisdn": "11999999999",
"subject": "TIM Fashion Mensal",
"valor": 14.99,
"items": [{"msisdn": "11999999999", "name": "TIM Fashion Mensal", "value": 14.99}],
})
assert "R$ 14,99" in result["mensagem"]
@pytest.mark.asyncio
async def test_wrapper_next_subject_nao_afeta_mensagem_deterministica(monkeypatch):
from contas_mcp.servers.contas_mcp_server import main
cancel = _result("cancelamento_vas_avulso", "COMPLETED", {"cancelar_vas_avulso": {
"results": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
"contestation_candidates": [{"success": True, "msisdn": "11999999999", "subject": "TIM Fashion Mensal"}],
}})
contest = _result("contestacao_tool", "COMPLETED", {
"registrar_protocolo": {"protocolo_id": "PRT-100"},
"abrir_contestacao_cliente": {
"success": True,
"items_response": [{"status": "CRIAR", "itemName": "TIM Fashion Mensal"}],
"contested_invoice_amount_open": "14,99",
},
})
class Runtime:
def __init__(self): self.i=0
async def arun(self, name, payload, execution_id=None):
self.i += 1
return cancel if self.i == 1 else contest
runtime = Runtime()
monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime)
monkeypatch.setattr(main, "service", Service())
result = await main._run_cancelamento_com_contestacao({
"msisdn": "11999999999",
"subject": "TIM Fashion Mensal",
"valor": 14.99,
"next_subject": "contestação de fatura",
"items": [{"msisdn": "11999999999", "name": "TIM Fashion Mensal", "value": 14.99}],
})
assert "contestação de fatura" not in result["mensagem"]