from __future__ import annotations import pytest def _result(name: str, status: str, output: dict, *, error: str | None = None): return { "execution_id": f"exec-{name}", "workflow_name": name, "workflow_version": 1, "status": status, "output": output, "state": {}, "trace": [], "error": error, "error_details": None, } 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_success_implicito_por_cancelados_sem_flag_success(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0 async def arun(self, name, payload, execution_id=None): self.i += 1 if self.i == 1: return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "cancelados": [{"msisdn": payload["msisdn"], "servico": "Tamboro Mensal", "valor": "14,99"}], "itens_para_contestacao": [{"msisdn": payload["msisdn"], "subject": "Tamboro Mensal"}], }}) return _result(name, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-D2EFF9B40B", "protocol_closed": True}, "abrir_contestacao_cliente": {"success": True, "contested_items": [{"itemName": "Tamboro Mensal", "status": "INICIADA"}]}, }) 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": "Tamboro Mensal", "valor": "14,99"}) assert result["success"] is True assert result["protocol_closed"] is True assert result["contestacao_protocol"] == "PRT-D2EFF9B40B" @pytest.mark.asyncio async def test_lista_explicita_vazia_de_candidatos_nao_faz_fallback_para_cancelados(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) return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "cancelados": [{"msisdn": payload["msisdn"], "servico": "TIM Fashion Mensal"}], "contestation_candidates": [], }}) runtime = Runtime() monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime) result = await main._run_cancelamento_com_contestacao({"msisdn": "5511999999999", "subject": "TIM Fashion Mensal", "valor": "14,99"}) assert runtime.calls == ["cancelamento_vas_avulso"] assert result["success"] is True assert "contestacao_workflow" not in result @pytest.mark.asyncio async def test_no_match_nao_contesta_valor_nem_chama_contestacao(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) return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "results": [{"success": False, "subject": "Inexistente", "error": "serviço não encontrado", "reason": "service_not_found"}], "contestation_candidates": [], "nao_encontrados": [{"servico": "Inexistente"}], }}) runtime = Runtime() monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime) result = await main._run_cancelamento_com_contestacao({"msisdn": "5511999999999", "subject": "Inexistente", "valor": "99,99"}) assert runtime.calls == ["cancelamento_vas_avulso"] assert result["success"] is False assert "contestacao_workflow" not in result assert "99,99" not in result["mensagem"] @pytest.mark.asyncio async def test_protocol_closed_da_contestacao_e_preservado(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0 async def arun(self, name, payload, execution_id=None): self.i += 1 if self.i == 1: 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, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-200", "protocol_closed": True}, "abrir_contestacao_cliente": {"success": True, "contested_items": [{"itemName": "TIM Fashion Mensal", "status": "INICIADA"}], "protocol_closed": True}, }) 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["protocol_closed"] is True assert result["contestacao_protocol"] == "PRT-200" @pytest.mark.asyncio async def test_sms_flag_interno_nao_vaza_no_contrato_externo(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0 async def arun(self, name, payload, execution_id=None): self.i += 1 if self.i == 1: return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "results": [{"success": True, "subject": "Conteudo Premium", "msisdn": payload["msisdn"]}], "contestation_candidates": [{"success": True, "subject": "Conteudo Premium", "msisdn": payload["msisdn"]}], }}) return _result(name, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-201"}, "abrir_contestacao_cliente": {"success": True}, "enviar_sms": {"success": True, "sms_sent": True}, }) runtime = Runtime() monkeypatch.setattr(main, "get_workflow_runtime", lambda: runtime) monkeypatch.setattr(main, "service", _Service()) result = await main._run_cancelamento_com_contestacao({"msisdn": "5511988887777", "subject": "Conteudo Premium", "valor": "9,90"}) assert result["contestacao_protocol"] == "PRT-201" assert "sms_sent" not in result assert result["sms_not_send_error"] is False @pytest.mark.asyncio async def test_cancelamento_com_protocolo_sem_item_contestado_continua_resolvido(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0 async def arun(self, name, payload, execution_id=None): self.i += 1 if self.i == 1: return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "cancelados": [{"msisdn": payload["msisdn"], "servico": "Tamboro Mensal"}], "itens_para_contestacao": [{"msisdn": payload["msisdn"], "subject": "Tamboro Mensal"}], "protocolos_por_linha": [{"msisdn": payload["msisdn"], "protocolo_id": "PRT-CANCEL-1"}], }}) return _result(name, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-CONTEST-1"}, "abrir_contestacao_cliente": {"success": True, "contested_items": [], "not_contested_items": [], "contested_invoice_amount_open": "0"}, }) 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": "Tamboro Mensal", "valor": "14,99"}) assert result["success"] is True assert result["recomenda_finalizacao"] is True assert result["status_finalizacao_sugerido"] == "resolvido" assert result["protocols_for_response"] == ["PRT-CANCEL-1", "PRT-CONTEST-1"] @pytest.mark.asyncio async def test_falha_sms_e_propagada_sem_expor_sms_sent(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0 async def arun(self, name, payload, execution_id=None): self.i += 1 if self.i == 1: 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, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-101"}, "abrir_contestacao_cliente": {"success": True}, "enviar_sms": {"success": False, "sms_sent": False, "sms_not_send_error": True}, }) 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["sms_not_send_error"] is True assert "sms_sent" not in result @pytest.mark.asyncio async def test_candidato_explicito_e_contestado_mesmo_sem_cancelamento_bem_sucedido(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i = 0; self.calls=[] async def arun(self, name, payload, execution_id=None): self.i += 1; self.calls.append((name, payload)) if self.i == 1: return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "results": [{"success": False, "subject": "Inexistente", "reason": "service_not_found", "error": "não encontrado"}], "contestation_candidates": [{"success": False, "subject": "Inexistente", "msisdn": payload["msisdn"], "iu_reason": "service_not_found"}], }}) return _result(name, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-CANDIDATO"}, "abrir_contestacao_cliente": {"success": True, "contested_items": [{"itemName": "Inexistente", "status": "INICIADA"}]}, }) 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": "Inexistente", "valor": "14,99"}) assert [x[0] for x in runtime.calls] == ["cancelamento_vas_avulso", "contestacao_tool"] assert runtime.calls[1][1]["items"][0]["itemName"] == "Inexistente" assert runtime.calls[1][1]["items"][0]["claimedAmount"] == "14,99" assert result["contestacao_protocol"] == "PRT-CANDIDATO" @pytest.mark.asyncio @pytest.mark.parametrize("reason,error", [("block_vas_failed", "Timeout no bloqueio"), ("cancel_vas_failed", "Erro no cancelamento")]) async def test_falha_parcial_rt01_ainda_dispara_rt02(monkeypatch, reason, error): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i=0; self.calls=[] async def arun(self, name, payload, execution_id=None): self.i += 1; self.calls.append((name,payload)) if self.i == 1: return _result(name, "COMPLETED", {"cancelar_vas_avulso": { "results": [{"success": False, "subject": "TIM Fashion Mensal", "reason": reason, "error": error}], "nao_cancelados": [{"servico": "TIM Fashion Mensal", "motivo": reason, "erro": error}], "contestation_candidates": [{"subject": "TIM Fashion Mensal", "msisdn": payload["msisdn"], "motivo": reason, "erro": error}], }}) return _result(name, "COMPLETED", { "registrar_protocolo": {"protocolo_id": "PRT-RT02"}, "abrir_contestacao_cliente": {"success": True, "contested_items": [{"itemName": "TIM Fashion Mensal", "status": "INICIADA"}]}, }) 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 [x[0] for x in runtime.calls] == ["cancelamento_vas_avulso","contestacao_tool"] assert result["contestacao_protocol"] == "PRT-RT02" @pytest.mark.asyncio async def test_holder_msisdn_nao_e_trocado_pela_linha_dependente(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i=0; self.calls=[] async def arun(self,name,payload,execution_id=None): self.i += 1; self.calls.append((name,payload)) if self.i == 1: return _result(name,"COMPLETED",{"cancelar_vas_avulso":{ "results":[{"success":True,"subject":"TIM Fashion Mensal","msisdn":"5511888880000"}], "contestation_candidates":[{"success":True,"subject":"TIM Fashion Mensal","msisdn":"5511888880000"}], }}) 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", "holder_msisdn":"5511999999999", "items":[{"msisdn":"5511888880000","name":"TIM Fashion Mensal","value":"14,99"}], }) assert runtime.calls[0][1]["msisdn"] == "5511999999999" assert runtime.calls[1][1]["msisdn"] == "5511999999999" assert runtime.calls[1][1]["dependent_invoice_item"] is True @pytest.mark.asyncio async def test_item_do_titular_nao_e_marcado_como_dependente(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i=0; self.calls=[] async def arun(self,name,payload,execution_id=None): self.i += 1; self.calls.append((name,payload)) if self.i == 1: return _result(name,"COMPLETED",{"cancelar_vas_avulso":{ "results":[{"success":True,"subject":"TIM Games","msisdn":"5511999999999"}], "contestation_candidates":[{"success":True,"subject":"TIM Games","msisdn":"5511999999999"}], }}) return _result(name,"COMPLETED",{"registrar_protocolo":{"protocolo_id":"PRT-TIT"},"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":"5511999999999","subject":"TIM Games","valor":"9,90"}) assert runtime.calls[1][1]["dependent_invoice_item"] is False @pytest.mark.asyncio async def test_titular_e_dependentes_permancem_em_uma_unica_contestacao(monkeypatch): import contas_mcp.servers.contas_mcp_server.main as main class Runtime: def __init__(self): self.i=0; self.calls=[] async def arun(self,name,payload,execution_id=None): self.i += 1; self.calls.append((name,payload)) if self.i == 1: cands=[ {"success":True,"subject":"TIM Games","msisdn":"5511999999999"}, {"success":True,"subject":"TIM Fashion Mensal","msisdn":"5511888880000"}, {"success":True,"subject":"Tamboro Mensal","msisdn":"5511777770000"}, ] return _result(name,"COMPLETED",{"cancelar_vas_avulso":{"results":cands,"contestation_candidates":cands}}) return _result(name,"COMPLETED",{"registrar_protocolo":{"protocolo_id":"PRT-FAM-3"},"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":"5511999999999", "items":[ {"msisdn":"5511999999999","name":"TIM Games","value":"9,90"}, {"msisdn":"5511888880000","name":"TIM Fashion Mensal","value":"14,99"}, {"msisdn":"5511777770000","name":"Tamboro Mensal","value":"8,50"}, ], }) assert [x[0] for x in runtime.calls] == ["cancelamento_vas_avulso","contestacao_tool"] contest=runtime.calls[1][1] assert contest["msisdn"] == "5511999999999" assert {x["itemName"] for x in contest["items"]} == {"TIM Games","TIM Fashion Mensal","Tamboro Mensal"} assert contest["dependent_invoice_item"] is True