import { describe, it, expect, beforeEach } from 'vitest'; import type Database from 'better-sqlite3'; import { initDb } from '../../src/db/schema.ts'; import { getLoginAttempt, recordFailure, resetLoginAttempts, } from '../../src/db/login-attempts.ts'; describe('login-attempts db', () => { let db: Database.Database; beforeEach(() => { db = initDb(':memory:'); }); it('returns undefined for an unknown username', () => { expect(getLoginAttempt(db, 'ghost')).toBeUndefined(); }); it('inserts a row on first failure with count=1 and no lock', () => { const row = recordFailure(db, 'alice', null); expect(row.username).toBe('alice'); expect(row.failed_count).toBe(1); expect(row.last_failed_at).toBeTruthy(); expect(row.locked_until).toBeNull(); }); it('increments failed_count on subsequent failures', () => { recordFailure(db, 'alice', null); recordFailure(db, 'alice', null); const row = recordFailure(db, 'alice', null); expect(row.failed_count).toBe(3); }); it('persists locked_until when supplied', () => { const lockedUntil = new Date(Date.now() + 30_000).toISOString(); const row = recordFailure(db, 'alice', lockedUntil); expect(row.locked_until).toBe(lockedUntil); }); it('updates locked_until on subsequent failures', () => { recordFailure(db, 'alice', null); const newLock = new Date(Date.now() + 60_000).toISOString(); const row = recordFailure(db, 'alice', newLock); expect(row.failed_count).toBe(2); expect(row.locked_until).toBe(newLock); }); it('resetLoginAttempts deletes the row', () => { recordFailure(db, 'alice', null); resetLoginAttempts(db, 'alice'); expect(getLoginAttempt(db, 'alice')).toBeUndefined(); }); it('reset on a missing username is a no-op', () => { expect(() => resetLoginAttempts(db, 'ghost')).not.toThrow(); }); it('tracks failures for non-existent users (no FK to users table)', () => { const row = recordFailure(db, 'never-existed-user', null); expect(row.failed_count).toBe(1); }); it('getLoginAttempt returns the stored row', () => { recordFailure(db, 'alice', null); recordFailure(db, 'alice', null); const row = getLoginAttempt(db, 'alice'); expect(row?.username).toBe('alice'); expect(row?.failed_count).toBe(2); }); });