test: exercise the action on Gitea with a Rust integration test - #533
marcoienibot wants to merge 24 commits into
Conversation
The unique per-run project name is why runner images accumulated and why a stack leaked by an interrupted run (Drop does not run on Ctrl-C/SIGKILL) could never be reclaimed. With a fixed name, the next run tears down whatever the previous one left behind, at the cost of not being able to run the test twice at the same time on one host. The credentials are never secret, so fixed obviously-fake values replace `getrandom`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Embedding the token in the push URL replaces the `origin` remote and the `GIT_CONFIG_*` extraheader environment, which was the only user of `base64`. The workflow clones the repository fresh from Gitea, so the local checkout never needs a remote. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
The workflow's last step queried the PR it had just created through the API and asserted the same properties the Rust harness checks on the same endpoint. The harness already fetches the open PRs, so it now also asserts the base and head branches, and the workflow keeps only the checks on the action's step outputs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
One `send` method does the auth, the request, and the status check and hands back the response; `get` decodes it as JSON and mutating callers discard it, which removes the empty-body-to-null special case. The immediately invoked closure that printed job logs is now a fallible `job_logs` method, with the single caller deciding how to report errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
`repo` was the `owner/name` slug in one function and the `/repos/...` API path in the next; it is now the slug everywhere and callees build their own paths. The Gitea client keeps the base address and derives the API prefix itself, so the address no longer travels as a separate argument. `Stack` is a compose project, and "stack" is Swarm vocabulary, so it becomes `Compose`, with `admin_user` naming the `gitea admin user` prefix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
The runner service had no healthcheck, so `docker compose up --wait` returned while `gitea-runner register` was still running and a failed registration only surfaced after the harness's 15-minute poll. Check for the `.runner` file the runner writes after a successful registration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
The runner Dockerfile only copies files from other images, but without a .dockerignore Docker still uploaded the harness's target/ directory on every build. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Replace the hand-written clone and credential setup with the pinned actions/checkout, which Gitea resolves from GitHub via DEFAULT_ACTIONS_URL. v5 is the last major whose persisted token survives release-plz's temporary copy of the repository (release-plz#2957), and fetch-depth 0 keeps the v0.1.0 tag and history. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
taiki-e/install-action installs xz itself when a tool ships a .tar.xz, and cargo-semver-checks ships .tar.gz archives, so the package was never used. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
The root README repeated the test command, and tests/gitea/README.md described harness internals that go stale. Link to the test README from the root and keep only prerequisites, the command, what is asserted, and how the fixed Compose project is cleaned up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 1Two review passes (correctness/tests/security, and maintainability/simplicity) produced 16 findings. I applied 10 as one commit each and skipped 6. The merged branch passes Commits
Skipped
🤖 Generated with Claude Code |
The project name and the runner registration token are constants, so the `-p` flag and the `GITEA_RUNNER_REGISTRATION_TOKEN` variable only broke hand-run `docker compose config`/`down` with "required variable ... is missing a value". Declare both in compose.yml so the harness and the manual cleanup command need no extra arguments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Since the assertions were split, the workflow no longer queries Gitea: it checks the action outputs on their own, and the harness checks the pull request through the Gitea API. Say so instead of describing the outputs as matching what Gitea reports. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Workflow `run` steps already default to bash with `-e -o pipefail` on Linux; only composite actions need an explicit `shell:`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 2Both reviewers re-ran on b6a7cdb. The correctness/security pass found nothing significant and verified the round-1 changes against upstream sources (compose Commits
Corrections to my round-1 comment
Skipped
🤖 Generated with Claude Code |
Compose resolves the project name as `-p`, then `COMPOSE_PROJECT_NAME`, then the top-level `name` in compose.yml. With the variable exported in the developer's shell, `Compose::new`'s `down --volumes --rmi local` would run against their project and the promise that the next run cleans up a leaked one would depend on the shell. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Since the `curl | jq` block was removed the step no longer talks to Gitea; it compares the action outputs with literal expectations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
`Compose.directory` was always `CARGO_MANIFEST_DIR` and `test_release_pr` took `&Compose` only to read it. A module-level `DIRECTORY` constant serves both, so `Compose` shrinks to its `passed` flag and `test_release_pr` becomes a function of `&Gitea` only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 3Both reviewers re-ran on 7198e94. One low-severity gap introduced by round 2, one stale name left over from round 1, and one small structural simplification. Three commits, verified with a passing Docker run (this time with Commits
Correction to a round-2 commit messageThe body of 7198e94 says run steps default to bash with Skipped
🤖 Generated with Claude Code |
An env file named by COMPOSE_ENV_FILES can carry a COMPOSE_PROJECT_NAME entry, which beats the top-level `name` in compose.yml exactly like the exported variable (verified on compose v5.5.1). Without this, a developer exporting it for their own stack would have the harness's `down --volumes --rmi local` target that stack instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Rename `Compose::new()` to `Compose::up()` and move the `docker compose up` invocation into it, after the cleanup of a killed previous run. A failed `up` still drops the guard, so `logs` and `down` run exactly as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 4Both reviewers re-ran on e904c62. The correctness pass probed every compose environment knob ( Commits
Skipped
The maintainability reviewer's verdict: apart from the reshaping above, the harness is as simple as it reasonably gets and another round would be churn. One more round will confirm that. 🤖 Generated with Claude Code |
Renovate's github-actions manager also matches the fixture workflow under tests/gitea/fixture/.gitea/workflows, so the grouped weekend automerge would bump actions/checkout to v6+, whose token file is invisible to release-plz's temporary copy of the repository and breaks the gitea CI job. Restrict that one dependency to v5 while still allowing digest refreshes within it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 5Both reviewers re-ran on 03c6efa. The correctness pass verified the round-4 changes (a failed Commit
Skipped
🤖 Generated with Claude Code |
… rule Both comments explained the checkout v6 mechanism in different words, and the Renovate rule had no pointer from the fixture, so a future bump to v7 would leave the rule silently freezing the fixture. The fixture now owns the explanation and each side points at the other. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhCChWJ9X6ryPuuPLF8Ggx
Review round 6Both reviewers re-ran on 584ff0e. The correctness pass verified the Renovate rule against Renovate's current source ( Commit
Skipped
Both reviewers state the branch is as simple as it reasonably gets and further rounds would be churn. One more scoped round on this comment-only commit closes the loop. 🤖 Generated with Claude Code |
Review round 7 (final)Both reviewers re-ran on a274e24. Correctness confirmed the last commit is comment-only (both files parse; the fixture workflow parses to the same YAML document as before) and found nothing new across the full diff. Maintainability found nothing worth a commit. No further rounds. Final state: 4 original commits plus 20 review commits, every one verified locally with 🤖 Generated with Claude Code |
|
|
||
| [dev-dependencies] | ||
| anyhow = "1" | ||
| reqwest = { version = "0.12", default-features = false, features = ["blocking", "json"] } |
There was a problem hiding this comment.
Update all dependencies to the latest version
Add a Docker-backed integration test that runs the checked-out action through a real Gitea server and Gitea Actions runner. A tagged Rust fixture receives a fix commit, and the test verifies that release-plz opens a
0.1.1release PR, changes the manifest and changelog, and returns outputs matching the Gitea API.The Rust test crate exercises
forge: gitea. Each run uses isolated containers, generated credentials, a dynamic localhost port, failure logs, and automatic cleanup. The GitHub test workflow now runs this test, and the README documents the local command.Validation: Cargo fmt, Clippy with warnings denied, and actionlint passed. The Gitea release PR test also passed locally with Docker.