/** @jest-environment node */ import type { ConnectionPool } from 'mssql' import { fetchPerformanceSourceRows, metricSourceForBrand, readTmallStoreSourceRanges, sourceSnapshotFilter, type SourceMetricRow, } from '@/lib/performance-conversion-source' const request = { input: jest.fn(), query: jest.fn() } const pool = { request: () => request } as unknown as ConnectionPool const productId = '1076069062942' function sourceRow(overrides = {}) { return { sourceRowId: '10', metricDate: '2026-10-20', productId, sourceBrand: '朴美家具旗舰店', ...overrides, } as SourceMetricRow } describe('performance conversion source selection and SQL scope', () => { beforeEach(() => { jest.clearAllMocks() request.input.mockReturnValue(request) request.query.mockResolvedValue({ recordset: [] }) }) it.each([ ['顾家', '顾家家居小件家具旗舰店'], ['索菲亚', '索菲亚实木风旗舰店'], ['朴美', '朴美家具旗舰店'], ] as const)('binds %s only to its verified store', async (brand, store) => { await fetchPerformanceSourceRows(pool, metricSourceForBrand(brand), productId, '2026-09-01', '2026-10-20') expect(request.input).toHaveBeenCalledWith('store', expect.anything(), store) const query = request.query.mock.calls[0][0] expect(query).toContain('FROM [rpt].[TmallStoreOverall] o') expect(query).toContain('LEFT JOIN [rpt].[TmallStoreBusinessRoi] r') expect(query).toContain('r.[店铺名称] = o.[店铺名称] AND r.[统计日期] = o.[统计日期]') expect(query).toContain('LTRIM(RTRIM(r.[商品ID])) = LTRIM(RTRIM(o.[商品ID]))') expect(query).toContain('o.[主图整体点击率]') expect(query).toContain('o.[支付买家数]') expect(query).toContain('r.[展现量]') expect(query).toContain('r.[推广ROI]') expect(query).not.toContain('PC端曝光量') expect(query).not.toContain(store) // Store/product/date values are parameterized. expect(query).not.toContain(productId) }) it.each(['亿家达', '卓禾', '欧意朗'] as const)('leaves %s on the original source', async (brand) => { const source = metricSourceForBrand(brand) expect(source).toEqual({ kind: 'legacy' }) await fetchPerformanceSourceRows(pool, source, productId, '2026-09-01', '2026-10-20') expect(request.query.mock.calls[0][0]).toContain('FROM [rpt].[TmallSrcBusinessRoi]') expect(request.input).not.toHaveBeenCalledWith('store', expect.anything(), expect.anything()) }) it('scopes local snapshots by source and brand, without mixing legacy IDs', () => { expect(sourceSnapshotFilter(metricSourceForBrand('朴美'))).toEqual({ sourceKey: { startsWith: 'tmall-store-v1:朴美:' } }) expect(sourceSnapshotFilter(metricSourceForBrand('索菲亚'))).toEqual({ sourceKey: { startsWith: 'tmall-store-v1:索菲亚:' } }) expect(sourceSnapshotFilter(metricSourceForBrand('亿家达'))).toEqual({ NOT: { sourceKey: { startsWith: 'tmall-store-v1:' } } }) }) it('refuses ambiguous same-store/product/date joins instead of multiplying totals', async () => { request.query.mockResolvedValue({ recordset: [sourceRow(), sourceRow({ sourceRowId: '11' })] }) await expect(fetchPerformanceSourceRows(pool, metricSourceForBrand('朴美'), productId, '2026-10-01', '2026-10-20')) .rejects.toThrow('重复记录') }) it.each([ { productId: '999999' }, { sourceBrand: '顾家家居小件家具旗舰店' }, { metricDate: '2026-09-30' }, { metricDate: null }, ])('rejects an unexpected join key or date: %j', async (overrides) => { request.query.mockResolvedValue({ recordset: [sourceRow(overrides)] }) await expect(fetchPerformanceSourceRows(pool, metricSourceForBrand('朴美'), productId, '2026-10-01', '2026-10-20')) .rejects.toThrow('关联键或日期无效') }) it('does not silently truncate a result and then purge uncopied snapshots', async () => { request.query.mockResolvedValue({ recordset: Array.from({ length: 5001 }, () => sourceRow()) }) await expect(fetchPerformanceSourceRows(pool, metricSourceForBrand('朴美'), productId, '2026-10-01', '2026-10-20')) .rejects.toThrow('安全上限') }) it('keeps the sources independent and leaves absent supplemental fields as null', async () => { const row = sourceRow({ promotionSourceRowId: null, impressions: null, roi: null, clickRate: '0.02' }) request.query.mockResolvedValue({ recordset: [row] }) const rows = await fetchPerformanceSourceRows(pool, metricSourceForBrand('朴美'), productId, '2026-10-01', '2026-10-20') expect(rows[0]).toMatchObject({ impressions: null, roi: null, clickRate: '0.02' }) }) it('returns retention boundaries and uses the slower view for correction progress', async () => { request.query.mockResolvedValue({ recordset: [ { sourceKind: 'overall', sourceStore: '朴美家具旗舰店', firstDate: '2026-09-20', lastDate: '2026-10-20' }, { sourceKind: 'roi', sourceStore: '朴美家具旗舰店', firstDate: '2026-09-20', lastDate: '2026-10-18' }, { sourceKind: 'overall', sourceStore: '索菲亚实木风旗舰店', firstDate: '2026-09-20', lastDate: '2026-10-20' }, ] }) const ranges = await readTmallStoreSourceRanges(pool, '2026-10-22') expect(ranges.get('朴美')).toEqual({ firstDate: '2026-09-20', lastDate: '2026-10-20', correctionThrough: '2026-10-18' }) expect(ranges.get('索菲亚')).toEqual({ firstDate: '2026-09-20', lastDate: '2026-10-20', correctionThrough: null }) expect(ranges.has('顾家')).toBe(false) }) })