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 () => { const moduleRef = await Test.createTestingModule({ providers: [PublicService, PrismaService], }).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); const organizeSvc = new OrganizeService( prisma, recomputeSvc, new ProductFamiliesService(prisma, recomputeSvc), ); 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], }).compile(); prisma = moduleRef.get(PrismaService); await prisma.onModuleInit(); familiesService = new (require('../product-families/product-families.service').ProductFamiliesService)( prisma, new FamilyRecomputeService(prisma), ); }); 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); }); });