- 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 例(读命中/写后立即可见 端到端/每条写路径域断言);既有两套件测数据语义改为显式禁缓存
262 lines
10 KiB
TypeScript
262 lines
10 KiB
TypeScript
import { PublicCacheService } from './public-cache.service';
|
|
import { Test } from '@nestjs/testing';
|
|
import { Prisma } from '@prisma/client';
|
|
import { PublicService } from './public.service';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
|
import { ProductFamiliesService } from '../product-families/product-families.service';
|
|
import { OrganizeService } from '../product-families/organize.service';
|
|
|
|
/**
|
|
* 三期灰度族块集成测试:PUBLIC_DETAIL_FROM_FAMILY 开关两态行为、
|
|
* 族块字段内容、Good.familyId 派生与成员移动联动。
|
|
*/
|
|
describe('PublicService family block (PUBLIC_DETAIL_FROM_FAMILY)', () => {
|
|
let service: PublicService;
|
|
let prisma: PrismaService;
|
|
const stamp = Date.now();
|
|
const createdOriginGoodIds: bigint[] = [];
|
|
const createdFamilyIds: bigint[] = [];
|
|
const createdGoodIds: bigint[] = [];
|
|
const createdCountryIds: bigint[] = [];
|
|
const createdCategoryIds: bigint[] = [];
|
|
const prevFlag = process.env.PUBLIC_DETAIL_FROM_FAMILY;
|
|
let sdsGoodId = '';
|
|
let familyId = 0n;
|
|
|
|
beforeAll(async () => {
|
|
// 本套件测数据语义(裸 prisma 改数后立即读公开端点),不走带 bump 的写路径——
|
|
// 禁用 public 缓存;缓存命中/失效语义由 public-cache.invalidation.spec.ts 覆盖
|
|
process.env.PUBLIC_CACHE_DISABLED = 'true';
|
|
const moduleRef = await Test.createTestingModule({
|
|
providers: [PublicService, PrismaService, PublicCacheService],
|
|
}).compile();
|
|
service = moduleRef.get(PublicService);
|
|
prisma = moduleRef.get(PrismaService);
|
|
await prisma.onModuleInit();
|
|
|
|
const country = await prisma.country.create({
|
|
data: { countryName: `公开族国家-${stamp}` },
|
|
});
|
|
createdCountryIds.push(country.id);
|
|
const category = await prisma.category.create({
|
|
data: { categoryName: `公开族分类-${stamp}` },
|
|
});
|
|
createdCategoryIds.push(category.id);
|
|
|
|
// 族 + 主链接(带变体)+ Good
|
|
const og = await prisma.originGood.create({
|
|
data: {
|
|
sdsGoodId: `pubfam-${stamp}`,
|
|
goodName: `美国(包邮)测试T恤-PF${stamp}-单面印花`,
|
|
goodPrice: new Prisma.Decimal(25),
|
|
craftLabel: '单面印花',
|
|
logisticsLabel: '包邮',
|
|
},
|
|
});
|
|
sdsGoodId = og.sdsGoodId;
|
|
createdOriginGoodIds.push(og.id);
|
|
await prisma.originGoodVariant.create({
|
|
data: {
|
|
originGoodId: og.id,
|
|
sdsVariantId: 'pf-v1',
|
|
sku: `PF-${stamp}-S`,
|
|
sizeId: 'size_S',
|
|
sizeName: 'S',
|
|
colorId: 'color_blk',
|
|
colorName: '黑色',
|
|
price: new Prisma.Decimal(25),
|
|
},
|
|
});
|
|
const family = await prisma.productFamily.create({
|
|
data: { familyName: `公开族-${stamp}`, familyCode: `PF${stamp}`, primaryOriginGoodId: og.id },
|
|
});
|
|
familyId = family.id;
|
|
createdFamilyIds.push(family.id);
|
|
await prisma.originGood.update({ where: { id: og.id }, data: { familyId: family.id } });
|
|
// 解析去运行时化:先整理派生标签,再重算(重算只聚合不解析)
|
|
const recomputeSvc = new FamilyRecomputeService(prisma, new PublicCacheService());
|
|
const organizeSvc = new OrganizeService(
|
|
prisma,
|
|
recomputeSvc,
|
|
new ProductFamiliesService(prisma, recomputeSvc),
|
|
new PublicCacheService(),
|
|
);
|
|
await organizeSvc.deriveFamilyTags(family.id);
|
|
await recomputeSvc.recomputeFamily(family.id);
|
|
|
|
const good = await prisma.good.create({
|
|
data: {
|
|
originGoodId: og.id,
|
|
familyId: family.id,
|
|
countryId: country.id,
|
|
categoryId: category.id,
|
|
goodName: `公开族商品-${stamp}`,
|
|
},
|
|
});
|
|
createdGoodIds.push(good.id);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
process.env.PUBLIC_DETAIL_FROM_FAMILY = prevFlag;
|
|
await prisma.good.deleteMany({ where: { id: { in: createdGoodIds } } });
|
|
await prisma.originGood.deleteMany({ where: { id: { in: createdOriginGoodIds } } });
|
|
await prisma.productFamily.deleteMany({ where: { id: { in: createdFamilyIds } } });
|
|
await prisma.country.deleteMany({ where: { id: { in: createdCountryIds } } });
|
|
await prisma.category.deleteMany({ where: { id: { in: createdCategoryIds } } });
|
|
await prisma.$disconnect();
|
|
});
|
|
|
|
it('默认(未设开关):输出族块', async () => {
|
|
delete process.env.PUBLIC_DETAIL_FROM_FAMILY
|
|
const detail = await service.getGood(familyId.toString());
|
|
expect(detail.goodId).toBe(familyId.toString());
|
|
expect(detail.family).toBeTruthy();
|
|
expect(detail.family!.familyCode).toBe(`PF${stamp}`);
|
|
expect(detail.family!.minPrice).toBe('25');
|
|
// 旧字段保留(向后兼容)
|
|
expect(detail.variants.length).toBe(1);
|
|
});
|
|
|
|
it('显式关闭(false):响应完全不含 family 键(应急回退)', async () => {
|
|
process.env.PUBLIC_DETAIL_FROM_FAMILY = 'false';
|
|
const detail = await service.getGood(familyId.toString());
|
|
expect(detail.goodId).toBe(familyId.toString());
|
|
expect('family' in detail).toBe(false);
|
|
});
|
|
|
|
it('族变体并集:族ID 命中商品且变体含全体成员;成员 sdsGoodId 不再可寻址', async () => {
|
|
process.env.PUBLIC_DETAIL_FROM_FAMILY = 'true';
|
|
// 再加一个同族成员(不同仓库段)
|
|
const og2 = await prisma.originGood.create({
|
|
data: {
|
|
sdsGoodId: `pubfam-m-${stamp}`,
|
|
goodName: `美国(包邮)测试T恤-PF${stamp}-单面印花-二仓`,
|
|
craftLabel: '单面印花',
|
|
logisticsLabel: '包邮',
|
|
familyId,
|
|
},
|
|
});
|
|
createdOriginGoodIds.push(og2.id);
|
|
await prisma.originGoodVariant.create({
|
|
data: {
|
|
originGoodId: og2.id,
|
|
sdsVariantId: 'pf-m-v1',
|
|
sku: `PF-${stamp}-M`,
|
|
sizeId: 'size_M',
|
|
sizeName: 'M',
|
|
colorId: 'color_blk',
|
|
colorName: '黑色',
|
|
price: new Prisma.Decimal(26),
|
|
},
|
|
});
|
|
|
|
// 族 ID 是唯一公开键:成员变体并入同一详情
|
|
const detail = await service.getGood(familyId.toString());
|
|
expect(detail.goodId).toBe(familyId.toString());
|
|
const skus = detail.variants.map((v) => v.sku);
|
|
expect(skus).toContain(`PF-${stamp}-S`);
|
|
expect(skus).toContain(`PF-${stamp}-M`); // 族成员变体并集
|
|
// 成员链接的 sdsGoodId 不再可寻址
|
|
await expect(service.getGood(`pubfam-m-${stamp}`)).rejects.toThrow();
|
|
// 清理:把成员移出族避免影响其他用例
|
|
await prisma.originGoodVariant.deleteMany({ where: { originGoodId: og2.id } });
|
|
await prisma.originGood.update({ where: { id: og2.id }, data: { familyId: null } });
|
|
});
|
|
|
|
it('无族商品:公开端点不可见(列表不含 / 详情 404)', async () => {
|
|
process.env.PUBLIC_DETAIL_FROM_FAMILY = 'true';
|
|
const og2 = await prisma.originGood.create({
|
|
data: {
|
|
sdsGoodId: `pubfam-2-${stamp}`,
|
|
goodName: `无族链接-${stamp}`,
|
|
goodPrice: new Prisma.Decimal(10),
|
|
},
|
|
});
|
|
createdOriginGoodIds.push(og2.id);
|
|
const good2 = await prisma.good.create({
|
|
data: {
|
|
originGoodId: og2.id,
|
|
countryId: createdCountryIds[0],
|
|
categoryId: createdCategoryIds[0],
|
|
goodName: `无族商品-${stamp}`,
|
|
},
|
|
});
|
|
createdGoodIds.push(good2.id);
|
|
const list = await service.getGoods({
|
|
page: 1,
|
|
pageSize: 50,
|
|
countryId: createdCountryIds[0].toString(),
|
|
});
|
|
expect(list.items.map((i) => i.goodName)).not.toContain(`无族商品-${stamp}`);
|
|
await expect(service.getGood(`pubfam-2-${stamp}`)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Good familyId derivation', () => {
|
|
let prisma: PrismaService;
|
|
let familiesService: import('../product-families/product-families.service').ProductFamiliesService;
|
|
const stamp = Date.now();
|
|
const ids = { originGood: [] as bigint[], family: [] as bigint[], good: [] as bigint[], country: [] as bigint[], category: [] as bigint[] };
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef = await Test.createTestingModule({
|
|
providers: [PrismaService, PublicCacheService],
|
|
}).compile();
|
|
prisma = moduleRef.get(PrismaService);
|
|
await prisma.onModuleInit();
|
|
familiesService = new (require('../product-families/product-families.service').ProductFamiliesService)(
|
|
prisma,
|
|
new FamilyRecomputeService(prisma, new PublicCacheService()),
|
|
new PublicCacheService(),
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await prisma.good.deleteMany({ where: { id: { in: ids.good } } });
|
|
await prisma.originGood.deleteMany({ where: { id: { in: ids.originGood } } });
|
|
await prisma.productFamily.deleteMany({ where: { id: { in: ids.family } } });
|
|
await prisma.country.deleteMany({ where: { id: { in: ids.country } } });
|
|
await prisma.category.deleteMany({ where: { id: { in: ids.category } } });
|
|
await prisma.$disconnect();
|
|
});
|
|
|
|
it('updateMembers 移除/添加成员时,Good.familyId 联动', async () => {
|
|
const country = await prisma.country.create({ data: { countryName: `联动国家-${stamp}` } });
|
|
ids.country.push(country.id);
|
|
const category = await prisma.category.create({ data: { categoryName: `联动分类-${stamp}` } });
|
|
ids.category.push(category.id);
|
|
const og = await prisma.originGood.create({
|
|
data: { sdsGoodId: `derive-${stamp}`, goodName: `联动链接-${stamp}` },
|
|
});
|
|
ids.originGood.push(og.id);
|
|
const family = await prisma.productFamily.create({
|
|
data: { familyName: `联动族-${stamp}`, primaryOriginGoodId: og.id },
|
|
});
|
|
ids.family.push(family.id);
|
|
await prisma.originGood.update({ where: { id: og.id }, data: { familyId: family.id } });
|
|
const good = await prisma.good.create({
|
|
data: {
|
|
originGoodId: og.id,
|
|
familyId: family.id,
|
|
countryId: country.id,
|
|
categoryId: category.id,
|
|
goodName: `联动商品-${stamp}`,
|
|
},
|
|
});
|
|
ids.good.push(good.id);
|
|
|
|
// 移出族 → Good.familyId 置空
|
|
await familiesService.updateMembers(family.id, { removeOriginGoodIds: [og.id.toString()] });
|
|
expect(
|
|
(await prisma.good.findUniqueOrThrow({ where: { id: good.id } })).familyId,
|
|
).toBeNull();
|
|
|
|
// 重新挂回 → Good.familyId 回填
|
|
await familiesService.updateMembers(family.id, { addOriginGoodIds: [og.id.toString()] });
|
|
expect(
|
|
(await prisma.good.findUniqueOrThrow({ where: { id: good.id } })).familyId,
|
|
).toBe(family.id);
|
|
});
|
|
});
|