import { INestApplication } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; import { configureApp } from '../src/app.setup'; import { AppModule } from '../src/app.module'; import { AiSimulationClient } from '../src/modules/simulations/ai-simulation.client'; import { PrismaService } from '../src/prisma/prisma.service'; describe('Simulations (e2e)', () => { let app: INestApplication; let jwtService: JwtService; let prisma: PrismaService; const execute = jest.fn(); const getResult = jest.fn(); async function signToken() { return jwtService.signAsync({ sub: 1, stylistId: '1', email: 'stylist@example.com', username: 'Ogawa', }); } beforeAll(async () => { execute.mockReset(); getResult.mockReset(); const moduleRef = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(AiSimulationClient) .useValue({ execute, getResult }) .compile(); app = moduleRef.createNestApplication(); configureApp(app); await app.init(); jwtService = app.get(JwtService); prisma = app.get(PrismaService); }); afterAll(async () => { await prisma.aiHairSimulationTask.deleteMany({ where: { taskId: { startsWith: 'sim-e2e-task-', }, }, }); await prisma.aiHairSimulation.deleteMany({ where: { simulateRemark: { startsWith: 'sim-e2e-', }, }, }); await prisma.hairTemplate.deleteMany({ where: { templateName: { startsWith: 'sim-e2e-', }, }, }); await prisma.customer.deleteMany({ where: { name: { startsWith: 'sg-', }, }, }); await app.close(); }); it('/api/v1/simulations (POST) creates an anonymous simulation', async () => { const token = await signToken(); const uniqueSuffix = Date.now(); const template = await prisma.hairTemplate.create({ data: { templateName: `sim-e2e-template-${uniqueSuffix}`, coverUrl: '', status: 'PUBLIC', category: 'SHORT', tags: '', description: null, modelUrl: '', clothesUrl: '', backgroundUrl: '', refHairUrl: '', hairstylistId: BigInt(1), templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`, isSystemTemplate: 'NO', } as never, }); execute.mockResolvedValueOnce({ taskId: `sim-e2e-task-${uniqueSuffix}`, }); const response = await request(app.getHttpServer()) .post('/api/v1/simulations') .set('Authorization', `Bearer ${token}`) .send({ originImage: 'https://cdn.example.com/origin.jpg', templateIds: [template.id.toString()], simulateRemark: `sim-e2e-${uniqueSuffix}`, }); expect(response.status).toBe(201); expect(execute).toHaveBeenCalledWith( template.templateUrl, 'https://cdn.example.com/origin.jpg', ); expect(response.body.data).toEqual({ simulationId: expect.any(String), }); }); it('/api/v1/simulations/:id (GET) returns task detail for the owner', async () => { const token = await signToken(); const uniqueSuffix = `${Date.now()}-detail`; const template = await prisma.hairTemplate.create({ data: { templateName: `sim-e2e-template-${uniqueSuffix}`, coverUrl: 'https://cdn.example.com/cover.png', status: 'PUBLIC', category: 'MEDIUM', tags: 'airy', description: 'detail template', modelUrl: '', clothesUrl: '', backgroundUrl: '', refHairUrl: '', hairstylistId: BigInt(1), templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`, isSystemTemplate: 'NO', } as never, }); execute.mockResolvedValueOnce({ taskId: `sim-e2e-task-${uniqueSuffix}`, }); const created = await request(app.getHttpServer()) .post('/api/v1/simulations') .set('Authorization', `Bearer ${token}`) .send({ originImage: 'https://cdn.example.com/origin-detail.jpg', templateIds: [template.id.toString()], simulateRemark: `sim-e2e-${uniqueSuffix}`, }) .expect(201); const response = await request(app.getHttpServer()) .get(`/api/v1/simulations/${created.body.data.simulationId}`) .set('Authorization', `Bearer ${token}`) .expect(200); expect(response.body.data).toMatchObject({ id: created.body.data.simulationId, createdAt: expect.any(String), originImage: 'https://cdn.example.com/origin-detail.jpg', totalTaskCount: 1, processingTaskCount: 1, successTaskCount: 0, failedTaskCount: 0, overallStatus: 'PROCESSING', tasks: [ expect.objectContaining({ templateId: template.id.toString(), templateName: template.templateName, templateCategory: template.category, templateSource: 'STYLIST', taskStatus: 'PROCESSING', resultImages: [], }), ], }); }); it('/api/v1/simulations/customer/:customerId/gallery (GET) returns flattened result cards', async () => { const token = await signToken(); const uniqueSuffix = `${Date.now()}-gallery`; const customer = await prisma.customer.create({ data: { hairstylistId: BigInt(1), name: `sg-${Date.now()}`, namePronunciation: '', phone: `090-${String(Date.now()).slice(-4)}-3333`, email: '', avatar: 'https://cdn.example.com/customer.png', expectContact: 'LINE', planDate: new Date(), nextFollowDate: new Date(), } as never, }); const template = await prisma.hairTemplate.create({ data: { templateName: `sim-e2e-template-${uniqueSuffix}`, coverUrl: 'https://cdn.example.com/cover.png', status: 'PUBLIC', category: 'SHORT', tags: 'airy', description: 'gallery template', modelUrl: '', clothesUrl: '', backgroundUrl: '', refHairUrl: '', hairstylistId: BigInt(1), templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`, isSystemTemplate: 'NO', } as never, }); execute.mockResolvedValueOnce({ taskId: `sim-e2e-task-${uniqueSuffix}`, }); const created = await request(app.getHttpServer()) .post('/api/v1/simulations') .set('Authorization', `Bearer ${token}`) .send({ customerId: customer.id.toString(), originImage: 'https://cdn.example.com/origin-gallery.jpg', templateIds: [template.id.toString()], simulateRemark: `sim-e2e-${uniqueSuffix}`, }) .expect(201); await prisma.aiHairSimulationTask.updateMany({ where: { simulationId: BigInt(created.body.data.simulationId), }, data: { taskStatus: 'SUCCESSES', resultImages: JSON.stringify([ 'https://cdn.example.com/gallery-2.png', 'https://cdn.example.com/gallery-1.png', ]), } as never, }); const response = await request(app.getHttpServer()) .get(`/api/v1/simulations/customer/${customer.id}/gallery`) .set('Authorization', `Bearer ${token}`); expect(response.status).toBe(200); expect(response.body.data.items).toEqual( expect.arrayContaining([ expect.objectContaining({ simulationId: created.body.data.simulationId, imageUrl: 'https://cdn.example.com/gallery-2.png', templateName: template.templateName, taskStatus: 'SUCCESSES', }), expect.objectContaining({ simulationId: created.body.data.simulationId, imageUrl: 'https://cdn.example.com/gallery-1.png', templateName: template.templateName, taskStatus: 'SUCCESSES', }), ]), ); }); });