GitNexus/gitnexus/test/integration/lbug-readonly-init.test.ts
Léon Simmons a8a8a3710d
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
fix(lbug): skip init lock and filesystem mutations for read-only opens (#1783) (#1784)
`doInitLbug` unconditionally called `acquireInitLock`, which creates
`${dbPath}.init.lock` inside the workspace. On a Docker `:ro` bind
mount this fails with EROFS.

The init lock prevents a TOCTOU race during DB creation — read-only
opens never create databases and don't need it. Split the init path:

- Read-only: skip path cleanup, init lock, orphan sidecar removal,
  and mkdir. Go straight to preflightLbugSidecars (allowQuarantine:
  false) then openLbugConnection with readOnly: true.
- Writable: unchanged behavior (lock, cleanup, open).
- Shadow-replay recovery: catch EROFS/EACCES/EPERM from the writable
  fallback in ensureReadOnlyConnectionUsable and surface an actionable
  error instead of a raw filesystem exception.

Includes integration test verifying read-only open never creates
lbug.init.lock on disk.

Fixes #1783

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-24 08:05:27 +01:00

29 lines
1 KiB
TypeScript

/**
* Integration Tests: read-only doInitLbug path (#1783)
*
* Verifies that read-only LadybugDB opens skip filesystem mutations
* (init lock, orphan sidecar cleanup, mkdir) so they work on read-only
* filesystems such as Docker :ro bind mounts.
*/
import fs from 'fs/promises';
import { it, expect } from 'vitest';
import { withTestLbugDB } from '../helpers/test-indexed-db.js';
import { _initLockPathForTest } from '../../src/core/lbug/lbug-adapter.js';
withTestLbugDB('lbug-readonly-init', (handle) => {
it('read-only open never creates lbug.init.lock on disk', async () => {
const { dbPath } = handle;
const lockPath = _initLockPathForTest(dbPath);
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
await adapter.closeLbug();
await expect(fs.access(lockPath)).rejects.toMatchObject({ code: 'ENOENT' });
await adapter.withLbugDb(dbPath, async () => {}, { readOnly: true });
await expect(fs.access(lockPath)).rejects.toMatchObject({ code: 'ENOENT' });
await adapter.closeLbug();
});
});