test: downstream client compatibility gate (Phase 1)#7923
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wn + manifest scaffold) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Code Review by Qodo
1.
|
Fail fast if the template's port/auth literals drift so a no-op sed can't silently boot the smoke server on the wrong port/auth. Also ignore docs/** (not just doc/**) so docs-only PRs don't trigger the boot job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @qodo — both addressed in 7c232a2:
|
* test: downstream wire-compat (vectors + smoke) Phase 2 of ether/etherpad#7923. Phase 1 added a canonical wire-format fixture that all Etherpad clients must decode identically. The desktop/mobile apps are thin shells: they embed core's Ace editor in a webview and load a server URL, so there is no local changeset decoder. The vectors test is therefore a fixture-integrity guard (shape/contract of the vendored wire-vectors.json), not a decode test. The smoke test is a headless-light HTTP roundtrip against the server contract the shell depends on; the full Electron e2e stays in this repo's own CI. - packages/shell/tests/fixtures/wire-vectors.json: vendored canonical fixture (overridable via ETHERPAD_WIRE_VECTORS). - packages/shell/tests/wire/vectors.spec.ts: asserts every record has the 5 fields with correct types; pool.numToAttrib is a plain object, nextNum a non-negative integer; initial/resultText non-empty and \n-terminated. - packages/shell/tests/wire/smoke.spec.ts: reads ETHERPAD_SMOKE_URL (default http://localhost:9003) + ETHERPAD_SMOKE_APIKEY; skips cleanly unless both a reachable server and a key are present. When live: create pad via HTTP API, fetch /p/<pad> (the URL the shell loads) -> 200, getText to confirm content. - root package.json: add test:vectors / test:smoke scripts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: address Qodo review on smoke test - Skip the reachability probe entirely when ETHERPAD_SMOKE_APIKEY is unset (no key => the test always skips, so the probe + timeout was wasted work). - Wrap the create -> fetch -> getText roundtrip in try/finally so the pad is always deleted even when an assertion throws; swallow delete errors so cleanup never masks the real failure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: normalize trailing newline in smoke getText assertion Etherpad guarantees a pad ends with exactly one trailing newline, so setText("X\n") reads back as "X\n\n". Compare normalized text instead of exact equality. Verified live against a real core on :9013. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of ether/etherpad#7923. Adds this repo's first test runner (node:test via tsx) plus two suites: - test:vectors — server-free, replays the canonical wire-format fixture through the repo's own Changeset/AttributePool decoders and asserts byte-for-byte text equality. All 5 vectors pass with no decoder changes. - test:smoke — live HTTP+socket.io round-trip; skips cleanly when no server/API key is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR Summary by Qodo
Add downstream client compatibility gate (Phase 1)
🧪 Tests⚙️ Configuration changes📝 Documentation🕐 20-40 MinutesWalkthroughs
User Description
What & why
Downstream clients (the Rust terminal editor
etherpad-pad, the Nodeetherpad-cli-client, andetherpad-desktop) live in separate repos and consume core's wire protocols rather than importing core. A core PR can change the HTTP API, the socket.iomessagesequence, or the changeset/attribpool wire format and break them silently — their CI never runs against the new core.This adds a two-layer compatibility gate so a PR against
developcatches that before merge.Layer A — contract tests (every PR, hermetic, in the existing mocha backend suite)
src/tests/backend/specs/downstream/generate-vectors.tsis the single source of truth;src/tests/fixtures/wire-vectors.jsonis the committed fixture (regenerate withpnpm run vectors:gen).wire-vectors.tsasserts the committed fixture is byte-identical to a fresh regeneration (any drift = a deliberate wire change) and self-consistent under core'sChangeset. Downstream clients reuse the same JSON with their own decoders (Phase 2).wire-socket-sequence.tspins handshake→CLIENT_VARSandUSER_CHANGES→ACCEPT_COMMIT.wire-http-api.tssnapshotscreatePad/setText+getText/getRevisionsCountresponse shapes.Layer B — downstream smoke harness
.github/workflows/downstream-smoke.yml: boots a real Etherpad on :9003 with apikey auth, healthcheck-polls, runs an authenticated create→read self-check, generates the canonical vectors, then matrixes over the manifest — tearing the server down by PID.src/tests/downstream/clients.json: manifest of the three clients pinned to specific commit SHAs (so a client's own breakage can't redden core; bumping a ref is a deliberate PR). All entries areenabled:falseuntil their Phase-2 smoke lands, so the harness lands green on its own.Verification
mocha --recursive tests/backend/specs/downstream).Scope
Phase 1 is core-only. Phase 2 wires each client repo's vector + smoke test and flips its manifest entry to
enabled:true, one repo at a time (pad → cli → desktop). Spec + plan underdocs/superpowers/.🤖 Generated with Claude Code
AI Description
Diagram
graph TD PR["Core PR"] --> A["Layer A: Downstream specs"] --> F[("wire-vectors.json")] A --> G["generate-vectors.ts"] A --> P["Socket/HTTP pins"] PR --> W["Layer B: Smoke workflow"] --> B["Boot Etherpad :9003"] --> M["clients.json (enabled gate)"] B --> G --> FHigh-Level Assessment
The following are alternative approaches to this PR:
1. Run downstream repos' CI via reusable workflows
2. Schema-first contracts (OpenAPI + explicit socket message schemas)
3. Pact-style contract testing
Recommendation: The PR’s hybrid approach (golden vectors + protocol/shape pins in core, plus a smoke harness scaffold) is the best Phase 1 tradeoff: it is deterministic, runs on every PR, and creates a shared artifact (wire-vectors.json) that downstream clients can validate against without importing core. Keep the manifest pinned and disabled-by-default until each client has a stable, minimal smoke + vector test (Phase 2), and consider tightening the smoke workflow over time (e.g., artifact caching for cloned repos/toolchains and explicit timeouts/retries per client to control flake).
File Changes
Tests (5)
Documentation (2)
Other (3)