import { Test, TestingModule } from '@nestjs/testing'; import { HttpException, HttpStatus } from '@nestjs/common'; import { SandboxService } from '../sandbox.service'; import { PrismaService } from '../../../prisma/prisma.service'; import { AIGatewayService } from '../../ai/ai-gateway.service'; describe('SandboxService', () => { let service: SandboxService; let prisma: PrismaService; let aiGateway: AIGatewayService; const mockPrisma = { user: { findUnique: jest.fn(), }, sandboxSession: { findMany: jest.fn(), findUnique: jest.fn(), count: jest.fn(), create: jest.fn(), upsert: jest.fn(), }, }; const mockAIGateway = { chat: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SandboxService, { provide: PrismaService, useValue: mockPrisma }, { provide: AIGatewayService, useValue: mockAIGateway }, ], }).compile(); service = module.get(SandboxService); prisma = module.get(PrismaService); aiGateway = module.get(AIGatewayService); jest.clearAllMocks(); }); describe('chat', () => { it('should throw if user not found', async () => { mockPrisma.user.findUnique.mockResolvedValue(null); await expect( service.chat(1, 'test-conv', 'gpt-3.5', [{ role: 'user', content: 'Hello' }]) ).rejects.toThrow(HttpException); }); it('should throw if user not active', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, status: 'INACTIVE' }); await expect( service.chat(1, 'test-conv', 'gpt-3.5', [{ role: 'user', content: 'Hello' }]) ).rejects.toThrow(HttpException); }); it('should throw if daily quota exceeded for new conversation', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, status: 'ACTIVE', sandboxDaily: 10 }); mockPrisma.sandboxSession.findUnique.mockResolvedValue(null); mockPrisma.sandboxSession.count.mockResolvedValue(10); await expect( service.chat(1, 'new-conv', 'gpt-3.5', [{ role: 'user', content: 'Hello' }]) ).rejects.toThrow(HttpException); }); it('should not count quota for existing conversation', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, status: 'ACTIVE', sandboxDaily: 10 }); mockPrisma.sandboxSession.findUnique.mockResolvedValue({ id: 1 }); mockAIGateway.chat.mockResolvedValue('AI回复'); mockPrisma.sandboxSession.upsert.mockResolvedValue({ id: 2 }); const result = await service.chat(1, 'existing-conv', 'gpt-3.5', [ { role: 'user', content: 'Hello' } ]); expect(result.reply).toBe('AI回复'); expect(mockPrisma.sandboxSession.count).not.toHaveBeenCalled(); }); it('should call AI gateway and save session', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, status: 'ACTIVE', sandboxDaily: 10 }); mockPrisma.sandboxSession.findUnique.mockResolvedValue(null); mockPrisma.sandboxSession.count.mockResolvedValue(5); mockAIGateway.chat.mockResolvedValue('AI回复'); mockPrisma.sandboxSession.upsert.mockResolvedValue({ id: 1 }); const result = await service.chat(1, 'test-conv', 'gpt-3.5', [ { role: 'user', content: 'Hello' } ]); expect(result.reply).toBe('AI回复'); expect(result.conversationId).toBe('test-conv'); expect(result.sessionId).toBeDefined(); expect(mockAIGateway.chat).toHaveBeenCalledWith('gpt-3.5', expect.any(Array), undefined); expect(mockPrisma.sandboxSession.upsert).toHaveBeenCalled(); }); }); describe('getHistory', () => { it('should return paginated history', async () => { const mockSessions = [ { id: 1, conversationId: 'conv-1', model: 'gpt-3.5', title: 'Test', createdAt: new Date(), tokens: 10 } ]; mockPrisma.sandboxSession.findMany.mockResolvedValue(mockSessions); mockPrisma.sandboxSession.count.mockResolvedValue(1); const result = await service.getHistory(1, { page: 1, pageSize: 20 }); expect(result).toEqual({ items: mockSessions, total: 1, page: 1, pageSize: 20, }); }); }); describe('getQuota', () => { it('should return quota info', async () => { mockPrisma.user.findUnique.mockResolvedValue({ id: 1, sandboxDaily: 10 }); mockPrisma.sandboxSession.count.mockResolvedValue(3); const result = await service.getQuota(1); expect(result).toEqual({ dailyLimit: 10, used: 3, remaining: 7, }); }); }); });