Files
support_backend/tests/unit/identity/login-rate-limit-ordering.test.ts
T
saqib mirandClaude Sonnet 5 79bc2ef25b feat(013-auth-hardening): password reset, password strength policy, login rate-limiting
Closes the two gaps 010-identity-auth explicitly deferred (password reset,
login rate-limiting), plus a shared password-strength validator both the
reset-consume endpoint and admin account creation now depend on.

- Password reset: single-use, paired-Redis-key tokens (never in Postgres),
  identical response regardless of account existence, stubbed delivery via
  a structured log line (no email infrastructure exists yet).
- Password strength: one validatePasswordStrength() call site, wired into
  both POST /admin/users and the reset-consume flow.
- Login rate-limiting: checkRateLimit keyed by submitted email, checked
  before any credential verification.

Also fixes tests/helpers/auth.ts's shared loginAs() helper, which reused
two fixed accounts across the whole integration suite via upsert — now
rate-limited per email, that collided across ~30 files sharing one budget.
Each call now gets a unique email; no call sites needed to change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 21:22:49 +05:30

38 lines
1.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AuthService } from '@/modules/identity/auth/service/auth.service';
import * as cache from '@/infrastructure/cache';
describe('AuthService.login rate-limit ordering (User Story 3)', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('checks the rate limit before ever looking up the account', async () => {
const findByEmail = vi.fn().mockResolvedValue(null);
const repo = { findByEmail } as never;
const service = new AuthService(repo);
vi.spyOn(cache, 'checkRateLimit').mockResolvedValue({ allowed: false, count: 6 });
await expect(
service.login({ email: 'agent@example.com', password: 'anything' }),
).rejects.toMatchObject({ statusCode: 429 });
expect(findByEmail).not.toHaveBeenCalled();
});
it('proceeds to credential checks once the rate limit allows the attempt', async () => {
const findByEmail = vi.fn().mockResolvedValue(null);
const repo = { findByEmail } as never;
const service = new AuthService(repo);
vi.spyOn(cache, 'checkRateLimit').mockResolvedValue({ allowed: true, count: 1 });
await expect(
service.login({ email: 'agent@example.com', password: 'anything' }),
).rejects.toMatchObject({ statusCode: 401 });
expect(findByEmail).toHaveBeenCalledWith('agent@example.com');
});
});