From 22c8157ca414f52d666442840ab2a51c8baa9ddb Mon Sep 17 00:00:00 2001 From: latte Date: Wed, 14 Oct 2026 17:04:10 +0200 Subject: [PATCH] test: Actions run scenario on an act_runner container --- tests/integration/test_scenario_actions.py | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/integration/test_scenario_actions.py diff --git a/tests/integration/test_scenario_actions.py b/tests/integration/test_scenario_actions.py new file mode 100644 index 0000000..6579449 --- /dev/null +++ b/tests/integration/test_scenario_actions.py @@ -0,0 +1,117 @@ +"""Scenario: a real Actions run on an act_runner container, read back through the tools.""" + +from __future__ import annotations + +import asyncio +import time +from typing import Any + +import pytest + +from tests.integration.conftest import McpSession, RepoRef, b64 +from tests.integration.harness import GiteaInstance, HarnessError, runner_logs, start_runner + +WORKFLOW_PATH = ".gitea/workflows/it.yml" +WORKFLOW = """\ +name: it +on: [push, workflow_dispatch] +jobs: + hello: + runs-on: aegis-host + steps: + - name: Say hello + run: echo hello +""" +RUNNER_ONLINE_TIMEOUT = 90.0 +RUN_TIMEOUT = 180.0 + + +async def _wait_for(probe: Any, *, timeout: float, interval: float = 2.0) -> Any: + """Await ``probe()`` until it returns something truthy; return it (or None on timeout).""" + deadline = time.monotonic() + timeout + while True: + value = await probe() + if value or time.monotonic() >= deadline: + return value + await asyncio.sleep(interval) + + +async def test_workflow_run_on_act_runner( + mcp: McpSession, gitea: GiteaInstance, fresh_repo: RepoRef +) -> None: + repo = fresh_repo.args + token = await mcp.ok("gitea_actions_repo_create_runner_registration_token", **repo) + assert isinstance(token.data["token"], str) and token.data["token"] + try: + runner = start_runner(gitea, token.data["token"]) + except HarnessError as exc: + pytest.xfail(f"the act_runner container could not be started: {exc}") + + async def online() -> dict[str, Any] | None: + runners = await mcp.ok("gitea_actions_get_repo_runners", **repo) + entries = runners.data["runners"] if isinstance(runners.data, dict) else runners.data + for entry in entries or []: + if entry.get("status") in ("online", "idle", "active"): + found: dict[str, Any] = entry + return found + return None + + registered = await _wait_for(online, timeout=RUNNER_ONLINE_TIMEOUT) + if not registered: + pytest.xfail( + "the act_runner did not come online within " + f"{RUNNER_ONLINE_TIMEOUT:.0f}s; runner log tail: {runner_logs(runner, 15)}" + ) + assert token.data["token"] not in runner_logs(runner, 200) + + # Commit the workflow through the contents tool, then dispatch it explicitly. + await mcp.ok( + "gitea_repo_create_file", + status=201, + filepath=WORKFLOW_PATH, + body={"content": b64(WORKFLOW), "message": "add it workflow", "branch": "main"}, + **repo, + ) + workflows = await mcp.ok("gitea_actions_list_repository_workflows", **repo) + assert any(w["path"].endswith("it.yml") for w in workflows.data["workflows"]) + await mcp.ok( + "gitea_actions_dispatch_workflow", + status=(200, 204), + workflow_id="it.yml", + body={"ref": "main"}, + **repo, + ) + + async def finished() -> list[dict[str, Any]] | None: + runs = await mcp.ok("gitea_actions_get_workflow_runs", **repo) + entries = runs.data["workflow_runs"] + if len(entries) >= 2 and all(r["status"] == "completed" for r in entries): + done: list[dict[str, Any]] = entries + return done + return None + + runs = await _wait_for(finished, timeout=RUN_TIMEOUT) + assert runs, ( + f"workflow runs did not complete within {RUN_TIMEOUT:.0f}s; " + f"runner log tail: {runner_logs(runner, 20)}" + ) + assert {r["event"] for r in runs} == {"push", "workflow_dispatch"} + assert [r["conclusion"] for r in runs] == ["success", "success"] + + run_id = runs[0]["id"] + one = await mcp.ok("gitea_actions_get_workflow_run", run=run_id, **repo) + assert one.data["status"] == "completed" + jobs = await mcp.ok("gitea_actions_list_workflow_run_jobs", run=run_id, **repo) + job = jobs.data["jobs"][0] + assert job["conclusion"] == "success" + job_id = str(job["id"]) # the job id is a string argument + fetched = await mcp.ok("gitea_actions_get_workflow_job", job_id=job_id, **repo) + assert fetched.data["id"] == job["id"] + logs = await mcp.ok("gitea_actions_download_run_job_logs", job_id=job["id"], **repo) + assert isinstance(logs.data, str) + assert "hello" in logs.data + artifacts = await mcp.ok("gitea_actions_get_artifacts_of_run", run=run_id, **repo) + assert artifacts.data["artifacts"] == [] + assert (await mcp.ok("gitea_actions_get_artifacts", **repo)).data["total_count"] == 0 + tasks = await mcp.ok("gitea_actions_list_action_tasks", **repo) + assert tasks.data["total_count"] >= 2