- PublicCacheService:分域版本号失效(bump 即作废,不等 TTL)、loader 期间 bump 的竞态防护(返回但不回写)、并发 miss 单飞、PUBLIC_CACHE_DISABLED /TTL_MS/MAX_ENTRIES 应急开关;@Global 模块 - PublicService 六端点接缓存:列表缓存全量物化(分页切片在缓存外按请求执行, 修复'所有页返回第一页'的切片缓存错误)、详情/首页/树/标签组/树序元数据/ 族最低价聚合各按依赖域缓存 - 全写路径挂钩 bump:admin CRUD(goods/categories/countries/tags/tag-groups/ positions/origin-goods 标签)、sync 三同步、族重算、整理全家桶—— 事务提交成功后失效对应域,product-families 经 recompute 天然覆盖 - 测试:PublicCacheService 单测 13 例 + 失效链路集成 8 例(读命中/写后立即可见 端到端/每条写路径域断言);既有两套件测数据语义改为显式禁缓存
195 lines
7.7 KiB
TypeScript
195 lines
7.7 KiB
TypeScript
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();
|
|
});
|
|
});
|