Projeto do Agent Contas ORACLE
This commit is contained in:
448
tests/migration/test_original_contestation_validation.py
Normal file
448
tests/migration/test_original_contestation_validation.py
Normal file
@@ -0,0 +1,448 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.guardrails.calibrated import contestation_validation
|
||||
from agent_framework.guardrails.calibrated.contestation_validation import (
|
||||
validate_contestation_items,
|
||||
)
|
||||
|
||||
|
||||
def _vas_invoice(*items: dict[str, Any]) -> dict[str, Any]:
|
||||
return {"Servicos de Valor Adicionado": list(items)}
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_item_inexistente() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Pacote nao cobrado",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
_vas_invoice({"desc": "Pacote cobrado", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert error == "Item 'Pacote nao cobrado' nao encontrado no json da fatura."
|
||||
assert validation_log[-1]["erro"] == "item_nao_encontrado_na_fatura"
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_item_fora_da_secao_vas() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Pacote Dados",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
{"Plano": [{"desc": "Pacote Dados", "valor": "10,00"}]},
|
||||
)
|
||||
|
||||
assert error == "Item 'Pacote Dados' nao e do tipo VAS no json da fatura."
|
||||
assert validation_log[-1]["erro"] == "item_fora_secao_vas"
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_vas_estrategico() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Netflix Premium",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
_vas_invoice({"desc": "Netflix Premium", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert (
|
||||
error
|
||||
== "Item 'Netflix Premium' identificado como VAS estrategico e nao pode ser ajustado."
|
||||
)
|
||||
assert validation_log[-1]["erro"] == "vas_estrategico_nao_permitido"
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_valor_maior_que_o_item() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Pacote Musica",
|
||||
"claimed_amount": "12",
|
||||
"validated_amount": "12",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
_vas_invoice({"desc": "Pacote Musica", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert (
|
||||
error
|
||||
== "Valor de ajuste do item 'Pacote Musica' excede o valor cobrado na fatura."
|
||||
)
|
||||
assert validation_log[-1]["erro"] == "valor_ajuste_maior_que_item"
|
||||
|
||||
|
||||
def _record_observation_factory(records: list[dict[str, Any]]):
|
||||
@contextmanager
|
||||
def _record_observation(
|
||||
*,
|
||||
name: str,
|
||||
input: dict[str, Any] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
):
|
||||
record: dict[str, Any] = {
|
||||
"name": name,
|
||||
"input": input,
|
||||
"metadata": metadata,
|
||||
"updates": [],
|
||||
}
|
||||
|
||||
class _Observation:
|
||||
def update(self, **kwargs: Any) -> None:
|
||||
record["updates"].append(dict(kwargs))
|
||||
|
||||
records.append(record)
|
||||
yield _Observation()
|
||||
|
||||
return _record_observation
|
||||
|
||||
|
||||
def test_validate_contestation_items_emite_spans_quando_bloqueia(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
records: list[dict[str, Any]] = []
|
||||
monkeypatch.setattr(
|
||||
contestation_validation,
|
||||
"_start_guardrail_observation",
|
||||
_record_observation_factory(records),
|
||||
)
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
[
|
||||
{
|
||||
"item_name": "Pacote nao cobrado",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
],
|
||||
_vas_invoice({"desc": "Pacote cobrado", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert error
|
||||
assert [r["name"] for r in records] == [
|
||||
"guardrail.CVAL.evaluated",
|
||||
"guardrail.CVAL.blocked",
|
||||
]
|
||||
|
||||
parent, child = records
|
||||
assert parent["metadata"] == {
|
||||
"mechanism": "guardrail_action_validation",
|
||||
"code": "CVAL",
|
||||
"action": "abrir_contestacao_cliente",
|
||||
}
|
||||
assert parent["input"]["items_count"] == 1
|
||||
assert parent["input"]["invoice_candidates_count"] == 1
|
||||
assert parent["updates"][-1]["level"] == "WARNING"
|
||||
assert parent["updates"][-1]["output"]["approved"] is False
|
||||
assert parent["updates"][-1]["output"]["validation_log"] == validation_log
|
||||
assert (
|
||||
parent["updates"][-1]["output"]["reason"]
|
||||
== "item_nao_encontrado_na_fatura"
|
||||
)
|
||||
|
||||
assert child["metadata"]["reason"] == "item_nao_encontrado_na_fatura"
|
||||
assert child["updates"][-1]["level"] == "WARNING"
|
||||
assert child["updates"][-1]["output"]["blocked"] is True
|
||||
|
||||
|
||||
def test_validate_contestation_items_emite_span_quando_aprova(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
records: list[dict[str, Any]] = []
|
||||
monkeypatch.setattr(
|
||||
contestation_validation,
|
||||
"_start_guardrail_observation",
|
||||
_record_observation_factory(records),
|
||||
)
|
||||
|
||||
items, validation_log, error = validate_contestation_items(
|
||||
[
|
||||
{
|
||||
"item_name": "Pacote Musica",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
],
|
||||
_vas_invoice({"desc": "Pacote Musica", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert error is None
|
||||
assert validation_log[-1]["status"] == "aprovado"
|
||||
assert items[0]["validated_amount"] == "10"
|
||||
assert [r["name"] for r in records] == ["guardrail.CVAL.evaluated"]
|
||||
|
||||
update = records[0]["updates"][-1]
|
||||
assert "level" not in update
|
||||
assert update["output"]["approved"] is True
|
||||
assert update["output"]["items_count"] == 1
|
||||
assert update["output"]["items_validated_count"] == 1
|
||||
assert update["output"]["items_approved_count"] == 1
|
||||
assert update["output"]["items_rejected_count"] == 0
|
||||
assert update["output"]["validation_log"] == validation_log
|
||||
|
||||
|
||||
def test_validate_contestation_items_valida_todos_antes_de_retornar() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Pacote Musica",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
},
|
||||
{
|
||||
"item_name": "Pacote nao cobrado",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
},
|
||||
{
|
||||
"item_name": "Pacote Filme",
|
||||
"claimed_amount": "5",
|
||||
"validated_amount": "5",
|
||||
},
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
_vas_invoice(
|
||||
{"desc": "Pacote Musica", "valor": "10,00"},
|
||||
{"desc": "Pacote Filme", "valor": "5,00"},
|
||||
),
|
||||
)
|
||||
|
||||
assert error == "Item 'Pacote nao cobrado' nao encontrado no json da fatura."
|
||||
statuses = [(entry["item_name"], entry["status"]) for entry in validation_log]
|
||||
assert statuses == [
|
||||
("Pacote Musica", "aprovado"),
|
||||
("Pacote nao cobrado", "reprovado"),
|
||||
("Pacote Filme", "aprovado"),
|
||||
]
|
||||
|
||||
|
||||
def test_validate_contestation_items_usa_classe_avulso_canonica() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "Tamboro Mensal",
|
||||
"claimed_amount": "14.99",
|
||||
"validated_amount": "14.99",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
{
|
||||
"Secao Qualquer": [
|
||||
{
|
||||
"desc": "Tamboro Mensal",
|
||||
"value": 14.99,
|
||||
"classe": "avulso",
|
||||
"verb": "cancelar",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert error is None
|
||||
assert validation_log[-1]["status"] == "aprovado"
|
||||
assert validation_log[-1]["secao_vas"] is True
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_classe_estrategico_canonica() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "ServicoNovo",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
{
|
||||
"Servicos de Valor Adicionado": [
|
||||
{
|
||||
"desc": "ServicoNovo",
|
||||
"value": 10.0,
|
||||
"classe": "estrategico",
|
||||
"estrategico": True,
|
||||
"verb": "falar sobre",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert (
|
||||
error
|
||||
== "Item 'ServicoNovo' identificado como VAS estrategico e nao pode ser ajustado."
|
||||
)
|
||||
assert validation_log[-1]["erro"] == "vas_estrategico_nao_permitido"
|
||||
assert validation_log[-1]["vas_estrategico"] is True
|
||||
|
||||
|
||||
def test_validate_contestation_items_span_blocked_inclui_contagens(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
records: list[dict[str, Any]] = []
|
||||
monkeypatch.setattr(
|
||||
contestation_validation,
|
||||
"_start_guardrail_observation",
|
||||
_record_observation_factory(records),
|
||||
)
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
[
|
||||
{
|
||||
"item_name": "Pacote Musica",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
},
|
||||
{
|
||||
"item_name": "Pacote nao cobrado",
|
||||
"claimed_amount": "10",
|
||||
"validated_amount": "10",
|
||||
},
|
||||
],
|
||||
_vas_invoice({"desc": "Pacote Musica", "valor": "10,00"}),
|
||||
)
|
||||
|
||||
assert error
|
||||
assert [r["name"] for r in records] == [
|
||||
"guardrail.CVAL.evaluated",
|
||||
"guardrail.CVAL.blocked",
|
||||
]
|
||||
parent, child = records
|
||||
parent_output = parent["updates"][-1]["output"]
|
||||
assert parent_output["items_validated_count"] == 2
|
||||
assert parent_output["items_approved_count"] == 1
|
||||
assert parent_output["items_rejected_count"] == 1
|
||||
assert parent_output["validation_log"] == validation_log
|
||||
|
||||
child_output = child["updates"][-1]["output"]
|
||||
assert child_output["items_validated_count"] == 2
|
||||
assert child_output["items_approved_count"] == 1
|
||||
assert child_output["items_rejected_count"] == 1
|
||||
|
||||
|
||||
def test_validate_contestation_items_bloqueia_quando_total_ajustes_excede_fatura() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "VAS Musica",
|
||||
"claimed_amount": "100",
|
||||
"validated_amount": "100",
|
||||
},
|
||||
{
|
||||
"item_name": "VAS Filme",
|
||||
"claimed_amount": "100",
|
||||
"validated_amount": "100",
|
||||
},
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
{
|
||||
"Fatura Resumo": [{"desc": "Total geral", "value": 150.0}],
|
||||
"Servicos de Valor Adicionado": [
|
||||
{"desc": "VAS Musica", "valor": "100,00"},
|
||||
{"desc": "VAS Filme", "valor": "100,00"},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert error == (
|
||||
"Valor total de ajustes (200) excede o valor total da fatura (150)."
|
||||
)
|
||||
total_entry = validation_log[-1]
|
||||
assert total_entry["erro"] == "total_ajustes_excede_fatura"
|
||||
assert total_entry["valor_total_ajustes"] == "200"
|
||||
assert total_entry["valor_total_fatura"] == "150"
|
||||
|
||||
|
||||
def test_validate_contestation_items_aprova_quando_total_ajustes_dentro_do_limite() -> None:
|
||||
items = [
|
||||
{
|
||||
"item_name": "VAS Musica",
|
||||
"claimed_amount": "50",
|
||||
"validated_amount": "50",
|
||||
}
|
||||
]
|
||||
|
||||
_, validation_log, error = validate_contestation_items(
|
||||
items,
|
||||
{
|
||||
"Fatura Resumo": [{"desc": "Total geral", "value": 150.0}],
|
||||
"Servicos de Valor Adicionado": [
|
||||
{"desc": "VAS Musica", "valor": "50,00"},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert error is None
|
||||
assert validation_log[-1]["status"] == "aprovado"
|
||||
|
||||
|
||||
def test_contestation_action_classifies_already_contested_and_missing_requested_item():
|
||||
from app.domain.contas.service import ContasDomainService
|
||||
from app.domain.contas.workflow_actions import build_contas_workflow_actions
|
||||
|
||||
class Client:
|
||||
def billing_analysis(self, msisdn):
|
||||
return {"currentInvoice": {"items": [{"description": "TIM Fashion Mensal", "value": 14.99}, {"description": "Outro VAS", "value": 5.0}]}}
|
||||
def contestar(self, payload):
|
||||
return {"itemsResponse": [
|
||||
{"itemName": "TIM Fashion Mensal", "message": "Item ja foi contestado anteriormente", "status": "REJEITADA"}
|
||||
]}
|
||||
|
||||
action = build_contas_workflow_actions(ContasDomainService(client=Client())).get("abrir_contestacao_cliente")
|
||||
result = action({
|
||||
"msisdn": "11999999999", "skip_invoice_item_validation": True,
|
||||
"items": [
|
||||
{"itemName": "TIM Fashion Mensal", "claimedAmount": "14,99", "validatedAmount": "14,99"},
|
||||
{"itemName": "Outro VAS", "claimedAmount": "5,00", "validatedAmount": "5,00"},
|
||||
],
|
||||
}, {"input": {}})
|
||||
assert result["itens_ja_contestados"] == ["TIM Fashion Mensal"]
|
||||
assert result["contested_items"] == []
|
||||
assert result["not_contested_items"][0]["itemName"] == "Outro VAS"
|
||||
assert result["contested_invoice_amount_open"] == "0.00"
|
||||
|
||||
|
||||
def test_contestation_action_calculates_totals_only_for_items_actually_contested():
|
||||
from app.domain.contas.service import ContasDomainService
|
||||
from app.domain.contas.workflow_actions import build_contas_workflow_actions
|
||||
|
||||
class Client:
|
||||
def contestar(self, payload):
|
||||
return {"itemsResponse": [
|
||||
{"itemName": "TIM Fashion Mensal", "status": "INICIADA"},
|
||||
{"itemName": "Outro VAS", "status": "REJEITADA", "message": "fora da alcada"},
|
||||
]}
|
||||
|
||||
action = build_contas_workflow_actions(ContasDomainService(client=Client())).get("abrir_contestacao_cliente")
|
||||
result = action({
|
||||
"msisdn": "11999999999", "skip_invoice_item_validation": True,
|
||||
"items": [
|
||||
{"itemName": "TIM Fashion Mensal", "claimedAmount": "14,99", "validatedAmount": "14,99"},
|
||||
{"itemName": "Outro VAS", "claimedAmount": "5,00", "validatedAmount": "5,00"},
|
||||
],
|
||||
}, {"input": {}})
|
||||
assert [x["itemName"] for x in result["contested_items"]] == ["TIM Fashion Mensal"]
|
||||
assert [x["itemName"] for x in result["not_contested_items"]] == ["Outro VAS"]
|
||||
assert result["contested_invoice_amount_open"] == "14.99"
|
||||
assert result["contested_invoice_amount"] == "14.99"
|
||||
Reference in New Issue
Block a user