Projeto do Agent Contas ORACLE
This commit is contained in:
132
tests/migration/test_original_workflow_cases.py
Normal file
132
tests/migration/test_original_workflow_cases.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from agent_framework.workflows import FileWorkflowRepository, WorkflowActionRegistry, WorkflowRuntime
|
||||
from agent_framework.workflows.models import WorkflowDefinition
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
CASES = ROOT / "tests" / "migration" / "data" / "workflow_yaml_cases"
|
||||
|
||||
|
||||
def _load_cases():
|
||||
return [json.loads(p.read_text(encoding="utf-8")) for p in sorted(CASES.glob("*.json"))]
|
||||
|
||||
|
||||
def _contains_subset(expected: Any, observed: Any) -> bool:
|
||||
if isinstance(expected, dict):
|
||||
return isinstance(observed, dict) and all(
|
||||
key in observed and _contains_subset(value, observed[key]) for key, value in expected.items()
|
||||
)
|
||||
if isinstance(expected, list):
|
||||
return isinstance(observed, list) and len(expected) <= len(observed) and all(
|
||||
_contains_subset(value, observed[index]) for index, value in enumerate(expected)
|
||||
)
|
||||
return expected == observed
|
||||
|
||||
|
||||
class _DefinitionRepository:
|
||||
def __init__(self, definition):
|
||||
self.definition = definition
|
||||
|
||||
def get_active(self, name):
|
||||
assert name == self.definition.name
|
||||
return self.definition
|
||||
|
||||
def get_version(self, name, version):
|
||||
assert (name, version) == (self.definition.name, self.definition.version)
|
||||
return self.definition
|
||||
|
||||
|
||||
def _normalized_expected_nodes(case):
|
||||
nodes = list((case.get("expect") or {}).get("trace_nodes") or [])
|
||||
# Runtime antigo repetia a action imediatamente anterior ao pause. O runtime
|
||||
# novo deliberadamente não a repete para impedir side effects duplicados.
|
||||
normalized = []
|
||||
for node in nodes:
|
||||
if normalized and normalized[-1] == node:
|
||||
continue
|
||||
normalized.append(node)
|
||||
return normalized
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("case", _load_cases(), ids=lambda c: c["id"])
|
||||
async def test_original_workflow_case_on_framework_runtime(case):
|
||||
original = FileWorkflowRepository(ROOT / "workflows").get_version(case["workflow"], int(case["version"]))
|
||||
raw = original.model_dump(by_alias=True)
|
||||
registry = WorkflowActionRegistry()
|
||||
calls = []
|
||||
|
||||
# No teste cada node recebe uma action exclusiva. Isso permite reproduzir
|
||||
# exatamente os outputs configurados no caso JSON, mesmo quando dois nodes
|
||||
# compartilham a mesma action de produção.
|
||||
for node in raw["nodes"]:
|
||||
node_id = node["id"]
|
||||
real_action = node["action"]
|
||||
test_action = f"__case__{node_id}"
|
||||
node["action"] = test_action
|
||||
|
||||
async def handler(params, state, *, _node=node_id, _action=real_action):
|
||||
cfgs = dict(case.get("actions") or {})
|
||||
configured = cfgs.get(_node) if isinstance(cfgs.get(_node), dict) else cfgs.get(_action)
|
||||
configured = configured if isinstance(configured, dict) else {}
|
||||
calls.append({"node_id": _node, "action": _action, "params": deepcopy(params)})
|
||||
if configured.get("success") is False:
|
||||
raise RuntimeError(str(configured.get("error") or "falha configurada"))
|
||||
return deepcopy(configured.get("output") or {"success": True})
|
||||
|
||||
registry.register(test_action, handler)
|
||||
|
||||
definition = WorkflowDefinition.model_validate(raw)
|
||||
runtime = WorkflowRuntime(_DefinitionRepository(definition), actions=registry, checkpointer=None, allow_deterministic_fallback=True)
|
||||
first = await runtime.arun(definition.name, deepcopy(case.get("input") or {}), version=definition.version)
|
||||
|
||||
start_expect = case.get("expect_start")
|
||||
if isinstance(start_expect, dict):
|
||||
if start_expect.get("status") == "WAITING_INPUT":
|
||||
assert first.status == "PAUSED"
|
||||
if "paused_at" in start_expect:
|
||||
assert first.pause.get("node") == start_expect["paused_at"]
|
||||
expected_input = first.pause.get("expected_input") or {}
|
||||
if "expected_input_key" in start_expect:
|
||||
assert expected_input.get("key") == start_expect["expected_input_key"]
|
||||
if "allowed_values" in start_expect:
|
||||
assert expected_input.get("allowed_values") == start_expect["allowed_values"]
|
||||
|
||||
final = first
|
||||
resume = case.get("resume")
|
||||
if isinstance(resume, dict):
|
||||
final = await runtime.aresume(definition.name, first.execution_id, deepcopy(resume), version=definition.version)
|
||||
|
||||
expect = dict(case.get("expect") or {})
|
||||
assert final.status == expect.get("status")
|
||||
if "last_node" in expect:
|
||||
assert final.state.get("current_node") == expect["last_node"]
|
||||
|
||||
# Data do runtime antigo era a união dos outputs. ``vars`` representa essa
|
||||
# informação no runtime novo e é a fonte para referências $.vars.* do YAML.
|
||||
flattened = {}
|
||||
for value in (final.state.get("vars") or {}).values():
|
||||
if isinstance(value, dict):
|
||||
flattened.update(value)
|
||||
if "data_subset" in expect:
|
||||
assert _contains_subset(expect["data_subset"], flattened)
|
||||
|
||||
observed_nodes = [item.get("node") for item in final.trace if item.get("action") != "pause_resume"]
|
||||
if "trace_nodes" in expect:
|
||||
active_node_ids = {node.id for node in definition.nodes}
|
||||
expected_nodes = [node for node in _normalized_expected_nodes(case) if node in active_node_ids]
|
||||
assert observed_nodes == expected_nodes
|
||||
|
||||
for expected in expect.get("calls", []):
|
||||
found = [c for c in calls if c["node_id"] == expected.get("node_id") and ("action" not in expected or c["action"] == expected["action"])]
|
||||
assert found, expected
|
||||
subset = expected.get("params_subset")
|
||||
if subset is not None:
|
||||
assert any(_contains_subset(subset, c["params"]) for c in found)
|
||||
Reference in New Issue
Block a user