Files
agent_contas/tests/migration/test_framework_native_structure.py

112 lines
4.8 KiB
Python

from __future__ import annotations
import pytest
import re
from pathlib import Path
import yaml
from agent_framework.workflows import FileWorkflowRepository
from app.domain.contas import ContasDomainService
from app.domain.contas.workflow_actions import build_contas_workflow_actions
ROOT = Path(__file__).resolve().parents[2]
def test_domain_does_not_import_langgraph_directly():
violations = []
for base in (ROOT / "app", ROOT / "mcp"):
for path in base.rglob("*.py"):
text = path.read_text(encoding="utf-8", errors="ignore")
if re.search(r"(^|\n)\s*(from\s+langgraph|import\s+langgraph)", text):
violations.append(str(path.relative_to(ROOT)))
assert not violations
def test_official_templates_use_framework_state_graph():
for rel in (
"agent_framework_oci/templates/agent_template_backend/app/workflows/agent_graph.py",
"agent_framework_oci/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py",
):
text = (ROOT / rel).read_text(encoding="utf-8")
assert "FrameworkStateGraph" in text
assert "from langgraph.graph" not in text
def test_all_active_workflow_actions_registered(monkeypatch):
monkeypatch.setenv("TIM_USE_MOCK_GATEWAY", "true")
registry = build_contas_workflow_actions(ContasDomainService())
registered = set(registry._actions)
repo = FileWorkflowRepository(ROOT / "workflows")
active = []
required = set()
for marker in sorted((ROOT / "workflows").glob("*.active.yaml")):
name = marker.name.removesuffix(".active.yaml")
definition = repo.get_active(name)
active.append((name, definition.version))
required.update(node.action for node in definition.nodes)
assert len(active) == 10
assert required <= registered, sorted(required - registered)
def test_guardrail_config_declares_all_execution_stages():
raw = yaml.safe_load((ROOT / "config" / "guardrails.yaml").read_text(encoding="utf-8"))
assert raw.get("input")
assert raw.get("output")
assert raw.get("retrieval")
assert raw.get("tool")
def test_contestacao_workflow_encerra_imediatamente_quando_cval_bloqueia():
import yaml
from pathlib import Path
wf = yaml.safe_load((Path(__file__).parents[2] / "workflows" / "contestacao_tool.v2.yaml").read_text())
edges = wf["edges"]
blocked = [e for e in edges if e.get("from") == "abrir_contestacao_cliente" and e.get("to") == "END"]
assert blocked
assert blocked[0]["priority"] == 1
assert blocked[0]["when"] == {"eq": ["$.vars.abrir_contestacao_cliente.success", False]}
@pytest.mark.asyncio
async def test_workflow_runtime_preserva_snapshot_parcial_quando_action_posterior_falha(tmp_path, monkeypatch):
from types import SimpleNamespace
from agent_framework.workflows.models import WorkflowDefinition, WorkflowNode
from agent_framework.workflows.runtime import WorkflowRuntime
class Repo:
def get_active(self, name):
return WorkflowDefinition(name=name, version=1, start="a", nodes=[WorkflowNode(id="a", action="noop")], edges=[])
class Graph:
async def ainvoke(self, initial, config=None):
raise RuntimeError("API contestacao indisponivel")
async def aget_state(self, config=None):
return SimpleNamespace(values={
"nodes": {"registrar_protocolo": {"protocolo_id": "PRT-123"}},
"trace": [{"node": "registrar_protocolo", "status": "COMPLETED"}],
"input": {"msisdn": "119"},
})
runtime = WorkflowRuntime(Repo()) # type: ignore[arg-type]
monkeypatch.setattr(runtime, "_compile", lambda definition: Graph())
result = await runtime.arun("contestacao_tool", {"msisdn": "119"}, execution_id="exec-1")
assert result.status == "FAILED"
assert result.output["registrar_protocolo"]["protocolo_id"] == "PRT-123"
assert result.trace == [{"node": "registrar_protocolo", "status": "COMPLETED"}]
assert result.state["input"]["msisdn"] == "119"
def test_agent_workflow_init_realmente_inicializa_router_agentes_e_graph():
import ast
from pathlib import Path
source = (Path(__file__).parents[2] / "app" / "workflows" / "agent_graph.py").read_text()
tree = ast.parse(source)
cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "AgentWorkflow")
init = next(n for n in cls.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name == "__init__")
assigned = set()
for node in ast.walk(init):
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name) and target.value.id == "self":
assigned.add(target.attr)
assert {"router", "faturas", "vas", "contestacao", "suporte_contas", "graph"} <= assigned