import { Test, TestingModule } from '@nestjs/testing'; import { UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcryptjs'; import { AdminService } from '../admin.service'; import { PrismaService } from '../../../prisma/prisma.service'; // Mock bcrypt jest.mock('bcryptjs', () => ({ compare: jest.fn(), hash: jest.fn(), })); describe('AdminService', () => { let service: AdminService; let prisma: PrismaService; const mockPrisma = { adminUser: { findUnique: jest.fn(), update: jest.fn(), }, user: { count: jest.fn() }, course: { count: jest.fn() }, content: { count: jest.fn() }, prompt: { count: jest.fn() }, order: { count: jest.fn() }, adminLog: { create: jest.fn() }, }; const mockJwtService = { sign: jest.fn().mockReturnValue('mock-jwt-token'), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ AdminService, { provide: PrismaService, useValue: mockPrisma }, { provide: JwtService, useValue: mockJwtService }, ], }).compile(); service = module.get(AdminService); prisma = module.get(PrismaService); jest.clearAllMocks(); }); describe('login', () => { const mockAdmin = { id: 1, username: 'admin', passwordHash: '$2a$10$hashed', role: { name: 'superadmin' }, status: 'ACTIVE', }; it('should login successfully', async () => { mockPrisma.adminUser.findUnique.mockResolvedValue(mockAdmin); jest.spyOn(bcrypt, 'compare').mockResolvedValue(true as never); mockPrisma.adminUser.update.mockResolvedValue(mockAdmin); const result = await service.login('admin', 'admin123456'); expect(result).toEqual(expect.objectContaining({ id: 1, username: 'admin', role: 'superadmin' })); expect(mockPrisma.adminUser.update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: 1 }, data: { lastLoginAt: expect.any(Date) }, }) ); }); it('should throw on wrong password', async () => { mockPrisma.adminUser.findUnique.mockResolvedValue(mockAdmin); jest.spyOn(bcrypt, 'compare').mockResolvedValue(false as never); await expect(service.login('admin', 'wrongpass')).rejects.toThrow(UnauthorizedException); }); it('should throw when admin not found', async () => { mockPrisma.adminUser.findUnique.mockResolvedValue(null); await expect(service.login('unknown', 'pass')).rejects.toThrow(UnauthorizedException); }); it('should throw when admin is inactive', async () => { mockPrisma.adminUser.findUnique.mockResolvedValue({ ...mockAdmin, status: 'INACTIVE' }); await expect(service.login('admin', 'pass')).rejects.toThrow(UnauthorizedException); }); }); describe('getDashboard', () => { it('should return all stats', async () => { mockPrisma.user.count.mockResolvedValue(100); mockPrisma.course.count.mockResolvedValue(20); mockPrisma.content.count.mockResolvedValue(45); mockPrisma.prompt.count.mockResolvedValue(30); mockPrisma.order.count.mockResolvedValue(15); const result = await service.getDashboard(); expect(result).toEqual({ stats: { userCount: 100, courseCount: 20, contentCount: 45, promptCount: 30, orderCount: 15, }, }); }); it('should return zeroes when no data', async () => { mockPrisma.user.count.mockResolvedValue(0); mockPrisma.course.count.mockResolvedValue(0); mockPrisma.content.count.mockResolvedValue(0); mockPrisma.prompt.count.mockResolvedValue(0); mockPrisma.order.count.mockResolvedValue(0); const result = await service.getDashboard(); expect(result).toEqual({ stats: { userCount: 0, courseCount: 0, contentCount: 0, promptCount: 0, orderCount: 0, }, }); }); it('should filter deleted users and courses', async () => { mockPrisma.user.count.mockResolvedValue(50); mockPrisma.course.count.mockResolvedValue(10); mockPrisma.content.count.mockResolvedValue(5); mockPrisma.prompt.count.mockResolvedValue(3); mockPrisma.order.count.mockResolvedValue(2); await service.getDashboard(); expect(mockPrisma.user.count).toHaveBeenCalledWith({ where: { deletedAt: null } }); expect(mockPrisma.course.count).toHaveBeenCalledWith({ where: { deletedAt: null } }); expect(mockPrisma.content.count).toHaveBeenCalledWith({ where: { deletedAt: null, status: 'PUBLISHED' }, }); }); }); describe('logAction', () => { it('should create an admin log entry', async () => { const mockLog = { id: 1, adminId: 1, action: 'login', target: null, detail: null, ip: null }; mockPrisma.adminLog.create.mockResolvedValue(mockLog); const result = await service.logAction(1, 'login'); expect(result).toEqual(mockLog); expect(mockPrisma.adminLog.create).toHaveBeenCalledWith({ data: { adminId: 1, action: 'login', target: undefined, detail: undefined, ip: undefined }, }); }); it('should create log with full details', async () => { mockPrisma.adminLog.create.mockResolvedValue({ id: 2 }); await service.logAction(1, 'update', 'courses/1', '修改课程标题', '127.0.0.1'); expect(mockPrisma.adminLog.create).toHaveBeenCalledWith({ data: { adminId: 1, action: 'update', target: 'courses/1', detail: '修改课程标题', ip: '127.0.0.1' }, }); }); }); });