'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const planningDatabase = require('../src/services/planningDatabase'); const productCodeClient = require('../src/services/alibaba1688ProductCodeClient'); const encodingRegistry = require('../src/services/alibaba1688ProductEncodingRegistry'); const { MAX_CODING_SKUS, normalizeCodingRequest, normalizeUpstreamResult, encodeAlibaba1688Product, assertAlibaba1688PlanItemsEncoded, } = require('../src/services/alibaba1688ProductEncodingService'); function productRecord(overrides = {}) { return { productId: '1073252781818', title: '实木圆桌', storeName: '测试家具厂', skuCount: 2, product: { skus: [ { index: 1, name: '原木色 / 60cm', specs: [{ name: '颜色', value: '原木色' }, { name: '尺寸', value: '60cm' }], }, { index: 2, name: '胡桃色 / 80cm', specs: [{ name: '颜色', value: '胡桃色' }, { name: '尺寸', value: '80cm' }], }, ], }, ...overrides, }; } function codingInput(overrides = {}) { return { operationCenter: '事业二部', categoryName: '住宅家具 - 书架', taxStatus: '含税', invoiceType: '普票', installationService: '不包安装', installationMode: '组装', skuItems: [ { sourceSkuKey: '1:颜色=原木色|尺寸=60cm', productName: '实木圆桌原木色60cm', colorSpec: '颜色:原木色;尺寸:60cm', lengthCm: 60, widthCm: 60, heightCm: 75, }, { sourceSkuKey: '2:颜色=胡桃色|尺寸=80cm', productName: '实木圆桌胡桃色80cm', colorSpec: '颜色:胡桃色;尺寸:80cm', lengthCm: 80, widthCm: 80, heightCm: 75, }, ], ...overrides, }; } test('builds a server-owned coding payload with every captured SKU', () => { const normalized = normalizeCodingRequest(codingInput(), productRecord(), { id: 7, name: '香茗', departmentName: '事业二部', }); assert.equal(normalized.requestPayload.ali1688ProductId, '1073252781818'); assert.equal(normalized.requestPayload.ali1688SupplierName, '测试家具厂'); assert.equal(normalized.requestPayload.sourceSystem, 'maixiang'); assert.equal(normalized.requestPayload.operatorName, '香茗'); assert.equal(normalized.requestPayload.operatorDepartment, '事业二部'); assert.equal(normalized.requestPayload.skuItems.length, 2); assert.match(normalized.sourceSkus[0].sourceSkuKey, /^1:/); }); test('rejects partial SKU submission and products above the coding limit', () => { assert.throws( () => normalizeCodingRequest(codingInput({ skuItems: codingInput().skuItems.map((item, index) => ( index === 0 ? { ...item, widthCm: null } : item )), }), productRecord()), error => error.statusCode === 400 && error.code === 'PRODUCT_CODE_SKU_DIMENSIONS_REQUIRED' ); assert.throws( () => normalizeCodingRequest(codingInput({ skuItems: codingInput().skuItems.slice(0, 1) }), productRecord()), error => error.statusCode === 422 && error.code === 'PRODUCT_CODE_SKU_COUNT_MISMATCH' ); const skus = Array.from({ length: MAX_CODING_SKUS + 1 }, (_, index) => ({ index: index + 1, name: `规格${index + 1}`, specs: [{ name: '规格', value: String(index + 1) }], })); assert.throws( () => normalizeCodingRequest(codingInput({ skuItems: [] }), productRecord({ skuCount: skus.length, product: { skus }, })), error => error.statusCode === 422 && error.code === 'PRODUCT_CODE_SKU_LIMIT_EXCEEDED' ); assert.throws( () => normalizeCodingRequest(codingInput({ skuItems: codingInput().skuItems.map((item, index) => ( index === 0 ? { ...item, sourceSkuKey: 'stale-key' } : item )), }), productRecord()), error => error.statusCode === 409 && error.code === 'PRODUCT_CODE_SKU_SNAPSHOT_CHANGED' ); }); test('an already encoded product returns before validating or calling the upstream API', async () => { let upstreamCalls = 0; const encoding = { productId: '1073252781818', status: 'encoded', styleCode: 'DF-A2SJ0094', }; const result = await encodeAlibaba1688Product('1073252781818', {}, { name: '香茗' }, { getProduct: async () => productRecord(), getExistingEncoding: async () => encoding, createCodes: async () => { upstreamCalls += 1; throw new Error('must not be called'); }, }); assert.equal(result.alreadyEncoded, true); assert.equal(result.encoding, encoding); assert.equal(upstreamCalls, 0); }); test('maps returned product codes to source SKUs by the submitted order', () => { const normalized = normalizeCodingRequest(codingInput(), productRecord(), { name: '香茗' }); const result = normalizeUpstreamResult({ created: true, styleCode: 'DF-A2SJ0094', productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], items: [ { styleCode: 'DF-A2SJ0094', productCode: 'DF-A2SJ009401' }, { styleCode: 'DF-A2SJ0094', productCode: 'DF-A2SJ009402' }, ], }, normalized); assert.equal(result.productCodes.length, 2); assert.equal(result.skuMappings[0].sourceSkuName, '原木色 / 60cm'); assert.equal(result.skuMappings[0].lengthCm, 60); assert.equal(result.skuMappings[0].heightCm, 75); assert.equal(result.skuMappings[1].productCode, 'DF-A2SJ009402'); assert.throws( () => normalizeUpstreamResult({ productCodes: ['DF-A2SJ009401'] }, normalized), error => error.statusCode === 409 && error.code === 'PRODUCT_CODE_RESPONSE_COUNT_MISMATCH' ); assert.throws( () => normalizeUpstreamResult({ productCodes: ['DF-A2SJ009401', 'DF-A2SJ009401'] }, normalized), error => error.statusCode === 409 && error.code === 'PRODUCT_CODE_RESPONSE_DUPLICATE' ); assert.throws( () => normalizeUpstreamResult({ productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], items: [{ ali1688ProductId: 'different-id' }, {}], }, normalized), error => error.statusCode === 409 && error.code === 'PRODUCT_CODE_RESPONSE_PRODUCT_MISMATCH' ); }); test('reused upstream codes are matched by SKU name instead of response order', () => { const normalized = normalizeCodingRequest(codingInput(), productRecord(), { name: '香茗' }); const result = normalizeUpstreamResult({ created: false, styleCode: 'DF-A2SJ0094', productCodes: ['DF-A2SJ009402', 'DF-A2SJ009401'], items: [ { productName: '实木圆桌胡桃色80cm', productCode: 'DF-A2SJ009402' }, { productName: '实木圆桌原木色60cm', productCode: 'DF-A2SJ009401' }, ], }, normalized); assert.deepEqual(result.productCodes, ['DF-A2SJ009401', 'DF-A2SJ009402']); assert.throws( () => normalizeUpstreamResult({ created: false, productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], items: [ { productName: '无法匹配', productCode: 'DF-A2SJ009401' }, { productName: '仍然无法匹配', productCode: 'DF-A2SJ009402' }, ], }, normalized), error => error.statusCode === 409 && error.code === 'PRODUCT_CODE_REUSED_MAPPING_CONFLICT' ); }); test('does not call the upstream service again when the product is already encoded', async () => { let upstreamCalls = 0; const result = await encodeAlibaba1688Product('1073252781818', codingInput(), { name: '香茗' }, { getProduct: async () => productRecord(), claim: async () => ({ claimed: false, record: { productId: '1073252781818', status: 'encoded', productCodes: ['DF-A2SJ009401'] }, }), createCodes: async () => { upstreamCalls += 1; throw new Error('must not be called'); }, }); assert.equal(result.alreadyEncoded, true); assert.equal(upstreamCalls, 0); }); test('persists both newly-created and reused upstream code responses', async () => { for (const created of [true, false]) { let completed; const result = await encodeAlibaba1688Product('1073252781818', codingInput(), { name: '香茗' }, { getProduct: async () => productRecord(), claim: async () => ({ claimed: true, token: 'claim-token' }), createCodes: async () => ({ statusCode: created ? 201 : 200, data: { created, matchedExisting: !created, styleCode: 'DF-A2SJ0094', productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], items: created ? [] : [ { productName: '实木圆桌原木色60cm', productCode: 'DF-A2SJ009401' }, { productName: '实木圆桌胡桃色80cm', productCode: 'DF-A2SJ009402' }, ], }, }), complete: async (_productId, token, value) => { assert.equal(token, 'claim-token'); completed = value; return { status: 'encoded', ...value }; }, }); assert.equal(result.alreadyEncoded, false); assert.equal(result.encoding.status, 'encoded'); assert.equal(completed.upstreamCreated, created); assert.equal(completed.skuMappings.length, 2); } }); test('marks the result as a conflict when SKUs change during the outbound request', async () => { let conflict; await assert.rejects( encodeAlibaba1688Product('1073252781818', codingInput(), { name: '香茗' }, { getProduct: async () => productRecord(), getLatestProduct: async () => productRecord({ product: { skus: [ { index: 1, name: 'changed', specs: [{ name: '颜色', value: '白色' }] }, { index: 2, name: 'changed-2', specs: [{ name: '颜色', value: '黑色' }] }, ], }, }), claim: async () => ({ claimed: true, token: 'claim-token' }), createCodes: async () => ({ data: { created: true, styleCode: 'DF-A2SJ0094', productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], }, }), conflict: async (productId, token, error) => { conflict = { productId, token, code: error.code }; }, }), error => error.code === 'PRODUCT_CODE_SKU_CHANGED_DURING_ENCODING' ); assert.deepEqual(conflict, { productId: '1073252781818', token: 'claim-token', code: 'PRODUCT_CODE_SKU_CHANGED_DURING_ENCODING', }); }); test('selection plans are hydrated from canonical encoded 1688 data and reject stale mappings', async () => { const normalized = normalizeCodingRequest(codingInput(), productRecord(), { name: '香茗' }); const encoding = { status: 'encoded', sourceSkuCount: normalized.sourceSkus.length, sourceSkuFingerprint: normalized.sourceSkuFingerprint, productCodes: ['DF-A2SJ009401', 'DF-A2SJ009402'], skuMappings: normalized.sourceSkus.map((entry, index) => ({ sourceSkuKey: entry.sourceSkuKey, productCode: `DF-A2SJ00940${index + 1}`, })), }; const rows = await assertAlibaba1688PlanItemsEncoded([ { marketItemId: '1073252781818', channelName: '1688', sourceRow: { untrusted: true } }, ], { getProduct: async () => ({ ...productRecord(), skuFingerprint: normalized.sourceSkuFingerprint, }), getEncoding: async () => encoding, }); assert.equal(rows[0].channelName, '1688'); assert.deepEqual(rows[0].sourceRow.encoding.skuMappings, encoding.skuMappings); assert.equal(rows[0].sourceRow.source, 'alibaba_1688_products'); await assert.rejects( assertAlibaba1688PlanItemsEncoded([{ marketItemId: '1073252781818', channelName: '1688' }], { getProduct: async () => ({ ...productRecord(), skuFingerprint: 'changed-fingerprint' }), getEncoding: async () => encoding, }), error => error.code === 'ALIBABA_1688_PRODUCT_ENCODING_STALE' ); let ordinaryLookupCount = 0; const ordinaryRows = await assertAlibaba1688PlanItemsEncoded([ { marketItemId: '1073252781818', channelName: '拼多多' }, ], { getProduct: async () => { ordinaryLookupCount += 1; return productRecord(); }, }); assert.equal(ordinaryLookupCount, 0); assert.equal(ordinaryRows[0].channelName, '拼多多'); }); test('records a failed claim without retrying the upstream request', async () => { let upstreamCalls = 0; let failed; await assert.rejects( encodeAlibaba1688Product('1073252781818', codingInput(), { name: '香茗' }, { getProduct: async () => productRecord(), claim: async () => ({ claimed: true, token: 'claim-token' }), createCodes: async () => { upstreamCalls += 1; throw Object.assign(new Error('upstream timeout'), { statusCode: 504 }); }, fail: async (productId, token, error) => { failed = { productId, token, message: error.message }; }, }), /upstream timeout/ ); assert.equal(upstreamCalls, 1); assert.deepEqual(failed, { productId: '1073252781818', token: 'claim-token', message: 'upstream timeout', }); }); test('the outbound client keeps the API key in a backend request header', async () => { const previous = { key: process.env.PRODUCT_CODE_API_KEY, baseUrl: process.env.PRODUCT_CODE_API_BASE_URL, timeout: process.env.PRODUCT_CODE_API_TIMEOUT_MS, }; process.env.PRODUCT_CODE_API_KEY = 'server-only-test-key'; process.env.PRODUCT_CODE_API_BASE_URL = 'https://yyd.jiansuntuzhi.cn'; process.env.PRODUCT_CODE_API_TIMEOUT_MS = '31000'; let requestConfig; try { const response = await productCodeClient.getProductCodeMeta({ httpClient: async (config) => { requestConfig = config; return { status: 200, data: { success: true, data: { operationCenters: [] } } }; }, }); assert.deepEqual(response.data.operationCenters, []); assert.equal(requestConfig.headers['X-API-Key'], 'server-only-test-key'); assert.equal(requestConfig.timeout, 31000); assert.equal(requestConfig.url, 'https://yyd.jiansuntuzhi.cn/api/public/product-code-board/meta'); } finally { if (previous.key === undefined) delete process.env.PRODUCT_CODE_API_KEY; else process.env.PRODUCT_CODE_API_KEY = previous.key; if (previous.baseUrl === undefined) delete process.env.PRODUCT_CODE_API_BASE_URL; else process.env.PRODUCT_CODE_API_BASE_URL = previous.baseUrl; if (previous.timeout === undefined) delete process.env.PRODUCT_CODE_API_TIMEOUT_MS; else process.env.PRODUCT_CODE_API_TIMEOUT_MS = previous.timeout; } }); test('the outbound client refuses to run without a server-side API key', () => { const previous = process.env.PRODUCT_CODE_API_KEY; delete process.env.PRODUCT_CODE_API_KEY; try { assert.throws( () => productCodeClient.getProductCodeApiConfig(), error => error.statusCode === 503 && error.code === 'PRODUCT_CODE_API_KEY_MISSING' ); } finally { if (previous === undefined) delete process.env.PRODUCT_CODE_API_KEY; else process.env.PRODUCT_CODE_API_KEY = previous; } }); test('an existing encoded registry row wins a duplicate insert race', async () => { const originalExecute = planningDatabase.execute; const originalQuery = planningDatabase.query; let encodingLookupCount = 0; planningDatabase.execute = async (sql) => { if (sql.startsWith('CREATE TABLE')) return { affectedRows: 0 }; if (sql.startsWith('INSERT INTO')) { throw Object.assign(new Error('duplicate'), { code: 'ER_DUP_ENTRY', errno: 1062 }); } throw new Error(`unexpected SQL: ${sql}`); }; planningDatabase.query = async (sql) => { if (sql.includes('information_schema.tables')) return [{ ok: 1 }]; if (sql.includes('information_schema.columns')) { return [{ column_name: 'source_sku_count' }, { column_name: 'source_sku_fingerprint' }]; } encodingLookupCount += 1; if (encodingLookupCount === 1) return []; return [{ product_id: '1073252781818', status: 'encoded', style_code: 'DF-A2SJ0094', style_codes_json: '["DF-A2SJ0094"]', product_codes_json: '["DF-A2SJ009401"]', sku_mapping_json: '[]', source_sku_count: 1, source_sku_fingerprint: 'fingerprint', attempt_count: 1, }]; }; try { const normalized = normalizeCodingRequest(codingInput(), productRecord()); const result = await encodingRegistry.claimEncoding( '1073252781818', normalized.requestPayload, { name: '香茗' }, { sourceSkuCount: normalized.sourceSkus.length, sourceSkuFingerprint: normalized.sourceSkuFingerprint, } ); assert.equal(result.claimed, false); assert.equal(result.record.status, 'encoded'); assert.deepEqual(result.record.productCodes, ['DF-A2SJ009401']); } finally { planningDatabase.execute = originalExecute; planningDatabase.query = originalQuery; } }); test('the claim lease always exceeds the configured outbound timeout', () => { const previousTimeout = process.env.PRODUCT_CODE_API_TIMEOUT_MS; const previousTtl = process.env.PRODUCT_CODE_CLAIM_TTL_SECONDS; process.env.PRODUCT_CODE_API_TIMEOUT_MS = '120000'; process.env.PRODUCT_CODE_CLAIM_TTL_SECONDS = '120'; try { assert.equal(encodingRegistry.getClaimTtlSeconds(), 180); } finally { if (previousTimeout === undefined) delete process.env.PRODUCT_CODE_API_TIMEOUT_MS; else process.env.PRODUCT_CODE_API_TIMEOUT_MS = previousTimeout; if (previousTtl === undefined) delete process.env.PRODUCT_CODE_CLAIM_TTL_SECONDS; else process.env.PRODUCT_CODE_CLAIM_TTL_SECONDS = previousTtl; } }); test('retry comparison preserves the first business request while allowing a different operator', () => { const original = { ...codingInput(), operatorName: '香茗', operatorDepartment: '事业二部', }; const sameBusiness = { ...codingInput(), operatorName: '管理员', operatorDepartment: '系统管理', }; assert.equal( encodingRegistry.retryComparableRequest(original), encodingRegistry.retryComparableRequest(sameBusiness) ); assert.notEqual( encodingRegistry.retryComparableRequest(original), encodingRegistry.retryComparableRequest({ ...sameBusiness, categoryName: '住宅家具 - 电脑桌', }) ); }); test('legacy failed requests may fill only missing dimensions while preserving the first request', () => { const normalized = normalizeCodingRequest(codingInput(), productRecord(), { name: 'first-operator', departmentName: 'first-department', }); const legacyRequest = JSON.parse(JSON.stringify(normalized.requestPayload)); delete legacyRequest.skuItems[0].lengthCm; legacyRequest.skuItems[0].widthCm = null; delete legacyRequest.skuItems[1].heightCm; const incoming = { ...normalized.requestPayload, operatorName: 'retry-operator', operatorDepartment: 'retry-department', }; const upgraded = encodingRegistry.reconcileRetryRequest(legacyRequest, incoming); assert.equal(upgraded.operatorName, 'first-operator'); assert.equal(upgraded.skuItems[0].lengthCm, 60); assert.equal(upgraded.skuItems[0].widthCm, 60); assert.equal(upgraded.skuItems[1].heightCm, 75); assert.throws( () => encodingRegistry.reconcileRetryRequest(normalized.requestPayload, { ...incoming, skuItems: incoming.skuItems.map((item, index) => ( index === 0 ? { ...item, lengthCm: 61 } : item )), }), error => error.code === 'PRODUCT_ENCODING_RETRY_REQUEST_CHANGED' ); assert.throws( () => encodingRegistry.reconcileRetryRequest(legacyRequest, { ...incoming, skuItems: incoming.skuItems.map((item, index) => ( index === 0 ? { ...item, productName: 'changed-name' } : item )), }), error => error.code === 'PRODUCT_ENCODING_RETRY_REQUEST_CHANGED' ); }); test('source snapshots require the full request count and a SHA-256 fingerprint', () => { const normalized = normalizeCodingRequest(codingInput(), productRecord()); assert.deepEqual( encodingRegistry.normalizeSourceSnapshot({ sourceSkuCount: normalized.sourceSkus.length, sourceSkuFingerprint: normalized.sourceSkuFingerprint, }, normalized.requestPayload), { sourceSkuCount: 2, sourceSkuFingerprint: normalized.sourceSkuFingerprint, } ); assert.throws( () => encodingRegistry.normalizeSourceSnapshot({ sourceSkuCount: 1, sourceSkuFingerprint: normalized.sourceSkuFingerprint, }, normalized.requestPayload), error => error.code === 'PRODUCT_ENCODING_SOURCE_SKU_CHANGED' ); assert.throws( () => encodingRegistry.normalizeSourceSnapshot({ sourceSkuCount: 2, sourceSkuFingerprint: 'not-a-fingerprint', }, normalized.requestPayload), error => error.code === 'PRODUCT_ENCODING_SOURCE_SNAPSHOT_REQUIRED' ); }); test('the first registry claim persists the SKU count and fingerprint in the insert', async () => { const normalized = normalizeCodingRequest(codingInput(), productRecord()); const originalExecute = planningDatabase.execute; const originalQuery = planningDatabase.query; let inserted = false; let insertSql = ''; let insertParams = []; planningDatabase.query = async (sql) => { if (sql.includes('information_schema.tables')) return [{ ok: 1 }]; if (sql.includes('information_schema.columns')) { return [{ column_name: 'source_sku_count' }, { column_name: 'source_sku_fingerprint' }]; } if (sql.includes('SELECT * FROM alibaba_1688_product_encodings')) { if (!inserted) return []; return [{ product_id: productRecord().productId, status: 'encoding', request_json: JSON.stringify(normalized.requestPayload), source_sku_count: normalized.sourceSkus.length, source_sku_fingerprint: normalized.sourceSkuFingerprint, attempt_count: 1, }]; } throw new Error(`unexpected SQL: ${sql}`); }; planningDatabase.execute = async (sql, params) => { if (sql.includes('INSERT INTO alibaba_1688_product_encodings')) { inserted = true; insertSql = sql; insertParams = params; return { affectedRows: 1 }; } throw new Error(`unexpected SQL: ${sql}`); }; try { const result = await encodingRegistry.claimEncoding( productRecord().productId, normalized.requestPayload, {}, { sourceSkuCount: normalized.sourceSkus.length, sourceSkuFingerprint: normalized.sourceSkuFingerprint, } ); assert.equal(result.claimed, true); assert.match(insertSql, /source_sku_count, source_sku_fingerprint/); assert.equal(insertParams[3], normalized.sourceSkus.length); assert.equal(insertParams[4], normalized.sourceSkuFingerprint); } finally { planningDatabase.execute = originalExecute; planningDatabase.query = originalQuery; } }); test('a retry claim atomically upgrades only legacy missing dimensions and rejects a changed fingerprint', async () => { const normalized = normalizeCodingRequest(codingInput(), productRecord()); const legacyRequest = JSON.parse(JSON.stringify(normalized.requestPayload)); delete legacyRequest.skuItems[0].lengthCm; legacyRequest.skuItems[0].widthCm = null; const originalExecute = planningDatabase.execute; const originalQuery = planningDatabase.query; let storedFingerprint = null; let updateSql = ''; let updateParams = []; let updateCount = 0; planningDatabase.query = async (sql) => { if (sql.includes('information_schema.tables')) return [{ ok: 1 }]; if (sql.includes('information_schema.columns')) { return [{ column_name: 'source_sku_count' }, { column_name: 'source_sku_fingerprint' }]; } if (sql.includes('SELECT * FROM alibaba_1688_product_encodings')) { return [{ product_id: productRecord().productId, status: 'failed', request_json: JSON.stringify(legacyRequest), source_sku_count: null, source_sku_fingerprint: storedFingerprint, attempt_count: 1, }]; } throw new Error(`unexpected SQL: ${sql}`); }; planningDatabase.execute = async (sql, params) => { if (sql.includes('INSERT INTO alibaba_1688_product_encodings')) { throw Object.assign(new Error('duplicate'), { code: 'ER_DUP_ENTRY', errno: 1062 }); } if (sql.includes('UPDATE alibaba_1688_product_encodings')) { updateCount += 1; updateSql = sql; updateParams = params; return { affectedRows: 1 }; } throw new Error(`unexpected SQL: ${sql}`); }; const snapshot = { sourceSkuCount: normalized.sourceSkus.length, sourceSkuFingerprint: normalized.sourceSkuFingerprint, }; try { const result = await encodingRegistry.claimEncoding( productRecord().productId, normalized.requestPayload, {}, snapshot ); assert.equal(result.claimed, true); assert.equal(result.requestPayload.skuItems[0].lengthCm, 60); assert.equal(result.requestPayload.skuItems[0].widthCm, 60); assert.match(updateSql, /source_sku_count IS NULL OR source_sku_count = \?/); assert.match(updateSql, /source_sku_fingerprint IS NULL OR source_sku_fingerprint = \?/); assert.equal(updateParams[2], snapshot.sourceSkuCount); assert.equal(updateParams[3], snapshot.sourceSkuFingerprint); storedFingerprint = 'a'.repeat(64); await assert.rejects( encodingRegistry.claimEncoding( productRecord().productId, normalized.requestPayload, {}, snapshot ), error => error.code === 'PRODUCT_ENCODING_SOURCE_SKU_CHANGED' ); assert.equal(updateCount, 1); } finally { planningDatabase.execute = originalExecute; planningDatabase.query = originalQuery; } }); test('selection plans reject incomplete, duplicate or mismatched encoded SKU mappings', async () => { const normalized = normalizeCodingRequest(codingInput(), productRecord()); const product = { ...productRecord(), skuFingerprint: normalized.sourceSkuFingerprint, }; const validEncoding = { status: 'encoded', sourceSkuCount: 2, sourceSkuFingerprint: normalized.sourceSkuFingerprint, productCodes: ['CODE-1', 'CODE-2'], skuMappings: normalized.sourceSkus.map((entry, index) => ({ sourceSkuKey: entry.sourceSkuKey, productCode: `CODE-${index + 1}`, })), }; const invalidEncodings = [ { ...validEncoding, sourceSkuCount: 1 }, { ...validEncoding, productCodes: ['CODE-1'] }, { ...validEncoding, productCodes: ['CODE-1', 'CODE-1'] }, { ...validEncoding, skuMappings: validEncoding.skuMappings.slice(0, 1) }, { ...validEncoding, skuMappings: validEncoding.skuMappings.map(mapping => ({ ...mapping, sourceSkuKey: validEncoding.skuMappings[0].sourceSkuKey, })), }, { ...validEncoding, skuMappings: validEncoding.skuMappings.map((mapping, index) => ( index === 0 ? { ...mapping, productCode: 'UNKNOWN-CODE' } : mapping )), }, ]; for (const encoding of invalidEncodings) { await assert.rejects( assertAlibaba1688PlanItemsEncoded([ { marketItemId: product.productId, channelName: '1688' }, ], { getProduct: async () => product, getEncoding: async () => encoding, }), error => error.code === 'ALIBABA_1688_PRODUCT_ENCODING_STALE' ); } });