import { PublicCacheService } from '../public/public-cache.service'; import { Test } from '@nestjs/testing'; import { Prisma } from '@prisma/client'; import { SyncService } from './sync.service'; import { SdsClientService } from './sds-client.service'; import { FamilyRecomputeService } from '../product-families/family-recompute.service'; import { ProductFamiliesService } from '../product-families/product-families.service'; import { OrganizeService } from '../product-families/organize.service'; import { PrismaService } from '../prisma/prisma.service'; /** * 同步钩子集成测试(解析去运行时化后): * upsertOriginGood 纯镜像(不写解析列)、新链接不自动挂族(归族走整理)、 * persistProductDetail 后的族重算入队。 */ describe('SyncService family hooks', () => { let service: SyncService; let prisma: PrismaService; let recompute: FamilyRecomputeService; let organize: OrganizeService; const stamp = Date.now(); const createdOriginGoodIds: bigint[] = []; const createdFamilyIds: bigint[] = []; const sdsProduct = (id: string, name: string) => ({ id, name, pic: 'https://example.com/pic.jpg' }) as any; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ providers: [ PublicCacheService, SyncService, { provide: SdsClientService, useValue: {}, }, FamilyRecomputeService, ProductFamiliesService, OrganizeService, PrismaService, ], }).compile(); service = moduleRef.get(SyncService); prisma = moduleRef.get(PrismaService); recompute = moduleRef.get(FamilyRecomputeService); organize = moduleRef.get(OrganizeService); await prisma.onModuleInit(); }); afterAll(async () => { await prisma.originGood.deleteMany({ where: { id: { in: createdOriginGoodIds } } }); await prisma.productFamily.deleteMany({ where: { id: { in: createdFamilyIds } } }); await prisma.$disconnect(); }); it('upsertOriginGood:纯镜像——只存原文,不写解析列', async () => { const sdsId = `hook-${stamp}-parse`; const result1 = await (service as any).upsertOriginGood( sdsProduct(sdsId, '美国(包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓'), `cat-${stamp}-parse`, ); expect(result1).toBe('inserted'); const og = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } }); createdOriginGoodIds.push(og.id); expect(og.goodName).toBe('美国(包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓'); expect(og.skuCode).toBeNull(); expect(og.logisticsLabel).toBeNull(); expect(og.craftLabel).toBeNull(); expect(og.warehouseLabel).toBeNull(); // 更新名称 → 镜像原文变化;解析列保持为空(由整理回填) await (service as any).upsertOriginGood( sdsProduct(sdsId, '美国(不包邮)240g涤纶休闲短裤-DG206-单面印花'), `cat-${stamp}-parse`, ); const og2 = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } }); expect(og2.goodName).toBe('美国(不包邮)240g涤纶休闲短裤-DG206-单面印花'); expect(og2.skuCode).toBeNull(); expect(og2.logisticsLabel).toBeNull(); }); it('新链接不自动挂族(归族由整理显式完成);族矩阵保持不变', async () => { const sdsCat = `cat-hook-${stamp}`; const cat = await prisma.category.create({ data: { categoryName: `挂族测试分类-${stamp}`, sdsCategoryId: sdsCat }, }); const seed = await prisma.originGood.create({ data: { sdsGoodId: `hook-${stamp}-seed`, goodName: `自动挂${stamp}(包邮)卫衣-ZZA${stamp}-单面印花`, sdsCategoryId: sdsCat, }, }); createdOriginGoodIds.push(seed.id); await prisma.originGoodVariant.create({ data: { originGoodId: seed.id, sdsVariantId: 'seed-v1', sku: 'SEED-S', sizeId: 'size_S', sizeName: 'S', colorId: 'color_blk', colorName: '黑色', price: new Prisma.Decimal(25), }, }); const family = await prisma.productFamily.create({ data: { familyName: `自动挂族-${stamp}`, primaryOriginGoodId: seed.id, }, }); createdFamilyIds.push(family.id); await prisma.originGood.update({ where: { id: seed.id }, data: { familyId: family.id }, }); await organize.deriveTagsForOg(seed.id); await recompute.recomputeFamily(family.id); const before = await prisma.productFamily.findUniqueOrThrow({ where: { id: family.id } }); expect((before.priceMatrix as any).rows).toHaveLength(1); // 同分类新链接(不同工艺)→ 同步后保持无族,等整理/管理员归族 const newSdsId = `hook-${stamp}-new`; await (service as any).upsertOriginGood( sdsProduct(newSdsId, `自动挂${stamp}(不包邮)卫衣-ZZA${stamp}-双面印花-某仓`), sdsCat, ); const newOg = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: newSdsId } }); createdOriginGoodIds.push(newOg.id); expect(newOg.familyId).toBeNull(); // 族矩阵不因新链接同步而变化 await new Promise((r) => setTimeout(r, 200)); const after = await prisma.productFamily.findUniqueOrThrow({ where: { id: family.id } }); expect((after.priceMatrix as any).rows).toHaveLength(1); await prisma.category.delete({ where: { id: cat.id } }).catch(() => undefined); }); it('多族命中时不确定归属 → 不挂载', async () => { // 两个族各含一个同分类成员 → 新链接分类命中两个族,归属不明,留给管理员 const sdsCat = `cat-multi-${stamp}`; const mk = async (suffix: string) => { const og = await prisma.originGood.create({ data: { sdsGoodId: `hook-${stamp}-multi-${suffix}`, goodName: `${suffix}(包邮)卫衣-ZZB${stamp}-单面印花`, sdsCategoryId: sdsCat, craftLabel: '单面印花', logisticsLabel: '包邮', }, }); createdOriginGoodIds.push(og.id); const family = await prisma.productFamily.create({ data: { familyName: `多族${suffix}-${stamp}`, primaryOriginGoodId: og.id }, }); createdFamilyIds.push(family.id); await prisma.originGood.update({ where: { id: og.id }, data: { familyId: family.id } }); return og; }; await mk('x'); await mk('y'); const sdsId = `hook-${stamp}-multi-new`; await (service as any).upsertOriginGood( sdsProduct(sdsId, `新(包邮)卫衣-ZZB${stamp}-单面印花-新仓`), sdsCat, ); const og = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } }); createdOriginGoodIds.push(og.id); expect(og.familyId).toBeNull(); // 两个候选族 → 留给管理员 }); it('maybeEnqueueFamilyRecompute:有族入队、无族跳过', async () => { const enqueueSpy = jest.spyOn(recompute, 'enqueue').mockImplementation(() => undefined); const og = await prisma.originGood.create({ data: { sdsGoodId: `hook-${stamp}-noattach`, goodName: `不成组名称-${stamp}` }, }); createdOriginGoodIds.push(og.id); await (service as any).maybeEnqueueFamilyRecompute(og.id); expect(enqueueSpy).not.toHaveBeenCalled(); const family = await prisma.productFamily.create({ data: { familyName: `入队族-${stamp}` }, }); createdFamilyIds.push(family.id); await prisma.originGood.update({ where: { id: og.id }, data: { familyId: family.id } }); await (service as any).maybeEnqueueFamilyRecompute(og.id); expect(enqueueSpy).toHaveBeenCalledWith(family.id); enqueueSpy.mockRestore(); }); });