feat: complete production workflow migration
This commit is contained in:
@@ -6,6 +6,10 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from gyxx_flow.adapters.acceptance_policy import (
|
||||
WORKFLOW_ACCEPTANCE_RECIPIENT_OPEN_ID,
|
||||
WorkflowAcceptancePolicyError,
|
||||
)
|
||||
from gyxx_flow.adapters.native import (
|
||||
DeferredModuleCommandStep,
|
||||
ModuleCommandAdapter,
|
||||
@@ -14,6 +18,7 @@ from gyxx_flow.adapters.native import (
|
||||
)
|
||||
from gyxx_flow.catalog import WorkflowEntry
|
||||
from gyxx_flow.core.context import RunContext
|
||||
from gyxx_flow.workflow.steps import StepExecution
|
||||
|
||||
|
||||
def _entry(**overrides: object) -> WorkflowEntry:
|
||||
@@ -86,6 +91,31 @@ def test_deferred_module_step_dry_run_never_resolves_or_executes() -> None:
|
||||
assert execution.reason == "dry-run"
|
||||
|
||||
|
||||
def test_deferred_module_step_renders_business_date_in_catalog_args() -> None:
|
||||
captured: list[WorkflowEntry] = []
|
||||
|
||||
class SuccessStep:
|
||||
def execute(self, **_kwargs: object) -> StepExecution:
|
||||
return StepExecution(exit_code=0)
|
||||
|
||||
def command_factory(entry: WorkflowEntry, _context: RunContext) -> SuccessStep:
|
||||
captured.append(entry)
|
||||
return SuccessStep()
|
||||
|
||||
execution = DeferredModuleCommandStep(
|
||||
_entry(),
|
||||
command_factory=command_factory,
|
||||
command_args=("--start-date", "{business_date}", "run={run_id}"),
|
||||
).execute(context=_context(), timeout_seconds=60, dry_run=False)
|
||||
|
||||
assert execution.exit_code == 0
|
||||
assert captured[0].args == (
|
||||
"--start-date",
|
||||
"2026-07-27",
|
||||
f"run={_context().run_id}",
|
||||
)
|
||||
|
||||
|
||||
def test_module_adapter_uses_explicit_windows_launchers_for_batch_and_powershell(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -131,3 +161,115 @@ def test_product_config_uses_only_the_new_environment_name(tmp_path: Path) -> No
|
||||
|
||||
assert command.env["GYXX_PRODUCT_CONFIG"] == "D:/portable/product.json"
|
||||
assert "AUTO_FLOW_CONFIG" not in command.env
|
||||
|
||||
|
||||
def test_product_config_defaults_to_canonical_module_state(tmp_path: Path) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
data_root = tmp_path / "var"
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={},
|
||||
project_root=tmp_path,
|
||||
data_root=data_root,
|
||||
)
|
||||
|
||||
command = adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
assert command.env["GYXX_PRODUCT_CONFIG"] == str(
|
||||
(data_root / "state" / "product_commerce" / "auto-flow-config.json").resolve()
|
||||
)
|
||||
|
||||
|
||||
def test_product_config_maps_legacy_portable_alias(tmp_path: Path) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={"AUTOFLOW_CONFIG_PATH": "D:/portable/legacy-product.json"},
|
||||
project_root=tmp_path,
|
||||
data_root=tmp_path / "var",
|
||||
)
|
||||
|
||||
command = adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
assert command.env["GYXX_PRODUCT_CONFIG"] == "D:/portable/legacy-product.json"
|
||||
|
||||
|
||||
def test_product_config_maps_original_source_environment_name(tmp_path: Path) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={"AUTO_FLOW_CONFIG": "D:/original/product.json"},
|
||||
project_root=tmp_path,
|
||||
data_root=tmp_path / "var",
|
||||
)
|
||||
|
||||
command = adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
assert command.env["GYXX_PRODUCT_CONFIG"] == "D:/original/product.json"
|
||||
|
||||
|
||||
def test_product_config_uses_explicit_precedence_order(tmp_path: Path) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={
|
||||
"GYXX_PRODUCT_CONFIG": "D:/canonical/product.json",
|
||||
"AUTO_FLOW_CONFIG": "D:/original/product.json",
|
||||
"AUTOFLOW_CONFIG_PATH": "D:/portable/product.json",
|
||||
},
|
||||
project_root=tmp_path,
|
||||
data_root=tmp_path / "var",
|
||||
)
|
||||
|
||||
command = adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
assert command.env["GYXX_PRODUCT_CONFIG"] == "D:/canonical/product.json"
|
||||
|
||||
|
||||
def test_module_adapter_propagates_fail_closed_acceptance_policy(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={"GYXX_WORKFLOW_ACCEPTANCE": "1"},
|
||||
project_root=tmp_path,
|
||||
data_root=tmp_path / "var",
|
||||
)
|
||||
|
||||
command = adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
assert command.env["GYXX_FEISHU_TABLE_WRITE_DISABLED"] == "1"
|
||||
assert command.env["GYXX_COOKIE_INVALID_SKIP"] == "1"
|
||||
assert (
|
||||
command.env["GYXX_NOTIFICATION_RECIPIENT_OPEN_ID"]
|
||||
== WORKFLOW_ACCEPTANCE_RECIPIENT_OPEN_ID
|
||||
)
|
||||
|
||||
|
||||
def test_module_adapter_rejects_unsafe_acceptance_recipient(tmp_path: Path) -> None:
|
||||
root = tmp_path / "runtime"
|
||||
root.mkdir()
|
||||
(root / "job.py").write_text("pass", encoding="utf-8")
|
||||
adapter = ModuleCommandAdapter(
|
||||
ModuleSourceRoots({"product_commerce": root}),
|
||||
base_env={
|
||||
"GYXX_WORKFLOW_ACCEPTANCE": "1",
|
||||
"GYXX_NOTIFICATION_RECIPIENT_OPEN_ID": "ou_not_wang",
|
||||
},
|
||||
project_root=tmp_path,
|
||||
data_root=tmp_path / "var",
|
||||
)
|
||||
|
||||
with pytest.raises(WorkflowAcceptancePolicyError, match="Wang Yunlong"):
|
||||
adapter.build(_entry(entry="job.py", args=()), context=_context())
|
||||
|
||||
Reference in New Issue
Block a user