feat: complete production workflow migration
This commit is contained in:
+41
-52
@@ -2,72 +2,32 @@ from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from gyxx_flow.catalog import WorkflowCatalog
|
||||
from gyxx_flow.cli import main
|
||||
from gyxx_flow.cli import EXIT_USAGE, build_parser, main
|
||||
from gyxx_flow.core.config import Settings
|
||||
from gyxx_flow.scheduler import CurrentScheduledTask
|
||||
from gyxx_flow.workflow import WorkflowDefinition
|
||||
from gyxx_flow.workflow.registry import WorkflowRegistry
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class FakeCurrentTaskProvider:
|
||||
def __init__(self, tasks: list[CurrentScheduledTask]) -> None:
|
||||
self.tasks = tasks
|
||||
self.requested_path: str | None = None
|
||||
|
||||
def current_tasks(self, task_path: str) -> list[CurrentScheduledTask]:
|
||||
self.requested_path = task_path
|
||||
return self.tasks
|
||||
|
||||
|
||||
def test_schedule_plan_cli_writes_inert_bundle_and_drift_report(tmp_path: Path) -> None:
|
||||
provider = FakeCurrentTaskProvider([])
|
||||
output = io.StringIO()
|
||||
destination = tmp_path / "generated-plan"
|
||||
settings = Settings(project_root=PROJECT_ROOT, data_root=tmp_path / "data")
|
||||
|
||||
exit_code = main(
|
||||
[
|
||||
"schedule",
|
||||
"plan",
|
||||
"--output",
|
||||
str(destination),
|
||||
"--start-date",
|
||||
"2026-07-27",
|
||||
"--python-executable",
|
||||
str(PROJECT_ROOT / ".venv" / "Scripts" / "python.exe"),
|
||||
],
|
||||
settings=settings,
|
||||
current_task_provider=provider,
|
||||
stdout=output,
|
||||
stderr=io.StringIO(),
|
||||
def test_schedule_cli_exposes_only_project_owned_service_commands() -> None:
|
||||
parser = build_parser()
|
||||
command_action = next(action for action in parser._actions if action.dest == "command")
|
||||
schedule_parser = command_action.choices["schedule"]
|
||||
schedule_action = next(
|
||||
action for action in schedule_parser._actions if action.dest == "schedule_command"
|
||||
)
|
||||
|
||||
assert exit_code == 0
|
||||
payload = json.loads(output.getvalue())
|
||||
assert payload["desired_count"] == 21
|
||||
assert payload["applied"] is False
|
||||
assert payload["drift_count"] == 21
|
||||
assert provider.requested_path == "\\GYXX\\"
|
||||
assert (destination / "install.ps1").is_file()
|
||||
assert (destination / "drift.json").is_file()
|
||||
assert json.loads((destination / "plan.json").read_text(encoding="utf-8"))[
|
||||
"applied"
|
||||
] is False
|
||||
assert set(schedule_action.choices) == {"run", "status"}
|
||||
|
||||
|
||||
def test_scheduled_run_uses_shanghai_business_date_and_executes_registered_workflow(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
def test_run_rejects_implicit_scheduled_execution(tmp_path: Path) -> None:
|
||||
catalog = WorkflowCatalog.load(PROJECT_ROOT / "config")
|
||||
registry = WorkflowRegistry(catalog)
|
||||
registry.register(WorkflowDefinition("product.daily", ()))
|
||||
monkeypatch.setattr("gyxx_flow.cli._today_shanghai", lambda: date(2026, 7, 27))
|
||||
output = io.StringIO()
|
||||
|
||||
exit_code = main(
|
||||
@@ -78,8 +38,37 @@ def test_scheduled_run_uses_shanghai_business_date_and_executes_registered_workf
|
||||
stderr=io.StringIO(),
|
||||
)
|
||||
|
||||
assert exit_code == EXIT_USAGE
|
||||
assert output.getvalue() == ""
|
||||
|
||||
|
||||
def test_python_scheduler_status_is_project_owned_and_cross_platform(tmp_path: Path) -> None:
|
||||
output = io.StringIO()
|
||||
settings = Settings(project_root=PROJECT_ROOT, data_root=tmp_path / "data")
|
||||
|
||||
exit_code = main(
|
||||
["schedule", "status"], settings=settings,
|
||||
stdout=output, stderr=io.StringIO(),
|
||||
)
|
||||
|
||||
assert exit_code == 0
|
||||
payload = json.loads(output.getvalue())
|
||||
assert payload["business_date"] == "2026-07-27"
|
||||
assert payload["dry_run"] is False
|
||||
assert payload["status"] == "success"
|
||||
assert payload["timezone"] == "Asia/Shanghai"
|
||||
assert payload["scheduled_count"] == 23
|
||||
assert payload["state"]["schema_version"] == 1
|
||||
|
||||
|
||||
def test_python_scheduler_dry_run_never_launches_or_writes_state(tmp_path: Path) -> None:
|
||||
output = io.StringIO()
|
||||
settings = Settings(project_root=PROJECT_ROOT, data_root=tmp_path / "data")
|
||||
|
||||
exit_code = main(
|
||||
["schedule", "run", "--dry-run", "--once", "--misfire-grace-seconds", "3456000"],
|
||||
settings=settings, stdout=output, stderr=io.StringIO(),
|
||||
)
|
||||
|
||||
assert exit_code == 0
|
||||
payload = json.loads(output.getvalue())
|
||||
assert payload["mode"] == "dry-run"
|
||||
assert "product.market_rank" in payload["due_or_started"]
|
||||
assert not (settings.data_root / "state" / "scheduler" / "state.json").exists()
|
||||
|
||||
Reference in New Issue
Block a user