Linting, pre-commit
This commit is contained in:
@ -1,15 +1,17 @@
|
||||
import pytest
|
||||
|
||||
from falyx.action import Action, ChainedAction, LiteralInputAction, FallbackAction
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.action import Action, ChainedAction, FallbackAction, LiteralInputAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
|
||||
# --- Helpers ---
|
||||
async def capturing_hook(context: ExecutionContext):
|
||||
context.extra["hook_triggered"] = True
|
||||
|
||||
|
||||
# --- Fixtures ---
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_registry():
|
||||
@ -18,7 +20,6 @@ def clean_registry():
|
||||
er.clear()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_callable():
|
||||
"""Test if Action can be created with a callable."""
|
||||
@ -26,15 +27,22 @@ async def test_action_callable():
|
||||
result = await action()
|
||||
assert result == "Hello, World!"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_async_callable():
|
||||
"""Test if Action can be created with an async callable."""
|
||||
|
||||
async def async_callable():
|
||||
return "Hello, World!"
|
||||
|
||||
action = Action("test_action", async_callable)
|
||||
result = await action()
|
||||
assert result == "Hello, World!"
|
||||
assert str(action) == "Action(name='test_action', action=async_callable, args=(), kwargs={}, retry=False)"
|
||||
assert (
|
||||
str(action)
|
||||
== "Action(name='test_action', action=async_callable, args=(), kwargs={}, retry=False)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_non_callable():
|
||||
@ -42,11 +50,15 @@ async def test_action_non_callable():
|
||||
with pytest.raises(TypeError):
|
||||
Action("test_action", 42)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("return_list, expected", [
|
||||
(True, [1, 2, 3]),
|
||||
(False, 3),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"return_list, expected",
|
||||
[
|
||||
(True, [1, 2, 3]),
|
||||
(False, 3),
|
||||
],
|
||||
)
|
||||
async def test_chained_action_return_modes(return_list, expected):
|
||||
chain = ChainedAction(
|
||||
name="Simple Chain",
|
||||
@ -55,19 +67,23 @@ async def test_chained_action_return_modes(return_list, expected):
|
||||
Action(name="two", action=lambda: 2),
|
||||
Action(name="three", action=lambda: 3),
|
||||
],
|
||||
return_list=return_list
|
||||
return_list=return_list,
|
||||
)
|
||||
|
||||
result = await chain()
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("return_list, auto_inject, expected", [
|
||||
(True, True, [1, 2, 3]),
|
||||
(True, False, [1, 2, 3]),
|
||||
(False, True, 3),
|
||||
(False, False, 3),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"return_list, auto_inject, expected",
|
||||
[
|
||||
(True, True, [1, 2, 3]),
|
||||
(True, False, [1, 2, 3]),
|
||||
(False, True, 3),
|
||||
(False, False, 3),
|
||||
],
|
||||
)
|
||||
async def test_chained_action_literals(return_list, auto_inject, expected):
|
||||
chain = ChainedAction(
|
||||
name="Literal Chain",
|
||||
@ -79,6 +95,7 @@ async def test_chained_action_literals(return_list, auto_inject, expected):
|
||||
result = await chain()
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_literal_input_action():
|
||||
"""Test if LiteralInputAction can be created and used."""
|
||||
@ -88,6 +105,7 @@ async def test_literal_input_action():
|
||||
assert action.value == "Hello, World!"
|
||||
assert str(action) == "LiteralInputAction(value='Hello, World!')"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fallback_action():
|
||||
"""Test if FallbackAction can be created and used."""
|
||||
@ -102,4 +120,3 @@ async def test_fallback_action():
|
||||
result = await chain()
|
||||
assert result == "Fallback value"
|
||||
assert str(action) == "FallbackAction(fallback='Fallback value')"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pickle
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from falyx.action import ProcessAction
|
||||
@ -7,17 +8,21 @@ from falyx.execution_registry import ExecutionRegistry as er
|
||||
|
||||
# --- Fixtures ---
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_registry():
|
||||
er.clear()
|
||||
yield
|
||||
er.clear()
|
||||
|
||||
|
||||
def slow_add(x, y):
|
||||
return x + y
|
||||
|
||||
|
||||
# --- Tests ---
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_action_executes_correctly():
|
||||
with warnings.catch_warnings():
|
||||
@ -27,8 +32,10 @@ async def test_process_action_executes_correctly():
|
||||
result = await action()
|
||||
assert result == 5
|
||||
|
||||
|
||||
unpickleable = lambda x: x + 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_action_rejects_unpickleable():
|
||||
with warnings.catch_warnings():
|
||||
@ -37,4 +44,3 @@ async def test_process_action_rejects_unpickleable():
|
||||
action = ProcessAction(name="proc_fail", func=unpickleable, args=(2,))
|
||||
with pytest.raises(pickle.PicklingError, match="Can't pickle"):
|
||||
await action()
|
||||
|
||||
|
@ -6,6 +6,7 @@ from falyx.retry_utils import enable_retries_recursively
|
||||
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
|
||||
# --- Fixtures ---
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_registry():
|
||||
@ -13,6 +14,7 @@ def clean_registry():
|
||||
yield
|
||||
er.clear()
|
||||
|
||||
|
||||
def test_action_enable_retry():
|
||||
"""Test if Action can be created with retry=True."""
|
||||
action = Action("test_action", lambda: "Hello, World!", retry=True)
|
||||
|
@ -1,16 +1,18 @@
|
||||
import pytest
|
||||
|
||||
from falyx.action import Action, ChainedAction, ActionGroup, FallbackAction
|
||||
from falyx.action import Action, ActionGroup, ChainedAction, FallbackAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookManager, HookType
|
||||
from falyx.context import ExecutionContext
|
||||
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
|
||||
# --- Helpers ---
|
||||
async def capturing_hook(context: ExecutionContext):
|
||||
context.extra["hook_triggered"] = True
|
||||
|
||||
|
||||
# --- Fixtures ---
|
||||
@pytest.fixture
|
||||
def hook_manager():
|
||||
@ -18,29 +20,33 @@ def hook_manager():
|
||||
hm.register(HookType.BEFORE, capturing_hook)
|
||||
return hm
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_registry():
|
||||
er.clear()
|
||||
yield
|
||||
er.clear()
|
||||
|
||||
|
||||
# --- Tests ---
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_runs_correctly():
|
||||
async def dummy_action(x: int = 0) -> int: return x + 1
|
||||
async def dummy_action(x: int = 0) -> int:
|
||||
return x + 1
|
||||
|
||||
sample_action = Action(name="increment", action=dummy_action, kwargs={"x": 5})
|
||||
result = await sample_action()
|
||||
assert result == 6
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_hook_lifecycle(hook_manager):
|
||||
async def a1(): return 42
|
||||
action = Action(
|
||||
name="hooked",
|
||||
action=a1,
|
||||
hooks=hook_manager
|
||||
)
|
||||
async def a1():
|
||||
return 42
|
||||
|
||||
action = Action(name="hooked", action=a1, hooks=hook_manager)
|
||||
|
||||
await action()
|
||||
|
||||
@ -48,28 +54,44 @@ async def test_action_hook_lifecycle(hook_manager):
|
||||
assert context.name == "hooked"
|
||||
assert context.extra.get("hook_triggered") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_result_injection():
|
||||
async def a1(): return 1
|
||||
async def a2(last_result): return last_result + 5
|
||||
async def a3(last_result): return last_result * 2
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 5
|
||||
|
||||
async def a3(last_result):
|
||||
return last_result * 2
|
||||
|
||||
actions = [
|
||||
Action(name="start", action=a1),
|
||||
Action(name="add_last", action=a2, inject_last_result=True),
|
||||
Action(name="multiply", action=a3, inject_last_result=True)
|
||||
Action(name="multiply", action=a3, inject_last_result=True),
|
||||
]
|
||||
chain = ChainedAction(name="test_chain", actions=actions, inject_last_result=True, return_list=True)
|
||||
chain = ChainedAction(
|
||||
name="test_chain", actions=actions, inject_last_result=True, return_list=True
|
||||
)
|
||||
result = await chain()
|
||||
assert result == [1, 6, 12]
|
||||
chain = ChainedAction(name="test_chain", actions=actions, inject_last_result=True)
|
||||
result = await chain()
|
||||
assert result == 12
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_group_runs_in_parallel():
|
||||
async def a1(): return 1
|
||||
async def a2(): return 2
|
||||
async def a3(): return 3
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2():
|
||||
return 2
|
||||
|
||||
async def a3():
|
||||
return 3
|
||||
|
||||
actions = [
|
||||
Action(name="a", action=a1),
|
||||
Action(name="b", action=a2),
|
||||
@ -80,10 +102,15 @@ async def test_action_group_runs_in_parallel():
|
||||
result_dict = dict(result)
|
||||
assert result_dict == {"a": 1, "b": 2, "c": 3}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_inject_from_action():
|
||||
async def a1(last_result): return last_result + 10
|
||||
async def a2(last_result): return last_result + 5
|
||||
async def a1(last_result):
|
||||
return last_result + 10
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 5
|
||||
|
||||
inner_chain = ChainedAction(
|
||||
name="inner_chain",
|
||||
actions=[
|
||||
@ -92,8 +119,13 @@ async def test_chained_action_inject_from_action():
|
||||
],
|
||||
return_list=True,
|
||||
)
|
||||
async def a3(): return 1
|
||||
async def a4(last_result): return last_result + 2
|
||||
|
||||
async def a3():
|
||||
return 1
|
||||
|
||||
async def a4(last_result):
|
||||
return last_result + 2
|
||||
|
||||
actions = [
|
||||
Action(name="first", action=a3),
|
||||
Action(name="second", action=a4, inject_last_result=True),
|
||||
@ -103,21 +135,33 @@ async def test_chained_action_inject_from_action():
|
||||
result = await outer_chain()
|
||||
assert result == [1, 3, [13, 18]]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_group():
|
||||
async def a1(last_result): return last_result + 1
|
||||
async def a2(last_result): return last_result + 2
|
||||
async def a3(): return 3
|
||||
async def a1(last_result):
|
||||
return last_result + 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 2
|
||||
|
||||
async def a3():
|
||||
return 3
|
||||
|
||||
group = ActionGroup(
|
||||
name="group",
|
||||
actions=[
|
||||
Action(name="a", action=a1, inject_last_result=True),
|
||||
Action(name="b", action=a2, inject_last_result=True),
|
||||
Action(name="c", action=a3),
|
||||
]
|
||||
],
|
||||
)
|
||||
async def a4(): return 1
|
||||
async def a5(last_result): return last_result + 2
|
||||
|
||||
async def a4():
|
||||
return 1
|
||||
|
||||
async def a5(last_result):
|
||||
return last_result + 2
|
||||
|
||||
actions = [
|
||||
Action(name="first", action=a4),
|
||||
Action(name="second", action=a5, inject_last_result=True),
|
||||
@ -127,6 +171,7 @@ async def test_chained_action_with_group():
|
||||
result = await chain()
|
||||
assert result == [1, 3, [("a", 4), ("b", 5), ("c", 3)]]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_error_triggers_error_hook():
|
||||
def fail():
|
||||
@ -146,6 +191,7 @@ async def test_action_error_triggers_error_hook():
|
||||
|
||||
assert flag.get("called") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_rollback_on_failure():
|
||||
rollback_called = []
|
||||
@ -161,7 +207,7 @@ async def test_chained_action_rollback_on_failure():
|
||||
|
||||
actions = [
|
||||
Action(name="ok", action=success, rollback=rollback_fn),
|
||||
Action(name="fail", action=fail, rollback=rollback_fn)
|
||||
Action(name="fail", action=fail, rollback=rollback_fn),
|
||||
]
|
||||
|
||||
chain = ChainedAction(name="chain", actions=actions)
|
||||
@ -171,13 +217,17 @@ async def test_chained_action_rollback_on_failure():
|
||||
|
||||
assert rollback_called == ["rolled back"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_hooks_recursively_propagates():
|
||||
def hook(context):
|
||||
context.extra.update({"test_marker": True})
|
||||
|
||||
async def a1(): return 1
|
||||
async def a2(): return 2
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2():
|
||||
return 2
|
||||
|
||||
chain = ChainedAction(
|
||||
name="chain",
|
||||
@ -193,6 +243,7 @@ async def test_register_hooks_recursively_propagates():
|
||||
for ctx in er.get_by_name("a") + er.get_by_name("b"):
|
||||
assert ctx.extra.get("test_marker") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_hook_recovers_error():
|
||||
async def flaky():
|
||||
@ -209,15 +260,26 @@ async def test_action_hook_recovers_error():
|
||||
result = await action()
|
||||
assert result == 99
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_group_injects_last_result():
|
||||
async def a1(last_result): return last_result + 10
|
||||
async def a2(last_result): return last_result + 20
|
||||
group = ActionGroup(name="group", actions=[
|
||||
Action(name="g1", action=a1, inject_last_result=True),
|
||||
Action(name="g2", action=a2, inject_last_result=True),
|
||||
])
|
||||
async def a3(): return 5
|
||||
async def a1(last_result):
|
||||
return last_result + 10
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 20
|
||||
|
||||
group = ActionGroup(
|
||||
name="group",
|
||||
actions=[
|
||||
Action(name="g1", action=a1, inject_last_result=True),
|
||||
Action(name="g2", action=a2, inject_last_result=True),
|
||||
],
|
||||
)
|
||||
|
||||
async def a3():
|
||||
return 5
|
||||
|
||||
chain = ChainedAction(
|
||||
name="with_group",
|
||||
actions=[
|
||||
@ -230,20 +292,30 @@ async def test_action_group_injects_last_result():
|
||||
result_dict = dict(result[1])
|
||||
assert result_dict == {"g1": 15, "g2": 25}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_inject_last_result():
|
||||
async def a1(): return 1
|
||||
async def a2(last_result): return last_result + 1
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 1
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2, inject_last_result=True)
|
||||
chain = ChainedAction(name="chain", actions=[a1, a2])
|
||||
result = await chain()
|
||||
assert result == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_inject_last_result_fail():
|
||||
async def a1(): return 1
|
||||
async def a2(last_result): return last_result + 1
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 1
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2)
|
||||
chain = ChainedAction(name="chain", actions=[a1, a2])
|
||||
@ -253,54 +325,82 @@ async def test_action_inject_last_result_fail():
|
||||
|
||||
assert "last_result" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_auto_inject():
|
||||
async def a1(): return 1
|
||||
async def a2(last_result): return last_result + 2
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 2
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2)
|
||||
chain = ChainedAction(name="chain", actions=[a1, a2], auto_inject=True, return_list=True)
|
||||
chain = ChainedAction(
|
||||
name="chain", actions=[a1, a2], auto_inject=True, return_list=True
|
||||
)
|
||||
result = await chain()
|
||||
assert result == [1, 3] # a2 receives last_result=1
|
||||
assert result == [1, 3] # a2 receives last_result=1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_no_auto_inject():
|
||||
async def a1(): return 1
|
||||
async def a2(): return 2
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2():
|
||||
return 2
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2)
|
||||
chain = ChainedAction(name="no_inject", actions=[a1, a2], auto_inject=False, return_list=True)
|
||||
chain = ChainedAction(
|
||||
name="no_inject", actions=[a1, a2], auto_inject=False, return_list=True
|
||||
)
|
||||
result = await chain()
|
||||
assert result == [1, 2] # a2 does not receive 1
|
||||
assert result == [1, 2] # a2 does not receive 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_auto_inject_after_first():
|
||||
async def a1(): return 1
|
||||
async def a2(last_result): return last_result + 1
|
||||
async def a1():
|
||||
return 1
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result + 1
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2)
|
||||
chain = ChainedAction(name="auto_inject", actions=[a1, a2], auto_inject=True)
|
||||
result = await chain()
|
||||
assert result == 2 # a2 receives last_result=1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_literal_input():
|
||||
async def a1(last_result): return last_result + " world"
|
||||
async def a1(last_result):
|
||||
return last_result + " world"
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
chain = ChainedAction(name="literal_inject", actions=["hello", a1], auto_inject=True)
|
||||
result = await chain()
|
||||
assert result == "hello world" # "hello" is injected as last_result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_manual_inject_override():
|
||||
async def a1(): return 10
|
||||
async def a2(last_result): return last_result * 2
|
||||
async def a1():
|
||||
return 10
|
||||
|
||||
async def a2(last_result):
|
||||
return last_result * 2
|
||||
|
||||
a1 = Action(name="a1", action=a1)
|
||||
a2 = Action(name="a2", action=a2, inject_last_result=True)
|
||||
chain = ChainedAction(name="manual_override", actions=[a1, a2], auto_inject=False)
|
||||
result = await chain()
|
||||
assert result == 20 # Even without auto_inject, a2 still gets last_result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_mid_literal():
|
||||
async def fetch_data():
|
||||
@ -330,6 +430,7 @@ async def test_chained_action_with_mid_literal():
|
||||
result = await chain()
|
||||
assert result == [None, "default_value", "default_value", "Enriched: default_value"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_mid_fallback():
|
||||
async def fetch_data():
|
||||
@ -389,15 +490,22 @@ async def test_chained_action_with_success_mid_fallback():
|
||||
result = await chain()
|
||||
assert result == ["Result", "Result", "Result", "Enriched: Result"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_group_partial_failure():
|
||||
async def succeed(): return "ok"
|
||||
async def fail(): raise ValueError("oops")
|
||||
async def succeed():
|
||||
return "ok"
|
||||
|
||||
group = ActionGroup(name="partial_group", actions=[
|
||||
Action(name="succeed_action", action=succeed),
|
||||
Action(name="fail_action", action=fail),
|
||||
])
|
||||
async def fail():
|
||||
raise ValueError("oops")
|
||||
|
||||
group = ActionGroup(
|
||||
name="partial_group",
|
||||
actions=[
|
||||
Action(name="succeed_action", action=succeed),
|
||||
Action(name="fail_action", action=fail),
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
await group()
|
||||
@ -406,10 +514,15 @@ async def test_action_group_partial_failure():
|
||||
assert er.get_by_name("fail_action")[0].exception is not None
|
||||
assert "fail_action" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_with_nested_group():
|
||||
async def g1(last_result): return last_result + "10"
|
||||
async def g2(last_result): return last_result + "20"
|
||||
async def g1(last_result):
|
||||
return last_result + "10"
|
||||
|
||||
async def g2(last_result):
|
||||
return last_result + "20"
|
||||
|
||||
group = ActionGroup(
|
||||
name="nested_group",
|
||||
actions=[
|
||||
@ -431,7 +544,11 @@ async def test_chained_action_with_nested_group():
|
||||
result = await chain()
|
||||
# "start" -> group both receive "start" as last_result
|
||||
assert result[0] == "start"
|
||||
assert dict(result[1]) == {"g1": "start10", "g2": "start20"} # Assuming string concatenation for example
|
||||
assert dict(result[1]) == {
|
||||
"g1": "start10",
|
||||
"g2": "start20",
|
||||
} # Assuming string concatenation for example
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_double_fallback():
|
||||
@ -461,5 +578,11 @@ async def test_chained_action_double_fallback():
|
||||
)
|
||||
|
||||
result = await chain()
|
||||
assert result == [None, "default1", "default1", None, "default2", "Enriched: default2"]
|
||||
|
||||
assert result == [
|
||||
None,
|
||||
"default1",
|
||||
"default1",
|
||||
None,
|
||||
"default2",
|
||||
"Enriched: default2",
|
||||
]
|
||||
|
@ -3,6 +3,7 @@ import pytest
|
||||
from falyx.action import ChainedAction
|
||||
from falyx.exceptions import EmptyChainError
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_raises_empty_chain_error_when_no_actions():
|
||||
"""A ChainedAction with no actions should raise an EmptyChainError immediately."""
|
||||
@ -14,6 +15,7 @@ async def test_chained_action_raises_empty_chain_error_when_no_actions():
|
||||
assert "No actions to execute." in str(exc_info.value)
|
||||
assert "empty_chain" in str(exc_info.value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chained_action_raises_empty_chain_error_when_actions_are_none():
|
||||
"""A ChainedAction with None as actions should raise an EmptyChainError immediately."""
|
||||
@ -24,4 +26,3 @@ async def test_chained_action_raises_empty_chain_error_when_actions_are_none():
|
||||
|
||||
assert "No actions to execute." in str(exc_info.value)
|
||||
assert "none_chain" in str(exc_info.value)
|
||||
|
||||
|
@ -3,12 +3,13 @@ import pytest
|
||||
|
||||
from falyx.action import Action, ActionGroup, ChainedAction
|
||||
from falyx.command import Command
|
||||
from falyx.io_action import BaseIOAction
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.io_action import BaseIOAction
|
||||
from falyx.retry import RetryPolicy
|
||||
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
|
||||
# --- Fixtures ---
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_registry():
|
||||
@ -16,10 +17,12 @@ def clean_registry():
|
||||
yield
|
||||
er.clear()
|
||||
|
||||
|
||||
# --- Dummy Action ---
|
||||
async def dummy_action():
|
||||
return "ok"
|
||||
|
||||
|
||||
# --- Dummy IO Action ---
|
||||
class DummyInputAction(BaseIOAction):
|
||||
async def _run(self, *args, **kwargs):
|
||||
@ -28,46 +31,46 @@ class DummyInputAction(BaseIOAction):
|
||||
async def preview(self, parent=None):
|
||||
pass
|
||||
|
||||
|
||||
# --- Tests ---
|
||||
def test_command_creation():
|
||||
"""Test if Command can be created with a callable."""
|
||||
action = Action("test_action", dummy_action)
|
||||
cmd = Command(
|
||||
key="TEST",
|
||||
description="Test Command",
|
||||
action=action
|
||||
)
|
||||
cmd = Command(key="TEST", description="Test Command", action=action)
|
||||
assert cmd.key == "TEST"
|
||||
assert cmd.description == "Test Command"
|
||||
assert cmd.action == action
|
||||
|
||||
|
||||
def test_command_str():
|
||||
"""Test if Command string representation is correct."""
|
||||
action = Action("test_action", dummy_action)
|
||||
cmd = Command(
|
||||
key="TEST",
|
||||
description="Test Command",
|
||||
action=action
|
||||
)
|
||||
cmd = Command(key="TEST", description="Test Command", action=action)
|
||||
print(cmd)
|
||||
assert str(cmd) == "Command(key='TEST', description='Test Command' action='Action(name='test_action', action=dummy_action, args=(), kwargs={}, retry=False)')"
|
||||
assert (
|
||||
str(cmd)
|
||||
== "Command(key='TEST', description='Test Command' action='Action(name='test_action', action=dummy_action, args=(), kwargs={}, retry=False)')"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"action_factory, expected_requires_input",
|
||||
[
|
||||
(lambda: Action(name="normal", action=dummy_action), False),
|
||||
(lambda: DummyInputAction(name="io"), True),
|
||||
(lambda: ChainedAction(name="chain", actions=[DummyInputAction(name="io")]), True),
|
||||
(lambda: ActionGroup(name="group", actions=[DummyInputAction(name="io")]), True),
|
||||
]
|
||||
(
|
||||
lambda: ChainedAction(name="chain", actions=[DummyInputAction(name="io")]),
|
||||
True,
|
||||
),
|
||||
(
|
||||
lambda: ActionGroup(name="group", actions=[DummyInputAction(name="io")]),
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_command_requires_input_detection(action_factory, expected_requires_input):
|
||||
action = action_factory()
|
||||
cmd = Command(
|
||||
key="TEST",
|
||||
description="Test Command",
|
||||
action=action
|
||||
)
|
||||
cmd = Command(key="TEST", description="Test Command", action=action)
|
||||
|
||||
assert cmd.requires_input == expected_requires_input
|
||||
if expected_requires_input:
|
||||
@ -75,6 +78,7 @@ def test_command_requires_input_detection(action_factory, expected_requires_inpu
|
||||
else:
|
||||
assert cmd.hidden is False
|
||||
|
||||
|
||||
def test_requires_input_flag_detected_for_baseioaction():
|
||||
"""Command should automatically detect requires_input=True for BaseIOAction."""
|
||||
cmd = Command(
|
||||
@ -85,6 +89,7 @@ def test_requires_input_flag_detected_for_baseioaction():
|
||||
assert cmd.requires_input is True
|
||||
assert cmd.hidden is True
|
||||
|
||||
|
||||
def test_requires_input_manual_override():
|
||||
"""Command manually set requires_input=False should not auto-hide."""
|
||||
cmd = Command(
|
||||
@ -96,6 +101,7 @@ def test_requires_input_manual_override():
|
||||
assert cmd.requires_input is False
|
||||
assert cmd.hidden is False
|
||||
|
||||
|
||||
def test_default_command_does_not_require_input():
|
||||
"""Normal Command without IO Action should not require input."""
|
||||
cmd = Command(
|
||||
@ -106,6 +112,7 @@ def test_default_command_does_not_require_input():
|
||||
assert cmd.requires_input is False
|
||||
assert cmd.hidden is False
|
||||
|
||||
|
||||
def test_chain_requires_input():
|
||||
"""If first action in a chain requires input, the command should require input."""
|
||||
chain = ChainedAction(
|
||||
@ -123,6 +130,7 @@ def test_chain_requires_input():
|
||||
assert cmd.requires_input is True
|
||||
assert cmd.hidden is True
|
||||
|
||||
|
||||
def test_group_requires_input():
|
||||
"""If any action in a group requires input, the command should require input."""
|
||||
group = ActionGroup(
|
||||
@ -155,6 +163,7 @@ def test_enable_retry():
|
||||
assert cmd.retry is True
|
||||
assert cmd.action.retry_policy.enabled is True
|
||||
|
||||
|
||||
def test_enable_retry_with_retry_policy():
|
||||
"""Command should enable retry if action is an Action and retry_policy is set."""
|
||||
retry_policy = RetryPolicy(
|
||||
@ -175,6 +184,7 @@ def test_enable_retry_with_retry_policy():
|
||||
assert cmd.action.retry_policy.enabled is True
|
||||
assert cmd.action.retry_policy == retry_policy
|
||||
|
||||
|
||||
def test_enable_retry_not_action():
|
||||
"""Command should not enable retry if action is not an Action."""
|
||||
cmd = Command(
|
||||
@ -188,6 +198,7 @@ def test_enable_retry_not_action():
|
||||
assert cmd.action.retry_policy.enabled is False
|
||||
assert "'function' object has no attribute 'retry_policy'" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_chain_retry_all():
|
||||
"""retry_all should retry all Actions inside a ChainedAction recursively."""
|
||||
chain = ChainedAction(
|
||||
@ -209,6 +220,7 @@ def test_chain_retry_all():
|
||||
assert chain.actions[0].retry_policy.enabled is True
|
||||
assert chain.actions[1].retry_policy.enabled is True
|
||||
|
||||
|
||||
def test_chain_retry_all_not_base_action():
|
||||
"""retry_all should not be set if action is not a ChainedAction."""
|
||||
cmd = Command(
|
||||
@ -221,4 +233,3 @@ def test_chain_retry_all_not_base_action():
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
assert cmd.action.retry_policy.enabled is False
|
||||
assert "'function' object has no attribute 'retry_policy'" in str(exc_info.value)
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import pytest
|
||||
import asyncio
|
||||
from falyx.action import Action, ChainedAction, ActionGroup, FallbackAction
|
||||
|
||||
import pytest
|
||||
|
||||
from falyx.action import Action, ActionGroup, ChainedAction, FallbackAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookType
|
||||
from falyx.context import ExecutionContext
|
||||
|
||||
# --- Fixtures ---
|
||||
|
||||
|
Reference in New Issue
Block a user