feat(api): sync hooks for parsing, auto-attach and family recompute

This commit is contained in:
yeuimu
2026-08-28 12:33:33 +08:00
parent f68898dea4
commit d20a933e34
3 changed files with 240 additions and 2 deletions
+174
View File
@@ -0,0 +1,174 @@
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 { PrismaService } from '../prisma/prisma.service';
/**
* 同步钩子集成测试:upsertOriginGood 的解析列写入、新链接自动挂族、
* persistProductDetail 后的族重算入队。
*/
describe('SyncService family hooks', () => {
let service: SyncService;
let prisma: PrismaService;
let recompute: FamilyRecomputeService;
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: [
SyncService,
{
provide: SdsClientService,
useValue: {},
},
FamilyRecomputeService,
PrismaService,
],
}).compile();
service = moduleRef.get(SyncService);
prisma = moduleRef.get(PrismaService);
recompute = moduleRef.get(FamilyRecomputeService);
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:写入四个解析列(create 与 update 全量覆盖)', async () => {
const sdsId = `hook-${stamp}-parse`;
const result1 = await (service as any).upsertOriginGood(
sdsProduct(sdsId, '美国(包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓'),
'cat-1',
);
expect(result1).toBe('inserted');
const og = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } });
createdOriginGoodIds.push(og.id);
expect(og.skuCode).toBe('DG206');
expect(og.logisticsLabel).toBe('包邮');
expect(og.craftLabel).toBe('单面印花');
expect(og.warehouseLabel).toBe('美西洛杉矶一仓');
// 更新为不带仓库的名称 → 解析列全量覆盖(warehouseLabel 置空)
await (service as any).upsertOriginGood(
sdsProduct(sdsId, '美国(不包邮)240g涤纶休闲短裤-DG206-单面印花'),
'cat-1',
);
const og2 = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } });
expect(og2.logisticsLabel).toBe('不包邮');
expect(og2.warehouseLabel).toBeNull();
});
it('新链接自动挂族:唯一命中族则挂载并触发重算', async () => {
const seed = await prisma.originGood.create({
data: {
sdsGoodId: `hook-${stamp}-seed`,
goodName: `自动挂${stamp}(包邮)卫衣-ZZA${stamp}-单面印花`,
craftLabel: '单面印花',
logisticsLabel: '包邮',
},
});
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 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}-单面印花-某仓`),
'cat-1',
);
const newOg = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: newSdsId } });
createdOriginGoodIds.push(newOg.id);
expect(newOg.familyId).toBe(family.id);
// 入队的重算已执行(等待异步完成)
await new Promise((r) => setTimeout(r, 200));
});
it('多族命中时不确定归属 → 不挂载', async () => {
const key = `自动挂${stamp}B`;
const mk = async (suffix: string) => {
const og = await prisma.originGood.create({
data: {
sdsGoodId: `hook-${stamp}-multi-${suffix}`,
goodName: `${key}(包邮)卫衣-ZZB${stamp}-单面印花`,
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, `${key}(包邮)卫衣-ZZB${stamp}-单面印花-新仓`),
'cat-1',
);
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();
});
});