Files
lvmh/e2e

lvmh e2e integration harness

Cross-component tests proving the seams between the three real components:

  • daemon — the golang server, booted with go run . from ../daemon (real HTTP server, real sqlite store, real WS hubs)
  • web dist — the production web/dist build served BY the daemon via --webdist (built first if missing; npm install too when node_modules is absent)
  • plugin client — a scripted websocket client speaking exactly the protocol the real plugin/lvmh-agent.ts speaks (PROTOCOL.md v: 1, flattened envelopes, Authorization: Bearer … on the upgrade) — plus a fake GitLab v4 (/api/v4/user, /api/v4/projects) on an ephemeral port.

No Docker, no LLM key, no real GitLab needed. Zero npm dependencies (Node built-ins only; the global WebSocket client, Node ≥ 22).

Run

make integration      # or: make e2e-integration, or: node e2e/driver.mjs

Prerequisites: go (toolchain for go run .) and node >= 22. First run compiles the daemon and may build the web dist; later runs reuse both. Exit code is non-zero when any check fails. LVMH_E2E_KEEP=1 keeps the harness scratch dir (daemon log + db) under .pi/scratch/e2e-*/ for debugging.

What is covered

scenario proves
auth REST 401 without/with wrong bearer; web /ws?token= rejected before upgrade. Agent-WS rejection is xfail: daemon Routes() serves /agent/ws without bearerAuth (protocol §Auth violation, real bug — see check reason).
web-dist daemon serves the real built UI: GET / index, hashed /assets/*, manifest.webmanifest, SPA fallback.
agent-lifecycle hello → welcome{lastSeq:0}, persisted events, GET /api/sessions online flag + snapshot fields, offline after disconnect, events?after= slicing, envelope re-serialization.
replay reconnect welcome.lastSeq = persisted high-water; seq continuation; message_update fanned out live but never persisted; REST events unaffected.
prompt-routing browser /ws subscribe → batched live events frames incl. message_update; POST /api/sessions/:id/promptprompt frame on the agent WS (promptId, envelope); 409 when offline.
session-list session_list broadcasts to every web client on session change (second subscriber included), online flag flips on disconnect.
gitlab POST /api/gitlab/connect against the fake GitLab (PAT forwarded as Private-Token), repos mapping + daemon-side sort by activity, status, PAT never present in any response, failed connect does not clobber the stored PAT.
spawn-validation POST /api/spawn rejects non group/project repos and malformed JSON with 400; /api/spawn/status shape. (A valid spawn needs Docker and is intentionally out of scope.)
resilience SIGKILL of the daemon process group + reboot on the same DB: sessions listed online:false, every persisted event intact, welcome.lastSeq honoured, seq continues past the high-water.

Layout

driver.mjs           boots daemon + fake gitlab, runs scenarios, summary, exit code
lib.mjs              helpers: deadline polling, REST client, scripted WS clients
fake-gitlab.mjs      minimal GitLab v4 on an ephemeral port
scenarios/*.mjs      one module per scenario (assert-based checks)

Determinism: ephemeral ports everywhere, all waits are deadline-bounded polls (waitUntil / frame waiters), never fixed sleeps. The daemon runs in its own process group and is always killed on exit (including on crash or SIGINT).

xfail entries are known-broken assertions (documented real bugs): they do not fail the run; if one starts passing it is reported as xpass so the xfail can be dropped.