353 lines
14 KiB
Python
353 lines
14 KiB
Python
"""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]
|