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:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user