passwordless.test.ts49 lines · main
| 1 | import { describe, expect, test } from 'bun:test'; |
| 2 | |
| 3 | import { |
| 4 | hashSecret, |
| 5 | matchPasswordlessSecret, |
| 6 | sixDigitCode, |
| 7 | } from './passwordless.js'; |
| 8 | |
| 9 | describe('passwordless pure helpers (Phase 3)', () => { |
| 10 | test('sixDigitCode is 6 digits', () => { |
| 11 | for (let i = 0; i < 20; i++) { |
| 12 | const c = sixDigitCode(); |
| 13 | expect(c).toMatch(/^\d{6}$/); |
| 14 | expect(Number(c)).toBeGreaterThanOrEqual(100000); |
| 15 | expect(Number(c)).toBeLessThanOrEqual(999999); |
| 16 | } |
| 17 | }); |
| 18 | |
| 19 | test('match OTP-only hash', () => { |
| 20 | const otp = '123456'; |
| 21 | const stored = hashSecret(otp); |
| 22 | expect(matchPasswordlessSecret(stored, { userInputCode: otp })).toBe(true); |
| 23 | expect(matchPasswordlessSecret(stored, { userInputCode: '000000' })).toBe( |
| 24 | false, |
| 25 | ); |
| 26 | }); |
| 27 | |
| 28 | test('match link-only hash', () => { |
| 29 | const link = 'abcLinkCodeXYZ'; |
| 30 | const stored = hashSecret(link); |
| 31 | expect(matchPasswordlessSecret(stored, { linkCode: link })).toBe(true); |
| 32 | expect(matchPasswordlessSecret(stored, { linkCode: 'nope' })).toBe(false); |
| 33 | }); |
| 34 | |
| 35 | test('match dual otp:link form', () => { |
| 36 | const otp = '654321'; |
| 37 | const link = 'linkSecret99'; |
| 38 | const stored = `${hashSecret(otp)}:${hashSecret(link)}`; |
| 39 | expect(matchPasswordlessSecret(stored, { userInputCode: otp })).toBe(true); |
| 40 | expect(matchPasswordlessSecret(stored, { linkCode: link })).toBe(true); |
| 41 | expect( |
| 42 | matchPasswordlessSecret(stored, { userInputCode: '111111', linkCode: 'x' }), |
| 43 | ).toBe(false); |
| 44 | }); |
| 45 | |
| 46 | test('empty input fails', () => { |
| 47 | expect(matchPasswordlessSecret(hashSecret('1'), {})).toBe(false); |
| 48 | }); |
| 49 | }); |