P2-9: Integration tests against a live cairnd in CI #29

Closed
opened 2026-09-10 17:42:53 +00:00 by Cordy · 1 comment
Owner

Depends on P2-8. Closes phase 2.

Goal

Prove the client works against a real Cairn server, not just fakes. Every layer below this has been tested in isolation; this is the first test of the whole stack.

Files

  • Create: .forgejo/workflows/interop.yml, test/integration/sync_test.go

Pattern to follow

The Cairn repo already does exactly this in interop-rclone.yml, interop-tus.yml and interop-s3.yml. Copy the shape: start a real cairnd in the job, run the client against it, assert on the outcome. Same runner constraints as ci.yml — host mode, arch detection, no TTY.

Job outline

  • Start cairnd in the background with a temp POSIX storage root, local auth mode, one test user, enableWebdav: true. Wait for the port to answer before proceeding.
  • Build cairnsync.
  • Run the scenarios below with cairnsync --once --json, asserting on output and on the state of both sides.

Scenarios — each is a separate test

  1. Upload: create local files including nested directories, sync, assert they exist server-side with correct content.
  2. Download: PUT files directly to the server over WebDAV, sync, assert they appear locally.
  3. Idempotence: sync twice, assert the second pass applies zero operations.
  4. Local delete propagates, and remote delete propagates.
  5. Rename is a MOVE, not a re-upload. Rename a local file, sync, and assert the server received a MOVE — not a DELETE plus PUT. Detect it by asserting the file's server-side ETag is unchanged, or by counting bytes transferred.
  6. Conflict: edit the same file on both sides between passes; assert both versions survive and the conflict copy is named per ConflictName.
  7. Large file over tus: create a file above the tus threshold, sync, assert content matches byte-for-byte.
  8. Interrupted upload resumes: kill cairnsync mid-transfer of a large file, restart it, assert the file completes and fewer total bytes were transferred than a fresh upload.
  9. Quota: configure a small maxTotalBytes, exceed it, assert the client surfaces a quota skip and does not retry-loop.
  10. Unicode and awkward names: files with non-ASCII names, spaces, and (on POSIX) a name that would be illegal on Windows. Assert the illegal one is skipped with a reason rather than failing the run.

The one that matters most

Scenario 8. Interrupted transfers are where real users lose data and where fakes are least convincing. If only one scenario is fully rigorous, make it this one.

Steps

  • Write the workflow and the tests.
  • Dispatch; read results from kubectl logs -n forgejo-runner deploy/forgejo-runner.
  • Iterate until green.
  • Tag: git tag -a v0.2.0-integration -m "sync engine against a real Cairn server"
  • Close the phase-2-integration milestone.

Acceptance criteria

  • All ten scenarios pass against a real cairnd in CI.
  • The workflow is repeatable — no leftover state between runs.
  • Tag v0.2.0-integration exists.
Depends on P2-8. Closes phase 2. ## Goal Prove the client works against a **real Cairn server**, not just fakes. Every layer below this has been tested in isolation; this is the first test of the whole stack. ## Files - Create: `.forgejo/workflows/interop.yml`, `test/integration/sync_test.go` ## Pattern to follow The Cairn repo already does exactly this in `interop-rclone.yml`, `interop-tus.yml` and `interop-s3.yml`. Copy the shape: start a real `cairnd` in the job, run the client against it, assert on the outcome. Same runner constraints as `ci.yml` — host mode, arch detection, no TTY. ## Job outline - [ ] Start `cairnd` in the background with a temp POSIX storage root, local auth mode, one test user, `enableWebdav: true`. Wait for the port to answer before proceeding. - [ ] Build `cairnsync`. - [ ] Run the scenarios below with `cairnsync --once --json`, asserting on output and on the state of both sides. ## Scenarios — each is a separate test 1. **Upload:** create local files including nested directories, sync, assert they exist server-side with correct content. 2. **Download:** PUT files directly to the server over WebDAV, sync, assert they appear locally. 3. **Idempotence:** sync twice, assert the second pass applies **zero** operations. 4. **Local delete propagates**, and **remote delete propagates**. 5. **Rename is a MOVE, not a re-upload.** Rename a local file, sync, and assert the server received a `MOVE` — not a DELETE plus PUT. Detect it by asserting the file's server-side ETag is unchanged, or by counting bytes transferred. 6. **Conflict:** edit the same file on both sides between passes; assert both versions survive and the conflict copy is named per `ConflictName`. 7. **Large file over tus:** create a file above the tus threshold, sync, assert content matches byte-for-byte. 8. **Interrupted upload resumes:** kill `cairnsync` mid-transfer of a large file, restart it, assert the file completes and fewer total bytes were transferred than a fresh upload. 9. **Quota:** configure a small `maxTotalBytes`, exceed it, assert the client surfaces a quota skip and does **not** retry-loop. 10. **Unicode and awkward names:** files with non-ASCII names, spaces, and (on POSIX) a name that would be illegal on Windows. Assert the illegal one is skipped with a reason rather than failing the run. ## The one that matters most **Scenario 8.** Interrupted transfers are where real users lose data and where fakes are least convincing. If only one scenario is fully rigorous, make it this one. ## Steps - [ ] Write the workflow and the tests. - [ ] Dispatch; read results from `kubectl logs -n forgejo-runner deploy/forgejo-runner`. - [ ] Iterate until green. - [ ] Tag: `git tag -a v0.2.0-integration -m "sync engine against a real Cairn server"` - [ ] Close the `phase-2-integration` milestone. ## Acceptance criteria - All ten scenarios pass against a real `cairnd` in CI. - The workflow is repeatable — no leftover state between runs. - Tag `v0.2.0-integration` exists.
Author
Owner

Done

  • 7fa30ef test(integration): cairnsync against a live cairnd in CI
  • 5780a33 fix(integration): reclaim a killed interop run, record cairnd on Linux
  • 54bd91a fix(integration): leave the client's request body to the proxy's server
  • final-review fix wave: 3882918 (X1), 9d77fb3 (X5), 3a06d72 (X2), cdebdb9 (X4), 1819f67 (X7), e4aead3 (X3)

What was built
.forgejo/workflows/interop.yml + test/integration/sync_test.go: all 10 issue scenarios via cairnsync --once --json against a real cairnd bd006ef (anonymous Cordy/Cairn clone, two throwaway users, every state path + HOME in one mktemp dir), driven through a recording reverse proxy with an independent net/http oracle on both sides. Plus the whole-branch final-review fix wave: X1 re-reads a vouched local file before deleting or overwriting it; X2 refuses a pass when either side lists nothing but rows exist (--allow-empty opt-in); X3 reclaims a SIGKILLed interop run's TMPDIR and processes; X4 never records a hash against a server copy cut short; X5 keeps a replaced file's permissions across OSFS.Write; X7 compares the runner's Linux cairnd answers against the committed capture.

Tests
CI run(s) ci.yml: run #31 (id 2931, http://192.168.10.245/Cordy/cairn-desktop/actions/runs/31); interop.yml: run #32 (id 2932, http://192.168.10.245/Cordy/cairn-desktop/actions/runs/32) — both confirmed via runner env dump to have GITHUB_SHA=e4aead3be6b11287d3331be1d93e675892158ec8, coverage total: (statements) 90.2%. Integration suite: 18 PASS + 1 SKIP (twins, APFS-only host), CGO_ENABLED=1 -race clean, 10 mutation tables (M1–M10) killed for #29, 3 more for the fix wave (X1–X5).

Acceptance criteria

  • All ten scenarios pass against a real cairnd in CI — met: interop.yml run #32, green, on the pinned Cordy/Cairn ref bd006ef.
  • Repeatable, no leftover state between runs — met: X3 closes the SIGKILL leak (TMPDIR under $work, pre-clean stops orphaned processes); verified by a kill-and-rerun on the extracted step.
  • Tag v0.2.0-integration exists — met, see below.

Tag
v0.2.0-integratione4aead3.

Rulings

  • P2-R2: tagging, closing #29 and closing the milestone are deferred until the whole-branch final review and its fix wave land with green CI — done now.
  • P2-R11/R12/R15/R17/R18 (interop.yml's anonymous-clone pattern, the integration build tag, cairnd's documented deviations, perUserBytes for quota, throwaway users only) — all confirmed at runtime in the suite.
  • P2-R16: cairnd's conditional-write gaps are recorded as t.Skip, never a failure — see Server notes.

Final review
Verdict: "ready to tag, with fixes." Two Important findings — X1 (a vouched local hash fed destructive local deletes/overwrites) and X2 (an empty side deleted every copy on the other) — plus five Minor (X3–X7), all landed in the fix wave (54bd91a..e4aead3), each test-first with a named mutant killed; ci.yml and interop.yml green afterward on e4aead3.

Server notes
For the owner (P2-R16, final review X7/X10): cairnd at bd006ef ignores If-Match and If-None-Match on PUT, DELETE and MOVE (verified by the integration suite), and returns no ETag on PUT or on tus finalize. The client's re-check just before each write narrows the concurrent-write window but cannot close it until the server honours conditional requests and returns ETags — both are worth a Cordy/Cairn issue. The three later-phase hand-off drafts (#34, #43, #44) await the owner's approval and are not part of this tag.

Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI.

**Done** - [7fa30ef](http://192.168.10.245/Cordy/cairn-desktop/commit/7fa30ef80cd4e11dde08520bc57853a0683401e5) test(integration): cairnsync against a live cairnd in CI - [5780a33](http://192.168.10.245/Cordy/cairn-desktop/commit/5780a33e7473b5e4e7a35d777eb51028b7ff45ca) fix(integration): reclaim a killed interop run, record cairnd on Linux - [54bd91a](http://192.168.10.245/Cordy/cairn-desktop/commit/54bd91ad22b76a14c1a37da2bdcc3c59f33f6218) fix(integration): leave the client's request body to the proxy's server - final-review fix wave: [3882918](http://192.168.10.245/Cordy/cairn-desktop/commit/3882918c0501dc69e4055296714aa538cbad8141) (X1), [9d77fb3](http://192.168.10.245/Cordy/cairn-desktop/commit/9d77fb30769c72223c497307ea2a46d0abb13c42) (X5), [3a06d72](http://192.168.10.245/Cordy/cairn-desktop/commit/3a06d72805d3c78a531009959cfdaf14c07447f6) (X2), [cdebdb9](http://192.168.10.245/Cordy/cairn-desktop/commit/cdebdb987e11a024e641f3d3b919031a3d0449dc) (X4), [1819f67](http://192.168.10.245/Cordy/cairn-desktop/commit/1819f67e6d9d856979f73b42a35c2434bded905a) (X7), [e4aead3](http://192.168.10.245/Cordy/cairn-desktop/commit/e4aead3be6b11287d3331be1d93e675892158ec8) (X3) **What was built** `.forgejo/workflows/interop.yml` + `test/integration/sync_test.go`: all 10 issue scenarios via `cairnsync --once --json` against a real cairnd bd006ef (anonymous Cordy/Cairn clone, two throwaway users, every state path + HOME in one mktemp dir), driven through a recording reverse proxy with an independent net/http oracle on both sides. Plus the whole-branch final-review fix wave: X1 re-reads a vouched local file before deleting or overwriting it; X2 refuses a pass when either side lists nothing but rows exist (`--allow-empty` opt-in); X3 reclaims a SIGKILLed interop run's TMPDIR and processes; X4 never records a hash against a server copy cut short; X5 keeps a replaced file's permissions across `OSFS.Write`; X7 compares the runner's Linux cairnd answers against the committed capture. **Tests** CI run(s) ci.yml: run #31 (id 2931, http://192.168.10.245/Cordy/cairn-desktop/actions/runs/31); interop.yml: run #32 (id 2932, http://192.168.10.245/Cordy/cairn-desktop/actions/runs/32) — both confirmed via runner env dump to have GITHUB_SHA=e4aead3be6b11287d3331be1d93e675892158ec8, coverage total: (statements) 90.2%. Integration suite: 18 PASS + 1 SKIP (twins, APFS-only host), `CGO_ENABLED=1 -race` clean, 10 mutation tables (M1–M10) killed for #29, 3 more for the fix wave (X1–X5). **Acceptance criteria** - All ten scenarios pass against a real cairnd in CI — met: interop.yml run #32, green, on the pinned Cordy/Cairn ref bd006ef. - Repeatable, no leftover state between runs — met: X3 closes the SIGKILL leak (TMPDIR under `$work`, pre-clean stops orphaned processes); verified by a kill-and-rerun on the extracted step. - Tag `v0.2.0-integration` exists — met, see below. **Tag** `v0.2.0-integration` → [e4aead3](http://192.168.10.245/Cordy/cairn-desktop/commit/e4aead3be6b11287d3331be1d93e675892158ec8). **Rulings** - P2-R2: tagging, closing #29 and closing the milestone are deferred until the whole-branch final review and its fix wave land with green CI — done now. - P2-R11/R12/R15/R17/R18 (interop.yml's anonymous-clone pattern, the `integration` build tag, cairnd's documented deviations, `perUserBytes` for quota, throwaway users only) — all confirmed at runtime in the suite. - P2-R16: cairnd's conditional-write gaps are recorded as `t.Skip`, never a failure — see Server notes. **Final review** Verdict: "ready to tag, with fixes." Two Important findings — X1 (a vouched local hash fed destructive local deletes/overwrites) and X2 (an empty side deleted every copy on the other) — plus five Minor (X3–X7), all landed in the fix wave (54bd91a..e4aead3), each test-first with a named mutant killed; ci.yml and interop.yml green afterward on e4aead3. **Server notes** For the owner (P2-R16, final review X7/X10): cairnd at bd006ef ignores `If-Match` and `If-None-Match` on PUT, DELETE and MOVE (verified by the integration suite), and returns no ETag on PUT or on tus finalize. The client's re-check just before each write narrows the concurrent-write window but cannot close it until the server honours conditional requests and returns ETags — both are worth a Cordy/Cairn issue. The three later-phase hand-off drafts (#34, #43, #44) await the owner's approval and are not part of this tag. _Implemented and reviewed by Claude (subagent-driven), landed on main after review and green CI._
Cordy closed this issue 2026-09-11 20:54:53 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/cairn-desktop#29
No description provided.