将 mockDb 的类型从 Record<string, Mock> 改为显式的映射类型, 消除 CI 中 "possibly undefined" 的类型检查报错。
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import type { Mock } from 'vitest';
|
|
|
|
// Mock bcryptjs
|
|
vi.mock('bcryptjs', () => ({
|
|
compare: vi.fn(),
|
|
hash: vi.fn(),
|
|
}));
|
|
|
|
// Build the mock DB once, then restore individual mocks in beforeEach
|
|
function buildMockDb() {
|
|
const db: { [K in 'select'|'from'|'where'|'limit'|'update'|'set']: Mock } = {} as typeof db;
|
|
db.select = vi.fn().mockReturnValue(db);
|
|
db.from = vi.fn().mockReturnValue(db);
|
|
db.where = vi.fn().mockReturnValue(db);
|
|
db.limit = vi.fn().mockResolvedValue([]);
|
|
db.update = vi.fn().mockReturnValue(db);
|
|
db.set = vi.fn().mockReturnValue(db);
|
|
return db;
|
|
}
|
|
|
|
const mockDb = buildMockDb();
|
|
|
|
vi.mock('../../../db/client.js', () => ({ db: mockDb }));
|
|
|
|
const { changePassword } = await import('../../../services/admin/admin-auth.js');
|
|
const bcrypt = await import('bcryptjs');
|
|
|
|
const mockAdmin = {
|
|
id: 'admin-123',
|
|
username: 'testadmin',
|
|
passwordHash: '$2a$10$hashedoldpassword',
|
|
role: 'admin',
|
|
isActive: 1,
|
|
};
|
|
|
|
describe('changePassword', () => {
|
|
beforeEach(() => {
|
|
// Reset call counts but keep implementations
|
|
mockDb.select.mockClear().mockReturnValue(mockDb);
|
|
mockDb.from.mockClear().mockReturnValue(mockDb);
|
|
mockDb.where.mockClear().mockReturnValue(mockDb);
|
|
mockDb.update.mockClear().mockReturnValue(mockDb);
|
|
mockDb.set.mockClear().mockReturnValue(mockDb);
|
|
|
|
// Per-test defaults
|
|
mockDb.limit.mockResolvedValue([mockAdmin]);
|
|
(bcrypt.compare as Mock).mockResolvedValue(true);
|
|
(bcrypt.hash as Mock).mockResolvedValue('$2a$10$hashednewpassword');
|
|
});
|
|
|
|
it('changes password when current password is correct', async () => {
|
|
await changePassword('admin-123', 'oldPassword1!', 'newPassword1!');
|
|
|
|
expect(bcrypt.compare).toHaveBeenCalledWith('oldPassword1!', mockAdmin.passwordHash);
|
|
expect(bcrypt.hash).toHaveBeenCalledWith('newPassword1!', 10);
|
|
expect(mockDb.update).toHaveBeenCalled();
|
|
expect(mockDb.set).toHaveBeenCalledWith(
|
|
expect.objectContaining({ passwordHash: '$2a$10$hashednewpassword' }),
|
|
);
|
|
});
|
|
|
|
it('throws UnauthorizedError when admin not found', async () => {
|
|
mockDb.limit.mockResolvedValue([]);
|
|
|
|
await expect(changePassword('admin-123', 'old', 'new'))
|
|
.rejects.toThrow('Admin user not found');
|
|
});
|
|
|
|
it('throws ForbiddenError when admin is disabled', async () => {
|
|
mockDb.limit.mockResolvedValue([{ ...mockAdmin, isActive: 0 }]);
|
|
|
|
await expect(changePassword('admin-123', 'old', 'new'))
|
|
.rejects.toThrow('Admin account is disabled');
|
|
});
|
|
|
|
it('throws UnauthorizedError when current password is wrong', async () => {
|
|
(bcrypt.compare as Mock).mockResolvedValue(false);
|
|
|
|
await expect(changePassword('admin-123', 'wrong', 'new'))
|
|
.rejects.toThrow('Current password is incorrect');
|
|
|
|
expect(mockDb.update).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('throws ValidationError when new password equals current password', async () => {
|
|
await expect(changePassword('admin-123', 'samePass1!', 'samePass1!'))
|
|
.rejects.toThrow('New password must be different from current password');
|
|
|
|
expect(mockDb.update).not.toHaveBeenCalled();
|
|
});
|
|
});
|