"""Paridade do compositor determinístico com o prompt ``fluxo_vas_cancelamento_resposta_cliente`` (regras 1.1, 2–8). Os goldens de sucesso/erro/SMS vêm dos próprios exemplos do prompt em ``builtin/prompts/vas_cancellation_response.yaml``. """ from __future__ import annotations from app.domain.contas.vas_cancellation_message import ( compose_vas_cancellation_message, ) PRT = "PRT475D8F1C1F" def test_regra_3_sucesso_com_sms_singular() -> None: # Exemplo do prompt: "Exemplo (sucesso com SMS)". msg = compose_vas_cancellation_message( { "contested_items": [{"itemName": "Tamboro Mensal"}], "contested_invoice_amount_open": "22,00", "sms_sent": True, "sms_not_send_error": False, "msisdn": "11999999999", "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento foi concluído com sucesso. O valor contestado foi retirado " "da sua fatura. Enviamos um SMS com o novo código de barras e o valor " "atualizado. O prazo para pagamento é de 4 dias. " f"Seu número de protocolo é {PRT}." ) def test_regra_3_e_6_2_falha_no_sms_plural() -> None: # Exemplo do prompt: "Exemplo (sucesso com falha no SMS)". msg = compose_vas_cancellation_message( { "contested_items": [ {"itemName": "Tamboro Mensal"}, {"itemName": "TIM Fashion"}, ], "contested_invoice_amount_open": "22,00", "sms_sent": True, "sms_not_send_error": True, "msisdn": "11999999999", "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento dos itens foi concluído com sucesso. O valor total de " "R$ 22,00 foi retirado da sua fatura. Identificamos uma instabilidade no " "envio do SMS com o novo código para pagamento. Você pode consultar o " "código atualizado e o prazo de pagamento diretamente no app Meu TIM. " f"Seu número de protocolo é {PRT}." ) def test_regra_2_credito_plural_com_regra_5() -> None: # Baseado no "Exemplo (crédito em fatura)" do prompt; o conector "No entanto," # era floreio do LLM — a forma determinística segue o template literal da regra 5. msg = compose_vas_cancellation_message( { "contested_items": [ {"itemName": "Aluguel de Série 1"}, {"itemName": "Aluguel de Série 2"}, ], "not_contested_items": [{"itemName": "Tamboro Mensal"}], "contested_invoice_amount_open": "22,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento dos itens foi feito com sucesso. O crédito no valor total " "de R$ 22,00 ficou registrado como crédito para sua próxima fatura. " "Não foi possível contestar o item Tamboro Mensal. " f"Seu número de protocolo é {PRT}." ) def test_regra_2_credito_singular() -> None: msg = compose_vas_cancellation_message( { "contested_items": [{"itemName": "Tamboro Mensal"}], "contested_invoice_amount_open": "22,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento do item Tamboro Mensal no valor de R$ 22,00 foi concluído " "com sucesso. O valor contestado ficou registrado como crédito para sua " f"próxima fatura. Seu número de protocolo é {PRT}." ) def test_regra_8_erro_terminal_copia_literal_e_protocolo() -> None: # Exemplo do prompt: "Exemplo (falha, usando um dos textos de ...)". erro = ( "Verifiquei aqui que este item já foi contestado anteriormente, por isso " "não consigo seguir com uma nova contestação. Peço que aguarde um instante." ) msg = compose_vas_cancellation_message( { "contestation_error_description": erro, "contestacao_protocol": PRT, } ) assert msg == f"{erro} Seu número de protocolo é {PRT}." def test_regra_8_adiciona_ponto_quando_texto_nao_termina_em_pontuacao() -> None: msg = compose_vas_cancellation_message( { "contestation_error_description": "Não foi possível processar", "contestacao_protocol": PRT, } ) assert msg == f"Não foi possível processar. Seu número de protocolo é {PRT}." def test_regra_8_item_ja_contestado_informa_cancelamentos_realizados() -> None: msg = compose_vas_cancellation_message( { "cancelados": [{"servico": "A"}, {"servico": "B"}], "contestation_error_description": ( "Verifiquei aqui que estes itens já foram contestados anteriormente, " "por isso não consigo seguir com uma nova contestação." ), "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento dos itens A e B foi concluído com sucesso. " "Verifiquei aqui que estes itens já foram contestados anteriormente, " "por isso não consigo seguir com uma nova contestação. " f"Seu número de protocolo é {PRT}." ) def test_regra_1_1_misto_credito() -> None: msg = compose_vas_cancellation_message( { "itens_ja_contestados": ["Tamboro Mensal"], "contested_items": [{"itemName": "TIM Fashion"}], "contested_invoice_amount_open": "10,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "Identifiquei que o item Tamboro Mensal já havia sido contestado " "anteriormente e, por esse motivo, não é possível registrar uma nova " "contestação para ele. Já a contestação do item TIM Fashion no valor de " "R$ 10,00 foi concluída com sucesso. O valor contestado ficou registrado " f"como crédito para sua próxima fatura. Seu número de protocolo é {PRT}." ) def test_regra_1_1_misto_com_sms_aplica_regra_6() -> None: msg = compose_vas_cancellation_message( { "itens_ja_contestados": ["A", "B"], "contested_items": [{"itemName": "C"}, {"itemName": "D"}], "contested_invoice_amount_open": "30,00", "sms_sent": True, "sms_not_send_error": False, "msisdn": "11988887777", "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento foi concluído com sucesso. O valor contestado foi retirado " "da sua fatura. Enviamos um SMS com o novo código de barras e o valor " "atualizado. O prazo para pagamento é de 4 dias. " f"Seu número de protocolo é {PRT}." ) def test_sms_enviado_prevalece_quando_contested_items_esta_vazio() -> None: # Regressão do trace 6ae8bd24: SMS efetivamente enviado nunca vira crédito. msg = compose_vas_cancellation_message( { "itens_ja_contestados": ["Tamboro Mensal"], "contested_items": [], "contested_invoice_amount_open": "22,00", "sms_sent": True, "contestacao_protocol": PRT, } ) assert msg == ( "O cancelamento foi concluído com sucesso. O valor contestado foi retirado " "da sua fatura. Enviamos um SMS com o novo código de barras e o valor " "atualizado. O prazo para pagamento é de 4 dias. " f"Seu número de protocolo é {PRT}." ) assert "próxima fatura" not in msg def test_regra_7_protocolos_multiplos() -> None: msg = compose_vas_cancellation_message( { "contested_items": [{"itemName": "Tamboro Mensal"}], "contested_invoice_amount_open": "22,00", "sms_sent": False, "contestacao_protocols": ["PRTAAA", "PRTBBB"], } ) assert msg.endswith("Seus números de protocolo são PRTAAA e PRTBBB.") def test_no_match_credito_todos_nao_encontrados_singular() -> None: # Cenário 2 (crédito): serviço não localizado na plataforma; valor contestado. msg = compose_vas_cancellation_message( { "success": True, "contested_items": [{"itemName": "Tamboro Mensal"}], "nao_encontrados": [{"servico": "Tamboro Mensal"}], "cancelados": [], "contested_invoice_amount_open": "22,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "O valor total de R$ 22,00 ficou registrado como crédito para sua próxima " "fatura. Sobre o cancelamento do serviço Tamboro Mensal, não consegui " "realizar por aqui. Você pode cancelar pelo App Meu TIM, na opção " f"Gerenciar Benefícios. Seu número de protocolo é {PRT}." ) def test_no_match_sms_todos_nao_encontrados() -> None: # Cenário 2 (SMS/boleto): valor retirado + SMS + no-match. msg = compose_vas_cancellation_message( { "success": True, "contested_items": [{"itemName": "Tamboro Mensal"}], "nao_encontrados": [{"servico": "Tamboro Mensal"}], "cancelados": [], "contested_invoice_amount_open": "22,00", "sms_sent": True, "sms_not_send_error": False, "msisdn": "11999999999", "contestacao_protocol": PRT, } ) assert msg == ( "O valor total de R$ 22,00 foi retirado da sua fatura. Enviamos um SMS com " "o novo código de barras e o valor atualizado. O prazo para pagamento é de " "4 dias. Sobre o cancelamento do serviço Tamboro " "Mensal, não consegui realizar por aqui. Você pode cancelar pelo App Meu " f"TIM, na opção Gerenciar Benefícios. Seu número de protocolo é {PRT}." ) def test_no_match_misto_credito() -> None: # Cenário 3 (crédito): A,B,C cancelados; D,E,F no-match. msg = compose_vas_cancellation_message( { "success": True, "contested_items": [{"itemName": x} for x in ("A", "B", "C", "D", "E", "F")], "cancelados": [{"servico": x} for x in ("A", "B", "C")], "nao_encontrados": [{"servico": x} for x in ("D", "E", "F")], "contested_invoice_amount_open": "60,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "O valor total de R$ 60,00 ficou registrado como crédito para sua próxima " "fatura. O cancelamento dos serviços A, B e C foram realizados com sucesso. " "Não consegui realizar por aqui o cancelamento dos serviços D, E e F. Você " "pode cancelá-los pelo App Meu TIM, na opção Gerenciar Benefícios. " f"Seu número de protocolo é {PRT}." ) def test_no_match_misto_sms() -> None: # Cenário 3 (SMS/boleto). msg = compose_vas_cancellation_message( { "success": True, "contested_items": [{"itemName": x} for x in ("A", "B", "C", "D", "E", "F")], "cancelados": [{"servico": x} for x in ("A", "B", "C")], "nao_encontrados": [{"servico": x} for x in ("D", "E", "F")], "contested_invoice_amount_open": "60,00", "sms_sent": True, "sms_not_send_error": False, "msisdn": "11988887777", "contestacao_protocol": PRT, } ) assert msg == ( "O valor total de R$ 60,00 foi retirado da sua fatura. Enviamos um SMS com " "o novo código de barras e o valor atualizado. O prazo para pagamento é de " "4 dias. O cancelamento dos serviços A, B e C foram " "realizados com sucesso. Não consegui realizar por aqui o cancelamento dos " "serviços D, E e F. Você pode cancelá-los pelo App Meu TIM, na opção " f"Gerenciar Benefícios. Seu número de protocolo é {PRT}." ) def test_no_match_misto_singular_cada_lista() -> None: # Concordância: 1 cancelado + 1 no-match. msg = compose_vas_cancellation_message( { "success": True, "contested_items": [{"itemName": "A"}, {"itemName": "B"}], "cancelados": [{"servico": "A"}], "nao_encontrados": [{"servico": "B"}], "contested_invoice_amount_open": "20,00", "sms_sent": False, "contestacao_protocol": PRT, } ) assert msg == ( "O valor total de R$ 20,00 ficou registrado como crédito para sua próxima " "fatura. O cancelamento do serviço A foi realizado com sucesso. Não " "consegui realizar por aqui o cancelamento do serviço B. Você pode " f"cancelá-lo pelo App Meu TIM, na opção Gerenciar Benefícios. Seu número " f"de protocolo é {PRT}." ) def test_sucesso_sem_itens_detalhados_gera_confirmacao_generica() -> None: # Sem itens contestados/não contestados/já contestados, mas cancelamento OK: # confirmação neutra + protocolo (regra 7). msg = compose_vas_cancellation_message( { "success": True, "contested_items": [], "not_contested_items": [], "contestacao_protocol": PRT, } ) assert msg == f"O cancelamento foi concluído com sucesso. Seu número de protocolo é {PRT}." def test_sucesso_sem_itens_e_sem_protocolo_so_confirmacao() -> None: # Protocolo de cancelamento (não de contestação) é anexado pelo backend, não aqui. msg = compose_vas_cancellation_message({"success": True, "contested_items": []}) assert msg == "O cancelamento foi concluído com sucesso." def test_payload_nao_classificavel_sem_sucesso_retorna_vazio() -> None: assert compose_vas_cancellation_message({}) == "" assert compose_vas_cancellation_message({"success": False}) == "" assert compose_vas_cancellation_message("nao-mapping") == "" # type: ignore[arg-type]