simulations.e2e-spec.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import { INestApplication } from '@nestjs/common';
  2. import { JwtService } from '@nestjs/jwt';
  3. import { Test } from '@nestjs/testing';
  4. import * as request from 'supertest';
  5. import { configureApp } from '../src/app.setup';
  6. import { AppModule } from '../src/app.module';
  7. import { AiSimulationClient } from '../src/modules/simulations/ai-simulation.client';
  8. import { PrismaService } from '../src/prisma/prisma.service';
  9. describe('Simulations (e2e)', () => {
  10. let app: INestApplication;
  11. let jwtService: JwtService;
  12. let prisma: PrismaService;
  13. const execute = jest.fn();
  14. const getResult = jest.fn();
  15. async function signToken() {
  16. return jwtService.signAsync({
  17. sub: 1,
  18. stylistId: '1',
  19. email: 'stylist@example.com',
  20. username: 'Ogawa',
  21. });
  22. }
  23. beforeAll(async () => {
  24. execute.mockReset();
  25. getResult.mockReset();
  26. const moduleRef = await Test.createTestingModule({
  27. imports: [AppModule],
  28. })
  29. .overrideProvider(AiSimulationClient)
  30. .useValue({ execute, getResult })
  31. .compile();
  32. app = moduleRef.createNestApplication();
  33. configureApp(app);
  34. await app.init();
  35. jwtService = app.get(JwtService);
  36. prisma = app.get(PrismaService);
  37. });
  38. afterAll(async () => {
  39. await prisma.aiHairSimulationTask.deleteMany({
  40. where: {
  41. taskId: {
  42. startsWith: 'sim-e2e-task-',
  43. },
  44. },
  45. });
  46. await prisma.aiHairSimulation.deleteMany({
  47. where: {
  48. simulateRemark: {
  49. startsWith: 'sim-e2e-',
  50. },
  51. },
  52. });
  53. await prisma.hairTemplate.deleteMany({
  54. where: {
  55. templateName: {
  56. startsWith: 'sim-e2e-',
  57. },
  58. },
  59. });
  60. await prisma.customer.deleteMany({
  61. where: {
  62. name: {
  63. startsWith: 'sg-',
  64. },
  65. },
  66. });
  67. await app.close();
  68. });
  69. it('/api/v1/simulations (POST) creates an anonymous simulation', async () => {
  70. const token = await signToken();
  71. const uniqueSuffix = Date.now();
  72. const template = await prisma.hairTemplate.create({
  73. data: {
  74. templateName: `sim-e2e-template-${uniqueSuffix}`,
  75. coverUrl: '',
  76. status: 'PUBLIC',
  77. category: 'SHORT',
  78. tags: '',
  79. description: null,
  80. modelUrl: '',
  81. clothesUrl: '',
  82. backgroundUrl: '',
  83. refHairUrl: '',
  84. hairstylistId: BigInt(1),
  85. templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`,
  86. isSystemTemplate: 'NO',
  87. } as never,
  88. });
  89. execute.mockResolvedValueOnce({
  90. taskId: `sim-e2e-task-${uniqueSuffix}`,
  91. });
  92. const response = await request(app.getHttpServer())
  93. .post('/api/v1/simulations')
  94. .set('Authorization', `Bearer ${token}`)
  95. .send({
  96. originImage: 'https://cdn.example.com/origin.jpg',
  97. templateIds: [template.id.toString()],
  98. simulateRemark: `sim-e2e-${uniqueSuffix}`,
  99. });
  100. expect(response.status).toBe(201);
  101. expect(execute).toHaveBeenCalledWith(
  102. template.templateUrl,
  103. 'https://cdn.example.com/origin.jpg',
  104. );
  105. expect(response.body.data).toEqual({
  106. simulationId: expect.any(String),
  107. });
  108. });
  109. it('/api/v1/simulations/:id (GET) returns task detail for the owner', async () => {
  110. const token = await signToken();
  111. const uniqueSuffix = `${Date.now()}-detail`;
  112. const template = await prisma.hairTemplate.create({
  113. data: {
  114. templateName: `sim-e2e-template-${uniqueSuffix}`,
  115. coverUrl: 'https://cdn.example.com/cover.png',
  116. status: 'PUBLIC',
  117. category: 'MEDIUM',
  118. tags: 'airy',
  119. description: 'detail template',
  120. modelUrl: '',
  121. clothesUrl: '',
  122. backgroundUrl: '',
  123. refHairUrl: '',
  124. hairstylistId: BigInt(1),
  125. templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`,
  126. isSystemTemplate: 'NO',
  127. } as never,
  128. });
  129. execute.mockResolvedValueOnce({
  130. taskId: `sim-e2e-task-${uniqueSuffix}`,
  131. });
  132. const created = await request(app.getHttpServer())
  133. .post('/api/v1/simulations')
  134. .set('Authorization', `Bearer ${token}`)
  135. .send({
  136. originImage: 'https://cdn.example.com/origin-detail.jpg',
  137. templateIds: [template.id.toString()],
  138. simulateRemark: `sim-e2e-${uniqueSuffix}`,
  139. })
  140. .expect(201);
  141. const response = await request(app.getHttpServer())
  142. .get(`/api/v1/simulations/${created.body.data.simulationId}`)
  143. .set('Authorization', `Bearer ${token}`)
  144. .expect(200);
  145. expect(response.body.data).toMatchObject({
  146. id: created.body.data.simulationId,
  147. createdAt: expect.any(String),
  148. originImage: 'https://cdn.example.com/origin-detail.jpg',
  149. totalTaskCount: 1,
  150. processingTaskCount: 1,
  151. successTaskCount: 0,
  152. failedTaskCount: 0,
  153. overallStatus: 'PROCESSING',
  154. tasks: [
  155. expect.objectContaining({
  156. templateId: template.id.toString(),
  157. templateName: template.templateName,
  158. templateCategory: template.category,
  159. templateSource: 'STYLIST',
  160. taskStatus: 'PROCESSING',
  161. resultImages: [],
  162. }),
  163. ],
  164. });
  165. });
  166. it('/api/v1/simulations/customer/:customerId/gallery (GET) returns flattened result cards', async () => {
  167. const token = await signToken();
  168. const uniqueSuffix = `${Date.now()}-gallery`;
  169. const customer = await prisma.customer.create({
  170. data: {
  171. hairstylistId: BigInt(1),
  172. name: `sg-${Date.now()}`,
  173. namePronunciation: '',
  174. phone: `090-${String(Date.now()).slice(-4)}-3333`,
  175. email: '',
  176. avatar: 'https://cdn.example.com/customer.png',
  177. expectContact: 'LINE',
  178. planDate: new Date(),
  179. nextFollowDate: new Date(),
  180. } as never,
  181. });
  182. const template = await prisma.hairTemplate.create({
  183. data: {
  184. templateName: `sim-e2e-template-${uniqueSuffix}`,
  185. coverUrl: 'https://cdn.example.com/cover.png',
  186. status: 'PUBLIC',
  187. category: 'SHORT',
  188. tags: 'airy',
  189. description: 'gallery template',
  190. modelUrl: '',
  191. clothesUrl: '',
  192. backgroundUrl: '',
  193. refHairUrl: '',
  194. hairstylistId: BigInt(1),
  195. templateUrl: `https://cdn.example.com/${uniqueSuffix}.zip`,
  196. isSystemTemplate: 'NO',
  197. } as never,
  198. });
  199. execute.mockResolvedValueOnce({
  200. taskId: `sim-e2e-task-${uniqueSuffix}`,
  201. });
  202. const created = await request(app.getHttpServer())
  203. .post('/api/v1/simulations')
  204. .set('Authorization', `Bearer ${token}`)
  205. .send({
  206. customerId: customer.id.toString(),
  207. originImage: 'https://cdn.example.com/origin-gallery.jpg',
  208. templateIds: [template.id.toString()],
  209. simulateRemark: `sim-e2e-${uniqueSuffix}`,
  210. })
  211. .expect(201);
  212. await prisma.aiHairSimulationTask.updateMany({
  213. where: {
  214. simulationId: BigInt(created.body.data.simulationId),
  215. },
  216. data: {
  217. taskStatus: 'SUCCESSES',
  218. resultImages: JSON.stringify([
  219. 'https://cdn.example.com/gallery-2.png',
  220. 'https://cdn.example.com/gallery-1.png',
  221. ]),
  222. } as never,
  223. });
  224. const response = await request(app.getHttpServer())
  225. .get(`/api/v1/simulations/customer/${customer.id}/gallery`)
  226. .set('Authorization', `Bearer ${token}`);
  227. expect(response.status).toBe(200);
  228. expect(response.body.data.items).toEqual(
  229. expect.arrayContaining([
  230. expect.objectContaining({
  231. simulationId: created.body.data.simulationId,
  232. imageUrl: 'https://cdn.example.com/gallery-2.png',
  233. templateName: template.templateName,
  234. taskStatus: 'SUCCESSES',
  235. }),
  236. expect.objectContaining({
  237. simulationId: created.body.data.simulationId,
  238. imageUrl: 'https://cdn.example.com/gallery-1.png',
  239. templateName: template.templateName,
  240. taskStatus: 'SUCCESSES',
  241. }),
  242. ]),
  243. );
  244. });
  245. });