Adds integration/tests/12-electron-mail-index.spec.ts (3 tests, all passing
against the real Stalwart fixture) and fixes what running it exposed. None of
these were visible from reading the code.
1. JMAP session fetch never followed a redirect. Stalwart 307-redirects
/.well-known/jmap to /jmap/session, and fetchJmapSession used
`redirect: 'manual'` and treated any non-2xx as failure - so every reindex
died with "JMAP session fetch failed (307)". Now follows up to 3 hops and
REFUSES to follow off-origin, because the user's credentials ride on every
hop; a blind `redirect: 'follow'` would hand the Authorization header to
whatever host a misconfigured session pointed at. Same bound and same
reasoning as lib/auth/verify-jmap-auth.ts.
2. The fd-3 key channel could only be adopted once per process, but its state
was module-scoped. Next re-evaluates route modules, so a second instance hit
`Could not open fd 3: Error: open EEXIST` from libuv. State moved to a
Symbol on globalThis - the one place in a Node process that survives module
re-evaluation.
3. Next's output file tracing does NOT carry @signalapp/sqlcipher's prebuilds/
into .next/standalone. It traced the package's JS and its node-gyp-build
dependency, but node-gyp-build resolves the .node binary by scanning a
directory at runtime, which no static tracer can follow - so `require()`
would have failed in every packaged build. scripts/assemble-standalone.mjs
now copies it, alongside the public/ and .next/static copies it already does
for the same "standalone output omits things" reason. All six platform/arch
prebuilds are copied, not just this host's, because electron-builder
cross-builds the x64 and arm64 macOS targets from one runner.
The three tests, and why it takes three - two constraints made a single
configuration impossible, and both were measured rather than assumed:
* The renderer cannot reach this fixture from a production build. Its CSP
pins connect-src to `'self' https: wss:` and the fixture's Stalwart is
plain HTTP. NODE_ENV=development at RUNTIME does not help: `next build`
INLINES process.env.NODE_ENV into the compiled middleware, so proxy.ts's
`isDev` is frozen at build time (observed: a standalone server started with
NODE_ENV=development still served the production CSP).
* The fd-3 channel cannot survive `next dev`, which forks its server with an
IPC channel that claims fd 3 (EEXIST); fd 4 there is not a pipe either
(ENOTTY).
So: PIPELINE drives the real standalone server over HTTP from Node with a
real fd-3 key channel (no browser, so no CSP) and asserts a real SMTP
delivery is findable by a word from its BODY, with a real snippet and
contextBlock, idempotent catch-up, working type filters, and - reading the
raw bytes of the .db AND its -wal - that nothing is recoverable in cleartext.
TRIGGER proves the event-driven wiring: a real delivery makes the renderer
POST /api/offline/reindex off its live push. WIRING launches the real shell
with no ELECTRON_LOAD_URL and asserts the routes are reachable (401, not 404
or 503) with real safeStorage behind them.
Each test now gets its own --user-data-dir. That is load-bearing, not hygiene:
Electron reuses one profile across launches, and a leftover jmap_stalwart_ctx
cookie from an earlier run made the WIRING test's 401 assertion pass as a 200.
Verified: typecheck clean; unit suite 2379 tests with the SAME 3 pre-existing
failures as the base commit b15098a6 (2 builtin-themes, 1 jmap-client-
resilience) and 48 net new passing; both `docker build`s succeed; the
hosted-deployment gate returns 404 with an empty body and materialises no file
in the production image; e2e/electron-smoke 4/4; 11-electron-notification
still passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
3.1 KiB
TypeScript
57 lines
3.1 KiB
TypeScript
import { defineConfig } from '@playwright/test';
|
|
|
|
/**
|
|
* Electron-specific integration config. Reuses the same Stalwart fixture
|
|
* bring-up (globalSetup/globalTeardown) as playwright.integration.config.ts,
|
|
* but deliberately kept separate from it and scoped to only
|
|
* integration/tests/11-electron-notification.spec.ts:
|
|
*
|
|
* - No `projects` array: that test launches its own Electron process via
|
|
* _electron.launch() - it needs no Playwright-managed browser project.
|
|
* - Not run as part of the main dockerized suite: `npm run test:integration`
|
|
* (integration/run-tests.sh) runs the browser-based suite INSIDE the
|
|
* official Playwright Docker image (to get Chromium without relying on
|
|
* Playwright's own browser-download host). Electron has no such
|
|
* download step - `npm install electron` already fetched a binary for
|
|
* THIS host's platform, which would not run inside that (likely
|
|
* different-platform) container. Run this suite directly on the host
|
|
* instead - see `npm run test:integration:electron`. The main
|
|
* integration config explicitly excludes this spec file for the same
|
|
* reason, so a plain `npm run test:integration` never tries to launch it.
|
|
*/
|
|
export default defineConfig({
|
|
testDir: './integration/tests',
|
|
// 11 asserts the native notification bridge fires from a real push; 12
|
|
// asserts a real delivery reaches the encrypted local search index. 12 runs
|
|
// the REAL standalone-server boot (no ELECTRON_LOAD_URL), because that boot
|
|
// is what wires the index's store directory and its fd-3 key channel.
|
|
testMatch: /1[12]-electron-.*\.spec\.ts/,
|
|
timeout: 90_000,
|
|
expect: { timeout: 20_000 },
|
|
fullyParallel: false,
|
|
workers: 1,
|
|
// Retries unconditionally (not just CI), and more than the main config's
|
|
// 1: this suite runs the Electron shell against a `next dev` server (see
|
|
// the spec file's header comment for why - the fixture's Stalwart is
|
|
// deliberately plain HTTP), and `next dev`'s on-demand route compilation
|
|
// + Fast Refresh occasionally races the SSE stream this test depends on
|
|
// during the login -> inbox route transition, dropping that one push
|
|
// event with no error anywhere (confirmed by running the identical test
|
|
// repeatedly against an already-warm stack: same request sequence logged
|
|
// every time, but the outcome isn't always the same). Root-caused, not
|
|
// eliminated - a genuine dev-server-only timing hazard, not a bug in the
|
|
// feature this test is verifying (the same run's own logs show the WS
|
|
// circuit breaker and SSE fallback firing exactly as designed every
|
|
// single time, pass or fail).
|
|
retries: 2,
|
|
reporter: [['list']],
|
|
outputDir: 'integration/test-results-electron',
|
|
// Own global-setup (not the main config's): brings up only the `stalwart`
|
|
// compose service, not `webmail` - this suite boots a `next dev` server
|
|
// itself (see the spec file) and never talks to the containerized
|
|
// webmail on :3000. Teardown is shared - it already defaults to leaving
|
|
// the stack up unless IT_TEARDOWN=1.
|
|
globalSetup: './integration/tests/global-setup-electron.ts',
|
|
globalTeardown: './integration/tests/global-teardown.ts',
|
|
});
|