const { hashPassword, verifyPassword, isLegacyHash, needsRehash, md5 } = require('../utils/password') describe('密码工具函数', () => { describe('hashPassword', () => { it('应返回 scrypt 格式的哈希字符串', () => { const hashed = hashPassword('mypassword') expect(hashed).toMatch(/^scrypt\$/) const parts = hashed.split('$') expect(parts).toHaveLength(6) expect(parts[0]).toBe('scrypt') }) it('应包含正确的 scrypt 参数', () => { const hashed = hashPassword('test') const parts = hashed.split('$') expect(parts[1]).toBe('16384') // N expect(parts[2]).toBe('8') // r expect(parts[3]).toBe('1') // p }) it('每次哈希应生成不同的 salt', () => { const h1 = hashPassword('samepassword') const h2 = hashPassword('samepassword') expect(h1).not.toBe(h2) }) it('应能验证自己哈希的密码', () => { const hashed = hashPassword('verifypassword') expect(verifyPassword('verifypassword', hashed)).toBe(true) }) }) describe('verifyPassword', () => { it('应验证正确的 scrypt 密码', () => { const hashed = hashPassword('correctpass') expect(verifyPassword('correctpass', hashed)).toBe(true) }) it('应拒绝错误的 scrypt 密码', () => { const hashed = hashPassword('correctpass') expect(verifyPassword('wrongpass', hashed)).toBe(false) }) it('应验证正确的 MD5 密码', () => { const md5Hash = md5('md5password') expect(verifyPassword('md5password', md5Hash)).toBe(true) }) it('应拒绝错误的 MD5 密码', () => { const md5Hash = md5('md5password') expect(verifyPassword('wrongmd5', md5Hash)).toBe(false) }) it('应在 stored 为空时返回 false', () => { expect(verifyPassword('pass', null)).toBe(false) expect(verifyPassword('pass', undefined)).toBe(false) expect(verifyPassword('pass', '')).toBe(false) }) it('应在 stored 格式不识别时返回 false', () => { expect(verifyPassword('pass', 'unknown$format$hash')).toBe(false) expect(verifyPassword('pass', 'plaintext')).toBe(false) }) it('MD5 哈希应不区分大小写比较', () => { const upperHash = md5('test').toUpperCase() const lowerHash = md5('test').toLowerCase() expect(verifyPassword('test', upperHash)).toBe(true) expect(verifyPassword('test', lowerHash)).toBe(true) }) }) describe('isLegacyHash', () => { it('应识别 MD5 格式的哈希', () => { const hash = md5('test') expect(isLegacyHash(hash)).toBe(true) }) it('应识别 32 位十六进制字符串', () => { expect(isLegacyHash('d41d8cd98f00b204e9800998ecf8427e')).toBe(true) expect(isLegacyHash('D41D8CD98F00B204E9800998ECF8427E')).toBe(true) }) it('应识别 scrypt 哈希为非旧版哈希', () => { const hashed = hashPassword('test') expect(isLegacyHash(hashed)).toBe(false) }) it('应在输入为空时返回 falsy', () => { expect(isLegacyHash(null)).toBeFalsy() expect(isLegacyHash(undefined)).toBeFalsy() expect(isLegacyHash('')).toBeFalsy() }) it('应识别非 32 位十六进制字符串为非旧版哈希', () => { expect(isLegacyHash('abc123')).toBe(false) expect(isLegacyHash('d41d8cd98f00b204e9800998ecf8427eextra')).toBe(false) }) }) describe('needsRehash', () => { it('应识别 scrypt 哈希不需要 rehash', () => { const hashed = hashPassword('test') expect(needsRehash(hashed)).toBe(false) }) it('应识别 MD5 哈希需要 rehash', () => { const md5Hash = md5('test') expect(needsRehash(md5Hash)).toBe(true) }) it('应识别空值需要 rehash', () => { expect(needsRehash(null)).toBe(true) expect(needsRehash(undefined)).toBe(true) expect(needsRehash('')).toBe(true) }) it('应识别其他格式需要 rehash', () => { expect(needsRehash('plaintext')).toBe(true) expect(needsRehash('bcrypt$hash')).toBe(true) }) }) })