feat: consolidate legacy workflows into gyxx-flow

This commit is contained in:
2026-07-28 14:51:15 +08:00
commit c23b62a8c8
374 changed files with 132990 additions and 0 deletions
+98
View File
@@ -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