Rewrite CLI around action runtime
This commit is contained in:
140
tests/test_actions_executor.py
Normal file
140
tests/test_actions_executor.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Tests for the canonical action executor."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from flow.actions import ActionExecutor, ActionPlan, PrimitiveAction, RollbackPolicy
|
||||
from flow.core.config import AppConfig, FlowContext
|
||||
from flow.core.console import Console
|
||||
from flow.core.errors import FlowError
|
||||
from flow.core.platform import PlatformInfo
|
||||
from flow.core.runtime import SystemRuntime
|
||||
|
||||
|
||||
def _ctx() -> FlowContext:
|
||||
return FlowContext(
|
||||
config=AppConfig(),
|
||||
manifest={},
|
||||
platform=PlatformInfo(),
|
||||
console=Console(color=False),
|
||||
runtime=SystemRuntime(),
|
||||
)
|
||||
|
||||
|
||||
def test_dry_run_does_not_mutate(tmp_path):
|
||||
target = tmp_path / "out.txt"
|
||||
plan = ActionPlan(
|
||||
name="dry-run",
|
||||
primitive_actions=(
|
||||
PrimitiveAction(
|
||||
id="write",
|
||||
type="file.write",
|
||||
description="Write a file",
|
||||
payload={"path": target, "content": "hello"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
summary = ActionExecutor(_ctx(), audit_path=tmp_path / "actions.jsonl").execute(
|
||||
plan,
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
assert not target.exists()
|
||||
assert summary.results[0].status == "dry-run"
|
||||
|
||||
|
||||
def test_audit_jsonl_records_success(tmp_path):
|
||||
target = tmp_path / "out.txt"
|
||||
audit_path = tmp_path / "actions.jsonl"
|
||||
plan = ActionPlan(
|
||||
name="write-plan",
|
||||
primitive_actions=(
|
||||
PrimitiveAction(
|
||||
id="write",
|
||||
type="file.write",
|
||||
description="Write a file",
|
||||
payload={"path": target, "content": "hello"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
ActionExecutor(_ctx(), audit_path=audit_path).execute(plan)
|
||||
|
||||
records = [json.loads(line) for line in audit_path.read_text().splitlines()]
|
||||
assert [record["event"] for record in records] == [
|
||||
"plan_start",
|
||||
"action_start",
|
||||
"action_success",
|
||||
"plan_success",
|
||||
]
|
||||
assert target.read_text() == "hello"
|
||||
|
||||
|
||||
def test_rollback_removes_created_symlink_after_failure(tmp_path):
|
||||
source = tmp_path / "source"
|
||||
target = tmp_path / "target"
|
||||
source.write_text("managed")
|
||||
plan = ActionPlan(
|
||||
name="rollback",
|
||||
primitive_actions=(
|
||||
PrimitiveAction(
|
||||
id="link",
|
||||
type="file.create_symlink",
|
||||
description="Create managed link",
|
||||
payload={"source": source, "target": target},
|
||||
),
|
||||
PrimitiveAction(
|
||||
id="copy-missing",
|
||||
type="file.copy",
|
||||
description="Fail on missing source",
|
||||
payload={"source": tmp_path / "missing", "target": tmp_path / "dest"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(FlowError, match="missing"):
|
||||
ActionExecutor(_ctx(), audit_path=tmp_path / "actions.jsonl").execute(plan)
|
||||
|
||||
assert not target.exists()
|
||||
assert not target.is_symlink()
|
||||
|
||||
|
||||
def test_barrier_prevents_rollback_across_external_boundary(tmp_path):
|
||||
source = tmp_path / "source"
|
||||
target = tmp_path / "target"
|
||||
source.write_text("managed")
|
||||
plan = ActionPlan(
|
||||
name="barrier",
|
||||
primitive_actions=(
|
||||
PrimitiveAction(
|
||||
id="link",
|
||||
type="file.create_symlink",
|
||||
description="Create managed link",
|
||||
payload={"source": source, "target": target},
|
||||
),
|
||||
PrimitiveAction(
|
||||
id="barrier",
|
||||
type="process.argv",
|
||||
description="External boundary",
|
||||
payload={"argv": (sys.executable, "-c", "")},
|
||||
rollback_policy=RollbackPolicy.BARRIER,
|
||||
),
|
||||
PrimitiveAction(
|
||||
id="copy-missing",
|
||||
type="file.copy",
|
||||
description="Fail on missing source",
|
||||
payload={"source": tmp_path / "missing", "target": tmp_path / "dest"},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(FlowError, match="missing"):
|
||||
ActionExecutor(_ctx(), audit_path=tmp_path / "actions.jsonl").execute(plan)
|
||||
|
||||
assert target.is_symlink()
|
||||
|
||||
Reference in New Issue
Block a user