mirror of
https://github.com/tompro/sattle.git
synced 2026-10-26 01:49:58 +00:00
test: stub Web Locks where the runtime lacks them
This commit is contained in:
@@ -14,5 +14,8 @@ export default defineConfig({
|
|||||||
// jsdom. The ops suite spins real mock-mint HTTP servers on loopback.
|
// jsdom. The ops suite spins real mock-mint HTTP servers on loopback.
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
include: ['src/**/*.test.ts'],
|
include: ['src/**/*.test.ts'],
|
||||||
|
// Node 22 (CI) has no navigator.locks; install a passthrough
|
||||||
|
// LockManager where it is absent so the suite is hermetic
|
||||||
|
setupFiles: ['./vitest.setup.ts'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// vitest setup (registered via setupFiles in vitest.config.ts).
|
||||||
|
//
|
||||||
|
// CI runs the suite on Node 22, where navigator.locks does not exist, so
|
||||||
|
// the fail-closed fund-mutation guard (withRequiredStorageLock) rejects
|
||||||
|
// every mutation; locally on Node 24 the native Web Locks are present.
|
||||||
|
// Install a minimal in-memory LockManager where locks are absent so the
|
||||||
|
// suite is hermetic across Node 20/22/24. It serializes same-name
|
||||||
|
// requests like the real API (storageLock.test.ts asserts that ordering)
|
||||||
|
// but gives no cross-tab guarantee - the same trade-off as
|
||||||
|
// withStorageLock's unlocked fallback. Tests that exercise the no-locks
|
||||||
|
// refusal stub navigator themselves (vi.stubGlobal('navigator', {})),
|
||||||
|
// which overrides this install. FORCE_STUB_LOCKS=1 replaces the native
|
||||||
|
// LockManager too, proving the suite never depends on real lock semantics.
|
||||||
|
|
||||||
|
const createPassthroughLockManager = (): {
|
||||||
|
request: (name: string, callback: () => unknown) => Promise<unknown>;
|
||||||
|
} => {
|
||||||
|
const tail = new Map<string, Promise<unknown>>();
|
||||||
|
return {
|
||||||
|
request: (name, callback) => {
|
||||||
|
const previous = tail.get(name) ?? Promise.resolve();
|
||||||
|
const result = previous.then(callback);
|
||||||
|
tail.set(
|
||||||
|
name,
|
||||||
|
result.then(
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const installPassthroughLocks = (): void => {
|
||||||
|
const locks = createPassthroughLockManager();
|
||||||
|
const navigator = globalThis.navigator;
|
||||||
|
if (typeof navigator === 'undefined') {
|
||||||
|
Object.defineProperty(globalThis, 'navigator', {
|
||||||
|
value: { locks },
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.defineProperty(navigator, 'locks', {
|
||||||
|
value: locks,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const locksAbsent =
|
||||||
|
typeof globalThis.navigator === 'undefined' ||
|
||||||
|
!('locks' in globalThis.navigator) ||
|
||||||
|
!globalThis.navigator.locks;
|
||||||
|
|
||||||
|
if (process.env.FORCE_STUB_LOCKS === '1' || locksAbsent) {
|
||||||
|
installPassthroughLocks();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user