# CI The test workflow lives at [`.gitea/workflows/test.yml`](../.gitea/workflows/test.yml). It runs `tests/run_tests.py` (full suite — unit + integration) on: - every push to a branch with an open pull request, and - every push to `main`. Integration tests need Docker on the runner; they skip cleanly via `tests/_docker.skip_unless_docker` when no daemon is reachable. A small subset of integration tests skip when running specifically under Gitea Actions (`GITEA_ACTIONS=true`), because `act_runner` runs the job inside a container with the host's `/var/run/docker.sock` mounted in. That topology breaks two assumptions those tests make: - networks created via the host daemon aren't always visible to a same-process `docker network ls` call from inside the job container, and - ports published by sibling containers land on the host's loopback, not on the job container's `127.0.0.1` — so HTTP probes against `http://127.0.0.1:` from inside the job time out. The affected tests (`test_orphan_cleanup.test_create_and_remove`, `test_pipelock_sidecar_smoke.test_smoke`) still run locally where the test process and Docker daemon share a host. Making them work in CI is a follow-up: either re-write them to discover container IPs via `docker inspect`, or reconfigure the runner with host networking. ## Branch protection on `main` Branch protection is **not** captured in tree — Gitea applies it via the repo settings UI / API, not a checked-in config file. Reproducing the rule on a fresh clone or migration therefore means re-applying the same setting through one of the two paths below. ### Via the Gitea UI 1. Go to the repo on `gitea.dideric.is`. 2. **Settings** → **Branches** → **Branch protection rules** → **Add rule**. 3. **Branch name pattern:** `main`. 4. Enable **Enable Status Check** and select the check named `test / run tests/run_tests.py` (the workflow's job display name). The check has to have run at least once on the repo for Gitea to list it; push a no-op commit on a feature branch first if needed. 5. (Recommended) Also enable **Require pull request before merging** so changes to `main` always go through a PR — otherwise a direct push to `main` bypasses the status check entirely. 6. Save. After saving, open a PR. The "Merge" button should be disabled until the test check is green. ### Via the Gitea API Equivalent call (requires an admin token in `$GITEA_TOKEN`): ```sh curl -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ https://gitea.dideric.is/api/v1/repos/didericis/claude-bottle/branch_protections \ -d '{ "rule_name": "main", "enable_status_check": true, "status_check_contexts": ["test / run tests/run_tests.py"], "required_approvals": 0, "block_on_outdated_branch": false }' ``` The exact field for the check name is `status_check_contexts` (an array of glob patterns). The Gitea Actions check appears under ` / ` — here `test / run tests/run_tests.py`. Confirm the actual rendered context string in **Repo → Actions → → Job summary** before pasting into the API call; Gitea versions occasionally tweak the formatting and a typo here silently matches no checks (rule loads, but never blocks). To inspect the live rule: ```sh curl -H "Authorization: token $GITEA_TOKEN" \ https://gitea.dideric.is/api/v1/repos/didericis/claude-bottle/branch_protections ``` ## Verifying the gate Once the rule is in place, prove it works once with a deliberately-failing test on a throwaway branch: 1. Create a branch (`gate-test-DELETEME`), add a test that fails (e.g. `self.assertTrue(False)`), push, open a PR. 2. Wait for the check to go red. Confirm the "Merge" button is disabled / shows the unmet status check. 3. Close the PR and delete the branch. Do not merge. This is a one-time check after applying the rule, not a recurring exercise.