import { Test } from '@nestjs/testing'; import { BadRequestException, NotFoundException, } from '@nestjs/common'; import { GoodsService } from './goods.service'; import { PrismaService } from '../prisma/prisma.service'; import { SyncService } from '../sync/sync.service'; describe('GoodsService', () => { let service: GoodsService; let prisma: PrismaService; const stamp = Date.now(); // Fixtures let countryId: bigint; let country2Id: bigint; let categoryId: bigint; let childCategoryId: bigint; let tagId: bigint; let positionId: bigint; let originGoodIds: bigint[] = []; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [ GoodsService, PrismaService, { provide: SyncService, useValue: { queueProductDetailSync: jest.fn() }, }, ], }).compile(); service = moduleRef.get(GoodsService); prisma = moduleRef.get(PrismaService); await prisma.onModuleInit(); const country = await prisma.country.create({ data: { countryName: `Goods Country ${stamp}` }, }); countryId = country.id; const country2 = await prisma.country.create({ data: { countryName: `Goods Country 2 ${stamp}` }, }); country2Id = country2.id; const cat = await prisma.category.create({ data: { categoryName: `Goods Cat ${stamp}` }, }); categoryId = cat.id; const child = await prisma.category.create({ data: { categoryName: `Goods Child ${stamp}`, parentCategoryId: cat.id }, }); childCategoryId = child.id; const tag = await prisma.tag.create({ data: { tagName: `Goods Tag ${stamp}`, tagColor: '#00FF00' }, }); tagId = tag.id; const pos = await prisma.position.create({ data: { indexVal: 1, countryId, categoryId }, }); positionId = pos.id; const originGoods = await Promise.all( Array.from({ length: 5 }).map((_, i) => prisma.originGood.create({ data: { sdsGoodId: `sds-goods-${stamp}-${i}`, goodName: `Goods Origin ${stamp} ${i}`, }, }), ), ); originGoodIds = originGoods.map((og) => og.id); }); afterAll(async () => { // Wipe all goods first so origin_goods/category can be removed. await prisma.good.deleteMany({ where: { goodName: { contains: `Goods Test ${stamp}` } }, }); await prisma.good.deleteMany({ where: { goodName: { contains: `Origin ${stamp}` } }, }); await prisma.originGood.deleteMany({ where: { id: { in: originGoodIds } }, }); await prisma.position.delete({ where: { id: positionId } }); await prisma.tag.delete({ where: { id: tagId } }); await prisma.category.delete({ where: { id: childCategoryId } }); await prisma.category.delete({ where: { id: categoryId } }); await prisma.country.delete({ where: { id: countryId } }); await prisma.country.delete({ where: { id: country2Id } }); await prisma.onModuleDestroy(); }); it('should be defined', () => { expect(service).toBeDefined(); }); it('creates and reads back a good', async () => { const created = await service.create({ goodName: `Goods Test ${stamp} basic`, originGoodId: Number(originGoodIds[0]), countryId: Number(countryId), categoryId: Number(categoryId), tagIds: [Number(tagId)], positionId: Number(positionId), goodPriority: 3, }); expect(created.id).toBeTruthy(); expect(created.country?.countryName).toBeTruthy(); expect(created.tags.some((t) => t.tagColor === '#00FF00')).toBe(true); const fetched = await service.findOne(BigInt(created.id)); expect(fetched.goodName).toBe(`Goods Test ${stamp} basic`); }); it('filters by countryId, tagId, positionId and keyword', async () => { const result = await service.findAll({ page: 1, pageSize: 20, countryId: Number(countryId), tagId: Number(tagId), keyword: `Goods Test ${stamp}`, }); expect(result.items.length).toBeGreaterThan(0); expect(result.items.every((g) => g.countryId === countryId.toString())).toBe(true); expect( result.items.every((g) => g.tags.some((t) => t.id === tagId.toString())), ).toBe(true); }); it('categoryId filter includes descendants recursively', async () => { const inChild = await service.create({ goodName: `Goods Test ${stamp} child`, originGoodId: Number(originGoodIds[1]), countryId: Number(countryId), categoryId: Number(childCategoryId), }); const result = await service.findAll({ page: 1, pageSize: 20, categoryId: Number(categoryId), keyword: `Goods Test ${stamp}`, }); const ids = result.items.map((g) => g.id); expect(ids).toContain(inChild.id); }); it('batch update priority is atomic', async () => { const created = await service.create({ goodName: `Goods Test ${stamp} prio`, originGoodId: Number(originGoodIds[2]), countryId: Number(countryId), categoryId: Number(categoryId), }); const result = await service.batchUpdatePriority({ items: [{ id: Number(created.id), priority: 42 }], }); expect(result.count).toBe(1); const after = await service.findOne(BigInt(created.id)); expect(after.goodPriority).toBe(42); }); it('batch create creates all rows or none', async () => { // Count of goods whose originGoodId is one of the two fixture ids, // so we are independent of goodName (which batch derives from origin). const before = await service.findAll({ page: 1, pageSize: 100, keyword: `Goods Origin ${stamp}`, }); const created = await service.batchCreate({ countryId: Number(countryId), categoryId: Number(categoryId), defaultPriority: 1, items: [ { originGoodId: Number(originGoodIds[3]) }, { originGoodId: Number(originGoodIds[4]) }, ], }); expect(created.length).toBe(2); const after = await service.findAll({ page: 1, pageSize: 100, keyword: `Goods Origin ${stamp}`, }); expect(after.total).toBe(before.total + 2); }); it('batch create rolls back on failure', async () => { const before = await service.findAll({ page: 1, pageSize: 100, keyword: `Goods Origin ${stamp}`, }); await expect( service.batchCreate({ countryId: Number(countryId), categoryId: Number(categoryId), items: [ { originGoodId: Number(originGoodIds[0]) }, { originGoodId: 99999999 }, // missing -> failure ], }), ).rejects.toBeInstanceOf(BadRequestException); const after = await service.findAll({ page: 1, pageSize: 100, keyword: `Goods Origin ${stamp}`, }); expect(after.total).toBe(before.total); }); it('throws NotFoundException for unknown id', async () => { await expect(service.findOne(BigInt(99999999))).rejects.toBeInstanceOf( NotFoundException, ); }); });