Files

124 lines
4.0 KiB
JavaScript
Raw Permalink Normal View History

2026-06-03 14:15:55 +08:00
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)
})
})
})