Files
gyxx-flow/tests/test_shared_adapters.py
T

94 lines
3.1 KiB
Python

from __future__ import annotations
import subprocess
from pathlib import Path
import pytest
from gyxx_flow.adapters import (
BrowserProfileManager,
FeishuOutboxAdapter,
HermesCommandAdapter,
PostgresOutboxAdapter,
)
from gyxx_flow.core.context import RunContext
from gyxx_flow.core.locks import ResourceBusyError
from gyxx_flow.ops import Outbox
def test_feishu_and_postgres_writes_share_idempotent_outbox_boundary(tmp_path: Path) -> None:
outbox = Outbox(tmp_path)
feishu = FeishuOutboxAdapter(outbox)
postgres = PostgresOutboxAdapter(outbox)
first = feishu.upsert(
run_id="run-001",
operation_key="weekly-report",
artifact_id="artifact-report-001",
)
duplicate = feishu.upsert(
run_id="run-001",
operation_key="weekly-report",
artifact_id="artifact-report-001",
)
db_message = postgres.upsert(
run_id="run-001",
operation_key="shop-metrics",
artifact_id="artifact-metrics-001",
)
assert duplicate == first
assert first.topic == "feishu.upsert"
assert db_message.topic == "postgres.upsert"
assert first.payload == {"artifact_id": "artifact-report-001"}
assert db_message.payload == {"artifact_id": "artifact-metrics-001"}
assert first.idempotency_key != db_message.idempotency_key
def test_hermes_adapter_builds_explicit_command_and_dry_run_starts_no_process(
monkeypatch, tmp_path: Path
) -> None:
prompt = tmp_path / "prompt.md"
prompt.write_text("analyze artifact-001", encoding="utf-8")
adapter = HermesCommandAdapter(executable="hermes", base_env={"PROFILE": "analysis"})
command = adapter.build(prompt_file=prompt, cwd=tmp_path)
assert command.argv == ("hermes", "-z", "analyze artifact-001")
assert command.cwd == tmp_path.resolve()
assert command.env == {"PROFILE": "analysis"}
monkeypatch.setattr(
subprocess,
"run",
lambda *args, **kwargs: pytest.fail("dry-run must not start Hermes"),
)
context = RunContext.create("product.style", "2026-07-27")
outcome = command.execute(context=context, timeout_seconds=60, dry_run=True)
assert outcome.skipped is True
def test_browser_profiles_are_portable_and_exclusively_locked(tmp_path: Path) -> None:
manager = BrowserProfileManager(tmp_path)
with manager.acquire("shop-compass", owner="run-001") as first:
assert first.profile_path == (
tmp_path / "state" / "browser_profiles" / "shop-compass"
).resolve()
assert first.profile_path.is_dir()
with pytest.raises(ResourceBusyError):
with manager.acquire(
"shop-compass", owner="run-002", timeout_seconds=0
):
pass
with manager.acquire("shop-compass", owner="run-002", timeout_seconds=0) as second:
assert second.profile_path == first.profile_path
@pytest.mark.parametrize("unsafe", ["../escape", "a/b", "a\\b", "", ".."])
def test_browser_profile_id_rejects_path_escape(tmp_path: Path, unsafe: str) -> None:
with pytest.raises(ValueError, match="profile"):
with BrowserProfileManager(tmp_path).acquire(unsafe, owner="run-001"):
pass