feat: consolidate legacy workflows into gyxx-flow
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from gyxx_flow.core.context import RunContext
|
||||
from gyxx_flow.core.layout import DataLayout
|
||||
from gyxx_flow.core.locks import LockManager, ResourceBusyError
|
||||
from gyxx_flow.core.records import RunJournal
|
||||
|
||||
|
||||
def _context() -> RunContext:
|
||||
return RunContext.create(
|
||||
"shop.weekly",
|
||||
"2026-07-27",
|
||||
now=datetime(2026, 7, 27, 4, 0, tzinfo=timezone.utc),
|
||||
random_suffix="abc123",
|
||||
)
|
||||
|
||||
|
||||
def test_run_journal_tracks_steps_and_final_status(tmp_path: Path) -> None:
|
||||
context = _context()
|
||||
journal = RunJournal.create(DataLayout(tmp_path), context)
|
||||
|
||||
journal.start_step("collect.jd", attempt=1)
|
||||
journal.finish_step("collect.jd", status="success", exit_code=0)
|
||||
journal.start_step("publish.feishu", attempt=1)
|
||||
journal.finish_step(
|
||||
"publish.feishu",
|
||||
status="failed",
|
||||
exit_code=3,
|
||||
error="credential binding missing",
|
||||
)
|
||||
journal.finalize("failed", error="publish.feishu failed")
|
||||
|
||||
payload = json.loads(journal.path.read_text(encoding="utf-8"))
|
||||
assert payload["run_id"] == context.run_id
|
||||
assert payload["status"] == "failed"
|
||||
assert payload["steps"]["collect.jd"]["status"] == "success"
|
||||
assert payload["steps"]["publish.feishu"]["exit_code"] == 3
|
||||
assert payload["steps"]["publish.feishu"]["error"] == "credential binding missing"
|
||||
assert payload["error"] == "publish.feishu failed"
|
||||
assert payload["trace"]["paths"]["run"].startswith("runs/")
|
||||
assert payload["trace"]["paths"]["log"].startswith("logs/")
|
||||
assert payload["trace"]["paths"]["evidence"].startswith("data/evidence/")
|
||||
|
||||
|
||||
def test_run_journal_records_deduplicated_trace_references(tmp_path: Path) -> None:
|
||||
journal = RunJournal.create(DataLayout(tmp_path), _context())
|
||||
|
||||
journal.record_input("artifact:raw-001")
|
||||
journal.record_input("artifact:raw-001")
|
||||
journal.record_output("artifact:normalized-001")
|
||||
journal.record_external_write("outbox:msg-001")
|
||||
|
||||
payload = json.loads(journal.path.read_text(encoding="utf-8"))
|
||||
assert payload["trace"]["inputs"] == ["artifact:raw-001"]
|
||||
assert payload["trace"]["outputs"] == ["artifact:normalized-001"]
|
||||
assert payload["trace"]["external_writes"] == ["outbox:msg-001"]
|
||||
|
||||
|
||||
def test_run_journal_rejects_invalid_transitions(tmp_path: Path) -> None:
|
||||
journal = RunJournal.create(DataLayout(tmp_path), _context())
|
||||
|
||||
with pytest.raises(ValueError, match="not running"):
|
||||
journal.finish_step("collect.jd", status="success")
|
||||
|
||||
journal.start_step("collect.jd", attempt=1)
|
||||
with pytest.raises(ValueError, match="already running"):
|
||||
journal.start_step("collect.jd", attempt=2)
|
||||
|
||||
|
||||
def test_named_resource_lock_blocks_concurrent_owner_and_releases(tmp_path: Path) -> None:
|
||||
manager = LockManager(tmp_path)
|
||||
|
||||
with manager.acquire("browser:xingtu", owner="run-001") as first:
|
||||
assert first.path.exists()
|
||||
with pytest.raises(ResourceBusyError, match="browser:xingtu"):
|
||||
with manager.acquire("browser:xingtu", owner="run-002", timeout_seconds=0):
|
||||
pass
|
||||
|
||||
assert not first.path.exists()
|
||||
with manager.acquire("browser:xingtu", owner="run-002", timeout_seconds=0) as second:
|
||||
metadata = json.loads(second.path.read_text(encoding="utf-8"))
|
||||
assert metadata["resource"] == "browser:xingtu"
|
||||
assert metadata["owner"] == "run-002"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resource", ["", "../state", "resource\nname"])
|
||||
def test_named_resource_lock_rejects_unsafe_name(tmp_path: Path, resource: str) -> None:
|
||||
manager = LockManager(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError, match="resource"):
|
||||
with manager.acquire(resource, owner="run-001"):
|
||||
pass
|
||||
Reference in New Issue
Block a user