import * as bcrypt from 'bcryptjs'; import { AuthService } from './auth.service'; describe('AuthService — authentication boundaries', () => { it('marks the preset 超管 role as super admin in the JWT payload', async () => { const userRepo = { findOne: jest.fn().mockResolvedValue({ id: 1, username: 'admin', name: '管理员', passwordHash: await bcrypt.hash('secret', 4), isActive: true, roles: [{ name: '超管', status: 1 }], }), save: jest.fn(), }; const jwtService = { sign: jest.fn().mockReturnValue('token') }; const rbacService = { getUserPermissions: jest.fn().mockResolvedValue(['attendance:create']), }; const service = new AuthService(userRepo as never, jwtService as never, rbacService as never); await service.login({ username: 'admin', password: 'secret' }, '127.0.0.1'); expect(jwtService.sign).toHaveBeenCalledWith(expect.objectContaining({ isSuperAdmin: true })); }); it('rejects an archived user even when the password is valid', async () => { const userRepo = { findOne: jest.fn().mockResolvedValue({ id: 2, username: 'archived', passwordHash: await bcrypt.hash('secret', 4), isActive: true, isArchived: true, roles: [], }), save: jest.fn(), }; const service = new AuthService( userRepo as never, { sign: jest.fn() } as never, { getUserPermissions: jest.fn() } as never, ); await expect( service.login({ username: 'archived', password: 'secret' }, '192.0.2.10'), ).rejects.toThrow('账号已失效'); expect(userRepo.save).not.toHaveBeenCalled(); }); it('allows a legacy disabled user because archive is the only account status', async () => { const userRepo = { findOne: jest.fn().mockResolvedValue({ id: 3, username: 'legacy-disabled', name: '旧账号', passwordHash: await bcrypt.hash('secret', 4), isActive: false, isArchived: false, roles: [], }), save: jest.fn(), }; const service = new AuthService( userRepo as never, { sign: jest.fn().mockReturnValue('token') } as never, { getUserPermissions: jest.fn().mockResolvedValue([]) } as never, ); await expect( service.login({ username: 'legacy-disabled', password: 'secret' }, '192.0.2.11'), ).resolves.toEqual(expect.objectContaining({ access_token: 'token' })); }); });