import { describe, expect, it } from 'vitest' import { assertUsablePin, hashPin, verifyPin, FRESH_LOCKOUT, isLocked, recordFailure, recordSuccess, DEFAULT_LOCKOUT, can, gateFor, openSession, } from '@sims/auth' import type { BusinessDay, User } from '@sims/domain' describe('PIN policy & hashing', () => { it('accepts sane PINs, rejects weak ones', () => { expect(() => assertUsablePin('4728')).not.toThrow() expect(() => assertUsablePin('12')).toThrow(/4–6 digits/) expect(() => assertUsablePin('0000')).toThrow(/repeated/) expect(() => assertUsablePin('1234')).toThrow(/sequence/) expect(() => assertUsablePin('8765')).toThrow(/sequence/) }) it('hashes with per-credential salt and verifies', () => { const stored = hashPin('4728') expect(verifyPin('4728', stored)).toBe(true) expect(verifyPin('4729', stored)).toBe(false) expect(hashPin('4728').hash).not.toBe(stored.hash) // fresh salt every time }) }) describe('lockout reducer', () => { it('locks after max failures and unlocks after the window', () => { let s = FRESH_LOCKOUT for (let i = 0; i < DEFAULT_LOCKOUT.maxFailures; i++) { expect(isLocked(s, 1000)).toBe(false) s = recordFailure(s, 1000) } expect(isLocked(s, 1000)).toBe(true) expect(isLocked(s, 1000 + DEFAULT_LOCKOUT.lockMs)).toBe(false) expect(isLocked(recordSuccess(), 1000)).toBe(false) }) }) const user = (roles: User['roles'], active = true): Pick => ({ roles, active }) describe('permissions & gates', () => { it('grants by role', () => { expect(can(user(['cashier']), 'BILL_CREATE')).toBe(true) expect(can(user(['cashier']), 'PRICE_OVERRIDE')).toBe(false) expect(can(user(['cashier'], false), 'BILL_CREATE')).toBe(false) }) it('in-flow gate escalates to supervisor PIN instead of hiding (09-UX §5.5)', () => { expect(gateFor(user(['cashier']), 'PRICE_OVERRIDE')).toBe('supervisor-pin') expect(gateFor(user(['cashier']), 'DAY_BEGIN')).toBe('supervisor-pin') expect(gateFor(user(['supervisor']), 'PRICE_OVERRIDE')).toBe('allow') }) }) describe('POS session — Day Begin binding', () => { const day: BusinessDay = { tenantId: 't1', storeId: 's1', date: '2026-07-09', openedAt: '2026-07-09T09:01:00+05:30', openedBy: 'mgr', } const base = { tenantId: 't1', storeId: 's1', counterId: 'c1', userId: 'u1', shiftId: 'sh1', nowWallClock: '2026-07-09T09:05:00+05:30', } it('binds the session to the working date, not the wall clock', () => { expect(openSession({ ...base, day }).businessDate).toBe('2026-07-09') }) it('refuses a closed day or another store’s day', () => { expect(() => openSession({ ...base, day: { ...day, closedAt: 'x', closedBy: 'mgr' } })).toThrow(/Day Begin/) expect(() => openSession({ ...base, day: { ...day, storeId: 's2' } })).toThrow(/another store/) }) })