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