test(auth): de-flake verify-user-credentials.live.spec — hoist bcrypt hash + 30s timeout

Under a fully parallel `pnpm -r test` run the suite computed five separate
bcrypt cost-12 hashes (one per test, ~300ms idle, multi-second with all cores
saturated) and tripped jest's default 5s per-test timeout ("DISABLED user"
test, suite 31s under load vs 6s isolated).

- compute the hash ONCE in a top-level beforeAll (covers both describe
  blocks) and share the read-only string across the five former call sites
- jest.setTimeout(30_000) at module scope so the per-test bcrypt compares
  inside verifyUserCredentials get headroom under load too

No change to test names, assertions, order, or the CREDENTIALS_MISMATCH
contract semantics. Suite: 8/8 green, 4.8s isolated (was ~6s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 17:24:24 +03:00
parent da94b1589c
commit 89a4bf28ce
@@ -23,8 +23,21 @@ import { hashPassword } from '../../../common/helpers';
* unthrottled password-guessing oracle.
*/
// bcrypt cost-12 hashing/compare takes ~300ms idle but multiple seconds when
// parallel jest workers saturate all CPU cores; the 5s default flakes.
jest.setTimeout(30_000);
const WORKSPACE_ID = 'ws-1';
let passwordHash: string;
// Hoist the expensive work: compute ONE bcrypt cost-12 hash shared by all
// tests instead of five. The hash is a read-only string and each test builds
// its own user object around it, so sharing is safe.
beforeAll(async () => {
passwordHash = await hashPassword('correct-horse');
}, 30_000);
// Build an AuthService with the dependencies verifyUserCredentials/login touch
// stubbed, and a userRepo whose findByEmail is overridable per test. Only the
// collaborators actually reached on these paths need real behaviour; the rest
@@ -95,7 +108,6 @@ describe('AuthService.verifyUserCredentials (live credentials-mismatch contract)
it('DISABLED user -> throws exactly CREDENTIALS_MISMATCH_MESSAGE (no password oracle)', async () => {
// A deactivated user must be indistinguishable from a wrong password: same
// message, before any password comparison.
const passwordHash = await hashPassword('correct-horse');
const disabledUser = {
id: 'u-1',
email: 'disabled@example.com',
@@ -117,7 +129,6 @@ describe('AuthService.verifyUserCredentials (live credentials-mismatch contract)
});
it('WRONG password -> throws exactly CREDENTIALS_MISMATCH_MESSAGE', async () => {
const passwordHash = await hashPassword('correct-horse');
const user = {
id: 'u-1',
email: 'user@example.com',
@@ -139,7 +150,6 @@ describe('AuthService.verifyUserCredentials (live credentials-mismatch contract)
});
it('CORRECT credentials -> resolves the matched user (no side effects here)', async () => {
const passwordHash = await hashPassword('correct-horse');
const user = {
id: 'u-1',
email: 'user@example.com',
@@ -179,7 +189,6 @@ describe('AuthService.login (live credentials-mismatch contract via verifyUserCr
});
it('WRONG password -> login throws exactly CREDENTIALS_MISMATCH_MESSAGE', async () => {
const passwordHash = await hashPassword('correct-horse');
const user = {
id: 'u-1',
email: 'user@example.com',
@@ -201,7 +210,6 @@ describe('AuthService.login (live credentials-mismatch contract via verifyUserCr
});
it('CORRECT credentials -> login mints the session (the side-effecting path)', async () => {
const passwordHash = await hashPassword('correct-horse');
const user = {
id: 'u-1',
email: 'user@example.com',