112 lines
4.8 KiB
Python
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
|