'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { normalizeAlibaba1688Product, normalizeAlibaba1688ImageUrl, mapProductRow, listAlibaba1688Products, replaceAlibaba1688Product, } = require('../src/services/alibaba1688ProductService'); const planningDatabase = require('../src/services/planningDatabase'); function product(overrides = {}) { return { productId: '1073252781818', title: '圆桌测试商品', sourceUrl: 'https://detail.1688.com/offer/1073252781818.html', mainImages: ['https://img.example.com/main.jpg'], detailImages: ['https://img.example.com/detail.jpg'], videos: [{ url: 'https://cloud.video.taobao.com/play/example.mp4', coverUrl: 'https://img.example.com/video-cover.jpg_sum.jpg', duration: 20.4, width: 720, height: 1280, source: 'main-gallery', }], skuDimensions: [ { name: '颜色', values: ['原木色', '胡桃色'] }, { name: '尺寸', values: ['60*60', '70*70'] }, ], skus: [{ name: '原木色 / 60*60', specs: [{ name: '颜色', value: '原木色' }, { name: '尺寸', value: '60*60' }], image: 'https://img.example.com/sku-original-60.jpg', price: 180, freight: 40, }], attributes: [{ name: '材质', value: '实木' }], storeCategory: '鞋架/鞋柜', platformCategory: '家装建材 > 简易家具', dropshipSupported: true, importTag: '测款', collectedAt: '2026-08-14T02:13:08.923Z', ...overrides, }; } test('normalizes a complete 1688 snapshot for full server replacement', () => { const normalized = normalizeAlibaba1688Product({ dingtalkUserName: '香茗', sourceType: 'product', sourceImportId: 'import-1', product: product(), }, '1073252781818'); assert.equal(normalized.productId, '1073252781818'); assert.equal(normalized.detailImageCount, 1); assert.equal(normalized.skuCount, 1); assert.equal(normalized.snapshot.skuDimensions.length, 2); assert.deepEqual(normalized.snapshot.skuImages, ['https://img.example.com/sku-original-60.jpg']); assert.equal(normalized.snapshot.skus[0].image, 'https://img.example.com/sku-original-60.jpg'); assert.deepEqual(normalized.snapshot.videos, [{ url: 'https://cloud.video.taobao.com/play/example.mp4', coverUrl: 'https://img.example.com/video-cover.jpg', duration: 20.4, width: 720, height: 1280, mimeType: '', source: 'main-gallery', }]); assert.equal(normalized.snapshot.schemaVersion, 2); assert.match(normalized.skuFingerprint, /^[a-f0-9]{64}$/); assert.equal(normalized.snapshot.storeCategory, '鞋架/鞋柜'); assert.equal(normalized.snapshot.platformCategory, '家装建材 > 简易家具'); assert.equal(normalized.snapshot.dropshipSupported, true); assert.deepEqual(normalized.snapshot.skus[0].specs, [ { name: '颜色', value: '原木色' }, { name: '尺寸', value: '60*60' }, ]); assert.equal(normalized.syncOperatorName, '香茗'); }); test('stores original Alibaba SKU image URLs instead of _sum thumbnails', () => { const compressed = 'https://cbu01.alicdn.com/img/ibank/example.jpg_sum.jpg'; const original = 'https://cbu01.alicdn.com/img/ibank/example.jpg'; const normalized = normalizeAlibaba1688Product({ dingtalkUserName: '香茗', sourceType: 'product', product: product({ images: [compressed], mainImages: [compressed], detailImages: [compressed], skuImages: [compressed], skus: [{ name: '原木色', image: compressed }], baseSkuRows: [{ name: '原木色', image: compressed }], }), }); assert.equal(normalizeAlibaba1688ImageUrl(`${compressed}?x=1`), `${original}?x=1`); assert.deepEqual(normalized.snapshot.images, [original]); assert.deepEqual(normalized.snapshot.mainImages, [original]); assert.deepEqual(normalized.snapshot.detailImages, [original]); assert.deepEqual(normalized.snapshot.skuImages, [original]); assert.equal(normalized.snapshot.skus[0].image, original); assert.equal(normalized.snapshot.baseSkuRows[0].image, original); }); test('promotes videos preserved by older servers in skuScan metadata', () => { const normalized = normalizeAlibaba1688Product({ product: product({ videos: [], skuScan: { complete: true, videos: [{ src: 'https://cloud.video.taobao.com/play/legacy.mp4', poster: 'https://img.example.com/legacy-cover.jpg', duration: 18, }], }, }), }); assert.equal(normalized.snapshot.videos.length, 1); assert.equal(normalized.snapshot.videos[0].url, 'https://cloud.video.taobao.com/play/legacy.mp4'); }); test('rejects incomplete snapshots and mismatched route IDs', () => { assert.throws( () => normalizeAlibaba1688Product({ product: product({ detailImages: [] }) }), error => error.statusCode === 422 && error.code === 'DETAIL_IMAGES_REQUIRED' ); assert.throws( () => normalizeAlibaba1688Product({ product: product() }, 'different-id'), error => error.statusCode === 409 && error.code === 'PRODUCT_ID_MISMATCH' ); }); test('maps server rows without exposing the large snapshot in list mode', () => { const row = { product_id: '1073252781818', title: '圆桌测试商品', product_snapshot_json: JSON.stringify(product()), main_image_count: 16, detail_image_count: 24, sku_count: 66, attribute_count: 49, sync_version: 3, }; const summary = mapProductRow(row, false); const detail = mapProductRow(row, true); assert.equal(summary.product, undefined); assert.equal(summary.skuCount, 66); assert.equal(detail.product.productId, '1073252781818'); assert.equal(detail.syncVersion, 3); }); test('lists 1688 products with multi-status filtering and unfiltered status summary', async () => { const originalQuery = planningDatabase.query; const queries = []; planningDatabase.query = async (sql, params = []) => { queries.push({ sql, params }); if (sql.includes('information_schema.tables')) return [{ exists: 1 }]; if (sql.includes('COUNT(*) AS total') && !sql.includes('GROUP BY')) return [{ total: 2 }]; if (sql.includes('GROUP BY publish_status')) { return [ { publish_status: 'pending', total: 4, latest_updated_at: '2026-08-14 10:00:00' }, { publish_status: 'ready', total: 2, latest_updated_at: '2026-08-14 11:00:00' }, { publish_status: 'published', total: 3, latest_updated_at: '2026-08-13 12:00:00' }, ]; } return [{ product_id: '1073252781818', title: '圆桌测试商品', publish_status: 'ready', sync_version: 2, }]; }; try { const result = await listAlibaba1688Products({ keyword: '圆桌', importTag: '测款', sourceType: 'product', syncOperatorName: '香茗', publishStatus: 'pending,ready', page: 2, pageSize: 10, }); assert.equal(result.total, 2); assert.equal(result.items.length, 1); assert.equal(result.summary.pending, 4); assert.equal(result.summary.ready, 2); assert.equal(result.summary.published, 3); assert.equal(result.summary.failed, 0); assert.equal(result.latestUpdatedAt, '2026-08-14 11:00:00'); const countQuery = queries.find(query => query.sql.includes('COUNT(*) AS total') && !query.sql.includes('GROUP BY')); assert.match(countQuery.sql, /publish_status IN \(\?, \?\)/); assert.deepEqual(countQuery.params.slice(-2), ['pending', 'ready']); const summaryQuery = queries.find(query => query.sql.includes('GROUP BY publish_status')); assert.doesNotMatch(summaryQuery.sql, /publish_status IN/); assert.deepEqual(summaryQuery.params.slice(-3), ['测款', 'product', '%香茗%']); const listQuery = queries.find(query => query.sql.includes('ORDER BY updated_at DESC')); assert.deepEqual(listQuery.params.slice(-4), ['pending', 'ready', 10, 10]); } finally { planningDatabase.query = originalQuery; } }); test('paginates 1688 products and applies common server-side filters', async () => { const originalQuery = planningDatabase.query; const calls = []; planningDatabase.query = async (sql, params = []) => { calls.push({ sql, params }); if (sql.includes('information_schema.tables')) return [{ exists: 1 }]; if (sql.includes('COUNT(*) AS total') && !sql.includes('GROUP BY')) return [{ total: 51 }]; if (sql.includes('GROUP BY publish_status')) return []; return [{ product_id: '1073252781818', title: '圆桌测试商品', store_name: '测试家具厂', category: '餐厅家具', import_tag: '测款', publish_status: 'pending', sync_operator_name: '香茗', source_type: 'store', product_snapshot_json: JSON.stringify(product()), }]; }; try { const result = await listAlibaba1688Products({ page: 2, pageSize: 50, keyword: '桌', importTag: '测款', publishStatus: 'pending', syncOperatorName: '香茗', sourceType: 'store', }); const countCall = calls.find(call => call.sql.includes('COUNT(*) AS total')); const listCall = calls.find(call => call.sql.includes('ORDER BY updated_at DESC')); assert.equal(result.page, 2); assert.equal(result.pageSize, 50); assert.equal(result.total, 51); assert.equal(result.items[0].syncOperatorName, '香茗'); assert.match(countCall.sql, /category LIKE \?/); assert.match(countCall.sql, /sync_operator_name LIKE \?/); assert.match(countCall.sql, /source_type = \?/); assert.deepEqual(countCall.params, [ '%桌%', '%桌%', '%桌%', '%桌%', '%桌%', '测款', 'store', '%香茗%', 'pending', ]); assert.deepEqual(listCall.params.slice(-2), [50, 50]); } finally { planningDatabase.query = originalQuery; } }); test('replaces the complete server snapshot without resetting publish workflow state', async () => { const originalQuery = planningDatabase.query; const originalExecute = planningDatabase.execute; const executed = []; planningDatabase.query = async (sql) => { if (sql.includes('information_schema.tables')) return [{ exists: 1 }]; return [{ product_id: '1073252781818', title: '新标题', product_snapshot_json: JSON.stringify(product({ title: '新标题' })), sync_version: 3, }]; }; planningDatabase.execute = async (sql, params) => { executed.push({ sql, params }); return { affectedRows: 2 }; }; try { const result = await replaceAlibaba1688Product( '1073252781818', { product: product({ title: '新标题' }) }, { apiKeyId: 'key-id', apiKeyName: 'desktop' } ); assert.equal(result.operation, 'replaced'); assert.equal(result.item.title, '新标题'); assert.equal(executed.length, 1); assert.equal(executed[0].params.length, 23); assert.doesNotMatch(executed[0].sql, /publish_status = 'pending'/); assert.doesNotMatch(executed[0].sql, /published_goods_json = NULL/); assert.doesNotMatch(executed[0].sql, /last_published_at = NULL/); assert.match(executed[0].sql, /sync_version = sync_version \+ 1/); } finally { planningDatabase.query = originalQuery; planningDatabase.execute = originalExecute; } });