Rewrite CLI around action runtime

This commit is contained in:
2026-05-14 13:14:38 +03:00
parent 0dc90f9005
commit 3503d81b06
28 changed files with 2778 additions and 574 deletions

View 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()