'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const axios = require('axios'); const database = require('../src/config/database'); const planningDatabase = require('../src/services/planningDatabase'); const { normalizeImageGenerationConfig } = require('../src/services/selectionFissionAiSettings'); const { requestQwenImage, resolveQwenImageDimensions, } = require('../src/services/selectionImageGenerationService'); const { _test: { advanceWorkflowProgress, mapPlan, mapPricingRow, mapActivityPricingRow, buildDeliveryPayload, buildDeliveryReadiness, buildDeliveryTaskPayload, buildDeliveryVideoPayload, buildEmptyPddActivityPrices, buildMarketProductDetailUrl, buildMarketReferencePriceBand, buildAlibaba1688SkuSnapshot, resolveAlibaba1688GlobalEncoding, applyAlibaba1688GlobalEncodingToSnapshot, buildAlibaba1688GlobalEncodingState, buildAlibaba1688SourceReadiness, buildAlibaba1688PricingEvidence, buildAlibaba1688ListingCompliance, getPddDraftItemConfig, assertAlibaba1688PddDraftPlan, buildSkuSelectionAssetsFromSnapshot, formatImageAspectRatio, hydrateDeliveryVideoAssets, buildDeliveryMaterials, getSkuSelectionImage, getMainGenerationSourceImage, getSimilarProductsConfig, getSimilarProductsRequestHeaders, createSimilarProductsRequestError, isRetryableSimilarProductsError, isSimilarProductsFallbackEligible, getWorkflowSelectedSkuCodes, getWorkflowSelectedSkuRows, buildBulkSkuNameSelections, getConfiguredMainImageCount, isSelectionFissionSpuCurrent, filterWorkflowRowsBySelectedSkus, getWorkflowProductTitleOptions, getCurrentTitleKeywordStrategy, getTaskForbiddenBrandTerms, findForbiddenTitleBrandTerm, extractReferenceBrandTerms, validateAdditionalProductTitleCandidates, prepareSkuSceneImageRows, groupSkuSceneRowsBySourceImage, splitIntoBatches, summarizeSkuSceneImageRows, finalizeSourceSkuImageRows, resetInterruptedSkuSceneImageState, mergeSelectedSkuRowsWithLegacySnapshot, batchUpdateSelectionPricingRows, insertRowsInBatches, getSelectionFissionItemNodeState, getRelistableSelectionTasks, hashClaimToken, normalizeClaimToken, normalizeSqlBindValues, assertValidClaimToken, fetchCompanySkusFromJstApi, hydrateCompanySpuCandidateDetails, mapJstCompanySkuRow, normalizeAutoListingProductId, normalizeProfitPricingRows, pruneSelectionSkuPayload, normalizeListingShop, buildSelectionFissionExecutionOptions, canDelegateListingExecution, resolveMinimumPrice, resolvePreservedSkuNameSelection, normalizeMarketItemLookupId, normalizeMarketReferenceCandidate, normalizePriceBandList, sortPriceBandValues, inferMarketChannelName, normalizeSelectionFissionPlanItems, normalizeSimilarCandidate, normalizeSimilarResponsePayload, normalizeWhiteBackgroundSkuAssetDetails, collectMarketReferenceCandidates, fillMonthlySalesTrend6m, normalizeSpuSelections, parseCompanySkuMarkdown, parseMarketReferenceMarkdown, filterStrictMarketReferenceCandidates, hasObviousMarketCategoryConflict, supplementMarketReferenceCandidates, rankSpuCandidatesByCategory, validateCompanySpuCandidates, getCompanyCategoryTerms, parseImageDimensions, requestSelectionGeneratedImage, generateMainReplacementImage, generateSkuSceneImage, detectImageContent, getExternalRequestErrorMessage, hasStandardImageExtension, shouldRehostImageGenerationReference, normalizeImageGenerationReferenceUrl, buildMainImageReferenceImages, normalizeMainImageRetryIndexes, isSelectionWorkflowTaskStepComplete, collectTitleStrategyKeywords, matchTitleKeywordEvidence, buildWorkflowSummary, toMysqlDateTime, }, } = require('../src/services/planningSelectionFissionService'); test('listing execution options prefer self and only expose same-shop same-department delegates', () => { const shops = [ { id: 1, dingtalk_user_name: '当前用户', shop_id: 'shop-a', shop_name: '旗舰店', platform: 'PDD', status: 'active', client_id: 'self-client', login_status: 'login_required', }, { id: 2, dingtalk_user_name: '同事甲', shop_id: 'shop-a', shop_name: '旗舰店', platform: 'PDD', status: 'active', client_id: 'team-client', login_status: 'authenticated', }, { id: 3, dingtalk_user_name: '同事甲', shop_id: 'shop-b', shop_name: '其他店铺', platform: 'PDD', status: 'active', client_id: 'team-client', login_status: 'authenticated', }, { id: 4, dingtalk_user_name: '其他部门', shop_id: 'shop-a', shop_name: '旗舰店', platform: 'PDD', status: 'active', client_id: 'other-client', login_status: 'authenticated', }, ]; const presence = { users: [ { userId: 10, name: '当前用户', username: 'self', departmentName: '运营中心B', isOnline: true, devices: [{ clientId: 'self-client', deviceName: '我的 Mac', isOnline: true }], }, { userId: 11, name: '同事甲', username: 'peer', departmentName: '运营中心B', isOnline: true, devices: [{ clientId: 'team-client', deviceName: '同事电脑', isOnline: true }], }, { userId: 12, name: '其他部门', username: 'outsider', departmentName: '企划中心', isOnline: true, devices: [{ clientId: 'other-client', deviceName: '其他电脑', isOnline: true }], }, ], }; const result = buildSelectionFissionExecutionOptions(shops, presence, { id: 10, name: '当前用户', username: 'self', departmentName: '运营中心B', role: 'user', }); assert.equal(result.shops.length, 1); assert.equal(result.shops[0].shopId, 'shop-a'); assert.equal(result.shops[0].availableSessionCount, 1); assert.equal(result.shops[0].recommendedSessionId, '2'); assert.equal(result.sessions.find((session) => session.sessionId === '2').delegated, true); assert.equal(result.recommended, null); assert.equal(canDelegateListingExecution( { name: '当前用户', departmentName: '运营中心B' }, { name: '同事甲', departmentName: '运营中心B' } ), true); assert.equal(canDelegateListingExecution( { name: '当前用户', departmentName: '运营中心B' }, { name: '其他部门', departmentName: '企划中心' } ), false); }); test('listing execution options recommend the requester authenticated session first', () => { const result = buildSelectionFissionExecutionOptions([ { id: 8, dingtalk_user_name: '当前用户', shop_id: 'shop-a', shop_name: '旗舰店', platform: 'PDD', status: 'active', client_id: 'self-client', login_status: 'authenticated', }, { id: 9, dingtalk_user_name: '同事甲', shop_id: 'shop-a', shop_name: '旗舰店', platform: 'PDD', status: 'active', client_id: 'team-client', login_status: 'authenticated', }, ], { users: [ { userId: 10, name: '当前用户', username: 'self', departmentName: '运营中心B', isOnline: true, devices: [{ clientId: 'self-client', deviceName: '我的 Mac', isOnline: true }], }, { userId: 11, name: '同事甲', username: 'peer', departmentName: '运营中心B', isOnline: true, devices: [{ clientId: 'team-client', deviceName: '同事电脑', isOnline: true }], }, ], }, { id: 10, name: '当前用户', username: 'self', departmentName: '运营中心B', role: 'user', }); assert.deepEqual(result.recommended, { listingShopId: 'shop-a', executorSessionId: '8' }); assert.equal(result.shops[0].recommendedSessionId, '8'); }); function createVerifiedSkuImageRow({ skuCode, skuName = skuCode, sourceImageUrl = '', generatedImageUrl, mode = 'source_original', marketReferenceImageUrl = '', status = 'completed', error = '', }) { return { skuCode, skuName, originalImageUrl: sourceImageUrl, generatedImageUrl, generationMode: mode, marketReferenceImageUrl: mode === 'market_composite' ? marketReferenceImageUrl : '', generatedForSkuCode: String(skuCode).trim().toUpperCase(), generatedForSkuName: skuName, generatedFromImageUrl: sourceImageUrl, status, progress: 100, error, }; } test('interrupted SKU image recovery preserves completed images and resets only unfinished work', () => { const recovered = resetInterruptedSkuSceneImageState({ currentBatch: 3, batchTotal: 3, retrySkuCodes: ['SKU-11'], rows: [ { skuCode: 'SKU-10', status: 'completed', progress: 100, generatedImageUrl: 'https://img.example/10.jpg' }, { skuCode: 'SKU-11', status: 'processing', progress: 20, generatedImageUrl: '', error: '' }, { skuCode: 'SKU-12', status: 'waiting', progress: 0, generatedImageUrl: '' }, ], }); assert.equal(recovered.currentBatch, 0); assert.deepEqual(recovered.retrySkuCodes, []); assert.equal(recovered.completedCount, 1); assert.equal(recovered.rows[0].generatedImageUrl, 'https://img.example/10.jpg'); assert.equal(recovered.rows[0].status, 'completed'); assert.equal(recovered.rows[1].status, 'waiting'); assert.equal(recovered.rows[1].progress, 0); assert.equal(recovered.rows[2].status, 'waiting'); }); const { buildAlibaba1688SkuKey, buildAlibaba1688SkuFingerprint, } = require('../src/services/alibaba1688SkuIdentity'); test('1688 draft config is selected per plan item and ordinary plans are rejected', () => { const input = { pddDraftItems: [ { planItemId: 101, catId: 10607 }, { planItemId: 102, catId: 10553 }, ], }; assert.equal(getPddDraftItemConfig(input, 102).catId, 10553); assert.equal(getPddDraftItemConfig(input, 999), null); assert.doesNotThrow(() => assertAlibaba1688PddDraftPlan({ workflowProfile: 'alibaba_1688_pending', workflowStatus: 'completed', workflowProgress: 100, })); assert.throws( () => assertAlibaba1688PddDraftPlan({ workflowProfile: 'ordinary' }), (error) => error.code === 'PDD_DRAFT_WORKFLOW_UNSUPPORTED' ); }); test('company SPU candidates always hydrate display images independently from operating metrics', async () => { const calls = []; const candidates = [{ spuCode: 'CZ0009', spuName: '', imageUrl: '', skuCount: 0, operatingMetrics: { minCost: null, maxCost: null, material: '' }, }]; const rows = await hydrateCompanySpuCandidateDetails(candidates, async (spuCode, options) => { calls.push({ spuCode, options }); return { rows: [{ skuCode: 'CZ0009N01V01', skuName: '胡桃色床头柜', productName: '实木床头柜', skuImageUrl: 'https://img.alicdn.com/example.jpg', material: '橡木', cost: 128, }], }; }); assert.deepEqual(calls, [{ spuCode: 'CZ0009', options: { limit: 300, includeInactive: true }, }]); assert.equal(rows[0].imageUrl, 'https://img.alicdn.com/example.jpg'); assert.equal(rows[0].spuName, '实木床头柜'); assert.equal(rows[0].skuCount, 1); assert.equal(rows[0].operatingMetrics.material, '橡木'); assert.equal(rows[0].operatingMetrics.minCost, 128); assert.equal(rows[0].operatingMetrics.maxCost, 128); }); test('company SPU candidate realtime cost excludes discontinued SKUs but keeps retired SKU evidence', async () => { const candidates = [{ spuCode: 'SZ0097', spuName: '电脑桌', imageUrl: 'https://example.com/existing.jpg', skuCount: 14, operatingMetrics: { minCost: null, maxCost: null, material: '板材' }, }]; const rows = await hydrateCompanySpuCandidateDetails(candidates, async () => ({ source: 'getJstSkusBySpu', rows: [ { skuCode: 'SZ0097-A', saleStatus: '销售', cost: 65.5 }, { skuCode: 'SZ0097-B', saleStatus: '销售', cost: 110 }, { skuCode: 'SZ0097-C', saleStatus: '退市', cost: 58 }, { skuCode: 'SZ0097-D', saleStatus: '停产', cost: 145.5 }, ], })); assert.equal(rows[0].operatingMetrics.minCost, 58); assert.equal(rows[0].operatingMetrics.maxCost, 110); assert.equal(rows[0].operatingMetrics.realtimeSkuCount, 4); assert.equal(rows[0].operatingMetrics.realtimeCostSkuCount, 3); assert.equal(rows[0].operatingMetrics.costSource, 'getJstSkusBySpu'); assert.equal( rows[0].operatingMetrics.costMetricLabel, '聚水潭实时接口成本区间(销售/退市 SKU,停产 SKU 不计;3 条有成本)' ); }); test('company SPU candidate prefers realtime JST cost over the ERP mirror cost', async () => { const candidates = [{ spuCode: 'SZ0097', spuName: '电脑桌', imageUrl: 'https://example.com/existing.jpg', skuCount: 31, operatingMetrics: { minCost: 58, maxCost: 137, material: '板材' }, }]; const rows = await hydrateCompanySpuCandidateDetails(candidates, async () => ({ source: 'getJstSkusBySpu', rows: [ { skuCode: 'SZ0097-A', saleStatus: '销售', cost: 65.5 }, { skuCode: 'SZ0097-B', saleStatus: '销售', cost: 110 }, ], })); assert.equal(rows[0].operatingMetrics.minCost, 65.5); assert.equal(rows[0].operatingMetrics.maxCost, 110); assert.equal(rows[0].operatingMetrics.costSource, 'getJstSkusBySpu'); }); test('company SPU candidate does not present fallback snapshot cost as realtime cost', async () => { const candidates = [{ spuCode: 'SZ0097', operatingMetrics: { minCost: null, maxCost: null, material: '' }, }]; const rows = await hydrateCompanySpuCandidateDetails(candidates, async () => ({ source: 'planning_may_product_snapshot_di', rows: [{ skuCode: 'SZ0097-A', saleStatus: '销售', cost: 37.2 }], })); assert.equal(rows[0].operatingMetrics.minCost, null); assert.equal(rows[0].operatingMetrics.maxCost, null); }); test('similar-products config accepts a complete endpoint URL', () => { const originalEndpoint = process.env.SELECTION_SIMILAR_PRODUCTS_ENDPOINT_URL; process.env.SELECTION_SIMILAR_PRODUCTS_ENDPOINT_URL = 'https://qh.yydd.ai/open-api/image-search/similar-products'; try { const config = getSimilarProductsConfig(); assert.equal(config.endpointUrl, 'https://qh.yydd.ai/open-api/image-search/similar-products'); } finally { if (originalEndpoint === undefined) delete process.env.SELECTION_SIMILAR_PRODUCTS_ENDPOINT_URL; else process.env.SELECTION_SIMILAR_PRODUCTS_ENDPOINT_URL = originalEndpoint; } }); test('similar-products request sends API key and bearer compatibility headers', () => { assert.deepEqual(getSimilarProductsRequestHeaders('image-search-key'), { Authorization: 'Bearer image-search-key', 'X-API-Key': 'image-search-key', 'Content-Type': 'application/json', }); }); test('company image search treats the upstream 7701 refusal as retryable and fallback eligible', () => { const error = { response: { status: 500, data: { message: 'request to http://127.0.0.1:7701/search failed, reason: connect ECONNREFUSED 127.0.0.1:7701', }, }, }; const requestError = createSimilarProductsRequestError(error, '公司相似款查询失败'); assert.equal(requestError.code, 'SIMILAR_PRODUCTS_REQUEST_FAILED'); assert.equal(requestError.status, 500); assert.equal(requestError.retryable, true); assert.equal(isRetryableSimilarProductsError(requestError), true); assert.equal(isSimilarProductsFallbackEligible(requestError), true); }); test('company image search does not hide non-retryable authentication failures', () => { const requestError = createSimilarProductsRequestError({ response: { status: 401, data: { message: 'invalid api key' }, }, }, '公司相似款查询失败'); assert.equal(requestError.retryable, false); assert.equal(isSimilarProductsFallbackEligible(requestError), false); }); test('1688 source products keep channel, category, image and source metadata in a new plan', () => { const [item] = normalizeSelectionFissionPlanItems([{ marketItemId: '1688:620789798938', channelName: '1688', title: '翻斗鞋柜极窄多层家用门口收纳玄关柜', shopName: '常州新佰厂家居有限公司', categoryLevel1: '家装建材', categoryLevel2: '柜类', categoryLevel3: '鞋柜', mainImageUrl: '//cbu01.alicdn.com/img/ibank/sample.jpg', marketDetailUrl: 'https://detail.1688.com/offer/620789798938.html', priceBandList: ['80-100', '100-120'], sourceMetadata: { productId: '620789798938', source: 'alibaba-1688-products' }, }]); assert.equal(item.marketItemId, '1688:620789798938'); assert.equal(item.channelName, '1688'); assert.equal(item.categoryLevel2, '柜类'); assert.equal(item.categoryLevel3, '鞋柜'); assert.equal(item.mainImageUrl, 'https://cbu01.alicdn.com/img/ibank/sample.jpg'); assert.deepEqual(item.priceBandList, ['80-100', '100-120']); assert.equal(item.metadata.sourceMetadata.source, 'alibaba-1688-products'); }); test('1688 workflow keeps exact source SKU names and never invents a fallback SKU', () => { const item = { marketItemId: '1688:620789798938', title: '翻斗鞋柜', mainImageUrl: 'https://example.com/main.jpg', metadata: { sourceRow: { source: 'alibaba_1688_products', product: { skus: [{ skuId: 'SKU-RED', name: '红色 / 80cm', image: 'https://example.com/red.jpg', price: '88.50', }], }, }, }, }; const snapshot = buildAlibaba1688SkuSnapshot(item); assert.equal(snapshot.rows.length, 1); assert.equal(snapshot.rows[0].skuCode, 'SKU-RED'); assert.equal(snapshot.rows[0].skuName, '红色 / 80cm'); assert.equal(snapshot.rows[0].cost, 88.5); const emptySnapshot = buildAlibaba1688SkuSnapshot({ ...item, metadata: { sourceRow: { source: 'alibaba_1688_products', product: { skus: [] } } }, }); assert.deepEqual(emptySnapshot.rows, []); }); test('1688 workflow uses the SKU row price when the page total lags behind', () => { const item = { marketItemId: '1688:620789798938', title: '实木电脑桌', metadata: { sourceRow: { product: { skus: [{ name: '原木色 / 140cm', price: 427.18, priceText: '商品价格:¥427.18', rowPriceText: '¥603.64', }], }, }, }, }; const snapshot = buildAlibaba1688SkuSnapshot(item); assert.equal(snapshot.rows[0].cost, 603.64); assert.equal(snapshot.rows[0].purchasePrice, 603.64); }); test('1688 workflow reuses the canonical global encoding by source SKU key', () => { const sourceSkus = [ { index: 1, skuId: 'SOURCE-RED', name: 'Red / 80cm', specs: [{ name: 'Color', value: 'Red' }, { name: 'Size', value: '80cm' }], price: 80, }, { index: 2, skuId: 'SOURCE-BLUE', name: 'Blue / 100cm', specs: [{ name: 'Color', value: 'Blue' }, { name: 'Size', value: '100cm' }], price: 100, }, ]; const item = { workflowProfile: 'alibaba_1688_pending', marketItemId: '1688:1073252781818', title: 'Source table', metadata: { sourceRow: { source: 'alibaba_1688_products', product: { skus: sourceSkus }, }, }, }; const fingerprint = buildAlibaba1688SkuFingerprint(sourceSkus); const encoding = { productId: '1073252781818', status: 'encoded', styleCode: 'STYLE-001', sourceSkuCount: 2, sourceSkuFingerprint: fingerprint, request: { operationCenter: 'Center A', categoryName: 'Furniture', taxStatus: 'Taxed', invoiceType: 'Normal', installationService: 'None', installationMode: 'Assembly', }, response: { created: true }, encodedAt: '2026-08-19T10:00:00.000Z', skuMappings: [ { sourceSkuKey: buildAlibaba1688SkuKey(sourceSkus[1], 1), sourceSkuIndex: 2, productName: 'Blue table', colorSpec: 'Blue;100cm', productCode: 'GLOBAL-SKU-02', }, { sourceSkuKey: buildAlibaba1688SkuKey(sourceSkus[0], 0), sourceSkuIndex: 1, productName: 'Red table', colorSpec: 'Red;80cm', productCode: 'GLOBAL-SKU-01', }, ], }; const resolved = resolveAlibaba1688GlobalEncoding(item, encoding); assert.deepEqual( resolved.skuMappings.map((mapping) => mapping.productCode), ['GLOBAL-SKU-01', 'GLOBAL-SKU-02'] ); const snapshot = applyAlibaba1688GlobalEncodingToSnapshot( buildAlibaba1688SkuSnapshot(item), resolved ); assert.deepEqual(snapshot.rows[0].specs, [ { name: 'Color', value: 'Red' }, { name: 'Size', value: '80cm' }, ]); assert.deepEqual(snapshot.rows.map((row) => ({ skuCode: row.skuCode, internalProductCode: row.internalProductCode, })), [ { skuCode: 'SOURCE-RED', internalProductCode: 'GLOBAL-SKU-01' }, { skuCode: 'SOURCE-BLUE', internalProductCode: 'GLOBAL-SKU-02' }, ]); const state = buildAlibaba1688GlobalEncodingState(resolved); assert.equal(state.status, 'completed'); assert.equal(state.styleCode, 'STYLE-001'); assert.equal(state.requestHash.length, 64); assert.equal(state.config.operationCenter, 'Center A'); }); test('1688 workflow rejects stale or incomplete global encoding mappings', () => { const sourceSkus = [{ index: 1, name: 'Red / 80cm', specs: [{ name: 'Size', value: '80cm' }], }]; const item = { workflowProfile: 'alibaba_1688_pending', marketItemId: '1073252781818', metadata: { sourceRow: { product: { skus: sourceSkus } } }, }; const baseEncoding = { productId: '1073252781818', status: 'encoded', sourceSkuCount: 1, sourceSkuFingerprint: buildAlibaba1688SkuFingerprint(sourceSkus), skuMappings: [{ sourceSkuKey: buildAlibaba1688SkuKey(sourceSkus[0], 0), productCode: 'GLOBAL-SKU-01', }], }; assert.throws( () => resolveAlibaba1688GlobalEncoding(item, { ...baseEncoding, sourceSkuFingerprint: 'changed-fingerprint', }), (error) => error.code === 'ALIBABA_1688_PRODUCT_ENCODING_STALE' ); assert.throws( () => resolveAlibaba1688GlobalEncoding(item, { ...baseEncoding, skuMappings: [], }), (error) => error.code === 'ALIBABA_1688_PRODUCT_ENCODING_MAPPING_INCOMPLETE' ); assert.throws( () => resolveAlibaba1688GlobalEncoding(item, null), (error) => error.code === 'ALIBABA_1688_PRODUCT_NOT_ENCODED' ); }); test('1688 workflow exposes source names and titles without changing ordinary workflow rules', () => { const aiTitles = ['AI标题1', 'AI标题2', 'AI标题3', 'AI标题4', 'AI标题5']; const alibabaItem = { title: '1688原始标题', workflowProfile: 'alibaba_1688_pending', optimizedTitleCandidates: aiTitles, }; const ordinaryItem = { title: '市场来源标题', optimizedTitleCandidates: aiTitles, }; assert.deepEqual(getWorkflowProductTitleOptions(alibabaItem), ['1688原始标题', ...aiTitles]); assert.deepEqual(getWorkflowProductTitleOptions(ordinaryItem), aiTitles); assert.equal(resolvePreservedSkuNameSelection({ originalSkuName: '1688原始SKU名', names: ['AI SKU 1', 'AI SKU 2'], }, '1688原始SKU名'), '1688原始SKU名'); assert.equal(resolvePreservedSkuNameSelection({ originalSkuName: '1688原始SKU名', names: ['AI SKU 1', 'AI SKU 2'], }, '旧的无效名称'), ''); }); test('1688 source readiness requires real title, images, SKUs and purchase prices', () => { const completeItem = { workflowProfile: 'alibaba_1688_pending', title: '1688原始商品标题', mainImageUrl: 'https://example.com/main.jpg', metadata: { sourceRow: { product: { sourceUrl: 'https://detail.1688.com/offer/123456.html', storeName: '源头工厂', mainImage: 'https://example.com/main.jpg', detailImages: ['https://example.com/detail-1.jpg'], skus: [{ skuId: 'SKU-A', name: '白色 / 80cm', image: 'https://example.com/sku-a.jpg', price: '88.50', }], }, }, }, }; const complete = buildAlibaba1688SourceReadiness(completeItem); assert.equal(complete.complete, true); assert.equal(complete.facts.detailImageCount, 1); assert.equal(complete.facts.pricedSkuCount, 1); const incomplete = buildAlibaba1688SourceReadiness({ workflowProfile: 'alibaba_1688_pending', metadata: { sourceRow: { product: { skus: [{ skuId: 'SKU-A', name: '白色' }] } } }, }); assert.equal(incomplete.complete, false); assert.deepEqual( incomplete.blockingIssues.map((row) => row.field), ['title', 'mainImages', 'detailImages', 'skuPrices'] ); }); test('1688 pricing evidence preserves missing freight and stock instead of inventing values', () => { const evidence = buildAlibaba1688PricingEvidence({ workflowProfile: 'alibaba_1688_pending', metadata: { sourceRow: { product: { priceText: '88.50-99.00', minPrice: 88.5, maxPrice: 99, skus: [{ skuId: 'SKU-A', name: '白色', price: '88.50' }], }, }, }, }); assert.equal(evidence.freightEvidenceCount, 0); assert.equal(evidence.stockEvidenceCount, 0); assert.equal(evidence.rows[0].purchasePrice, 88.5); assert.equal(evidence.rows[0].freight, null); assert.equal(evidence.rows[0].stock, null); }); test('1688 pricing SQL values preserve missing evidence as database NULL', () => { assert.deepEqual( normalizeSqlBindValues(['SKU-A', undefined, NaN, Infinity, 0, null]), ['SKU-A', null, null, null, 0, null] ); assert.equal(resolveMinimumPrice({ skuCode: 'SKU-A' }, { minimumPricesBySku: {} }), null); }); test('selection pricing updates all SKU rows in one database statement', async () => { const calls = []; const connection = { execute: async (sql, values) => calls.push({ sql, values }), }; await batchUpdateSelectionPricingRows( connection, 'pricing_rows', 88, [ { skuCode: 'SKU-A', price: 99 }, { skuCode: 'SKU-B', price: 109 }, ], [['group_price', (row) => row.price]], ['updated_at = NOW()'] ); assert.equal(calls.length, 1); assert.match(calls[0].sql, /CASE UPPER\(sku_code\)/); assert.match(calls[0].sql, /UPPER\(sku_code\) IN \(\?, \?\)/); assert.deepEqual(calls[0].values, ['SKU-A', 99, 'SKU-B', 109, 88, 'SKU-A', 'SKU-B']); }); test('pricing rows are inserted in bounded batches with normalized bind values', async () => { const calls = []; const connection = { execute: async (sql, values) => calls.push({ sql, values }), }; const rows = Array.from({ length: 53 }, (_, index) => [index + 1, undefined]); await insertRowsInBatches(connection, 'pricing_rows', ['id', 'optional_value'], rows, 25); assert.equal(calls.length, 3); assert.equal(calls[0].values.length, 50); assert.equal(calls[1].values.length, 50); assert.equal(calls[2].values.length, 6); assert.ok(calls.every((call) => !call.values.includes(undefined))); assert.equal(calls[0].values[1], null); }); test('1688 listing compliance blocks unconfirmed operational decisions', () => { const compliance = buildAlibaba1688ListingCompliance({ workflowProfile: 'alibaba_1688_pending', title: '1688原始标题', mainImageUrl: 'https://example.com/main.jpg', metadata: { sourceRow: { product: { detailImages: ['https://example.com/detail.jpg'], skus: [{ skuId: 'SKU-A', name: '白色', price: '88.50' }], }, }, }, skuSnapshot: { rows: [{ skuCode: 'SKU-A', skuName: '白色', purchasePrice: 88.5 }], }, }, {}); assert.equal(compliance.complete, false); assert.ok(compliance.blockers.some((row) => row.field === 'selectedProductTitle')); assert.ok(compliance.blockers.some((row) => row.field === 'pricing')); assert.ok(compliance.blockers.some((row) => row.field === 'mainImages')); }); test('company candidate supplementation prefers level 3 and falls back to level 2', () => { assert.deepEqual(getCompanyCategoryTerms({ level2: '柜类', level3: '餐边柜' }), ['餐边柜', '柜类', '柜']); assert.deepEqual(getCompanyCategoryTerms({ level2: '柜类', level3: '柜类' }), ['柜类', '柜']); }); test('workflow keeps only user-selected SKUs for naming and pricing nodes', () => { const task = { skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuName: 'A' }, { skuCode: 'SKU-B', skuName: 'B' }, { skuCode: 'SKU-C', skuName: 'C' }, ], }, skuSceneImages: { selectedSkuCodes: ['SKU-A', 'SKU-C'], rows: [ { skuCode: 'SKU-A', generatedImageUrl: 'https://example.com/a.jpg' }, { skuCode: 'SKU-C', generatedImageUrl: 'https://example.com/c.jpg' }, ], }, skuNameCandidates: { rows: [ { skuCode: 'SKU-A', names: ['A1', 'A2', 'A3', 'A4', 'A5'], selectedName: 'A1' }, { skuCode: 'SKU-C', names: ['C1', 'C2', 'C3', 'C4', 'C5'], selectedName: 'C1' }, ], }, pricingRows: [{ skuCode: 'SKU-A', groupPrice: 100 }, { skuCode: 'SKU-C', groupPrice: 120 }], activityPricingRows: [{ skuCode: 'SKU-A' }, { skuCode: 'SKU-C' }], pricingTableSavedAt: '2026-08-02 10:00:00', }; assert.deepEqual(getWorkflowSelectedSkuCodes(task), ['SKU-A', 'SKU-C']); assert.deepEqual(getWorkflowSelectedSkuRows(task).map((row) => row.skuCode), ['SKU-A', 'SKU-C']); assert.equal(isSelectionWorkflowTaskStepComplete(task, 6), true); assert.equal(isSelectionWorkflowTaskStepComplete(task, 8), true); }); test('bulk SKU-name selection applies the same candidate position to every selected SKU', () => { const task = { skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuName: '原始名称A' }, { skuCode: 'SKU-B', skuName: '原始名称B' }, { skuCode: 'SKU-C', skuName: '原始名称C' }, ], }, skuSceneImages: { selectedSkuCodes: ['SKU-A', 'SKU-C'] }, skuNameCandidates: { rows: [ { skuCode: 'SKU-A', originalSkuName: '原始名称A', names: ['推荐A1', '推荐A2'] }, { skuCode: 'SKU-B', originalSkuName: '原始名称B', names: ['推荐B1', '推荐B2'] }, { skuCode: 'SKU-C', originalSkuName: '原始名称C', names: ['推荐C1', '推荐C2'] }, ], }, }; assert.deepEqual(buildBulkSkuNameSelections(task, 'candidate_2_all'), [ { skuCode: 'SKU-A', selectedName: '推荐A2' }, { skuCode: 'SKU-C', selectedName: '推荐C2' }, ]); assert.deepEqual(buildBulkSkuNameSelections(task, 'original_all'), [ { skuCode: 'SKU-A', selectedName: '原始名称A' }, { skuCode: 'SKU-C', selectedName: '原始名称C' }, ]); }); test('company SKU snapshots are accepted only for the task current SPU', () => { assert.equal(isSelectionFissionSpuCurrent({ selectedSpuCode: 'DNZ0324' }, 'dnz0324'), true); assert.equal(isSelectionFissionSpuCurrent({ selectedSpuCode: 'DNZ0247' }, 'DNZ0324'), false); assert.equal(isSelectionFissionSpuCurrent({ selectedSpuCode: '' }, 'DNZ0324'), false); }); test('workflow SKU matching is case-insensitive after an upstream refresh', () => { const task = { skuSnapshot: { rows: [ { skuCode: 'DNZ015401N79V02', skuName: 'legacy selected' }, { skuCode: 'DNZ015499N79V02', skuName: 'legacy unselected' }, ], }, skuSceneImages: { selectedSkuCodes: ['dnz015401n79v02', 'DNZ0154100N79V01'], }, }; const refreshed = { spuCode: 'DNZ0154', rows: [{ skuCode: 'dnz0154100n79v01', skuName: 'current' }], source: 'getJstSkusBySpu', warnings: [], }; const merged = mergeSelectedSkuRowsWithLegacySnapshot(task, refreshed); assert.deepEqual(merged.rows.map((row) => row.skuCode), [ 'dnz0154100n79v01', 'DNZ015401N79V02', ]); assert.equal(merged.source, 'getJstSkusBySpu+legacy-selected'); assert.equal(merged.total, 2); assert.equal(getWorkflowSelectedSkuRows(task, merged.rows).length, 2); assert.match(merged.warnings[0], /retained from the previous snapshot/); }); test('title strategy keywords are deduplicated and matched to demand evidence', () => { const keywords = collectTitleStrategyKeywords({ category: '餐边柜', scenes: ['客厅', '客厅'], hotKeywords: ['餐边柜'], material: '实木', }); assert.deepEqual(keywords.map((row) => row.keyword), ['餐边柜', '实木', '客厅']); const matched = matchTitleKeywordEvidence('客厅', [ { searchKeyword: '客厅餐边柜', searchIndex: 500 }, { searchKeyword: '客厅', searchIndex: 200 }, ]); assert.equal(matched.candidate, '客厅'); assert.equal(matched.score, 3); }); test('market visual references reject zero-score and broad-category matches', () => { const candidates = filterStrictMarketReferenceCandidates([ { marketItemId: 'sideboard', score: 0.83, categoryMatchLevel: 3 }, { marketItemId: 'shoe-rack', score: 0.91, categoryMatchLevel: 2 }, { marketItemId: 'side-table', score: 0, categoryMatchLevel: 3 }, ], { level2: '柜类', level3: '餐边柜', }); assert.deepEqual(candidates.map((candidate) => candidate.marketItemId), ['sideboard']); }); test('market references reject obvious cross-category image-search results', () => { const categoryConstraint = { level2: '桌面收纳', level3: '桌面扩展板', }; assert.equal(hasObviousMarketCategoryConflict({ title: '多层入户鞋架家用收纳架', category: '住宅家具 / 架类', }, categoryConstraint), true); assert.equal(hasObviousMarketCategoryConflict({ title: '电脑桌面扩展板显示器增高架', category: '办公家具 / 桌面收纳 / 桌面扩展板', }, categoryConstraint), false); }); test('market reference supplementation matches a source level-1 category against stored level-3 rows', async () => { const originalQuery = planningDatabase.query; let supplementSql = ''; let supplementParams = []; planningDatabase.query = async (sql, params) => { if (String(sql).includes('WHERE main_image_url = ?')) { return [{ embeddingVectorJson: JSON.stringify([1, 0]) }]; } supplementSql = String(sql); supplementParams = params; return [{ marketItemId: 'market-电脑桌-1', sourceChannel: 'taobao', title: '电脑桌办公桌', imageUrl: 'https://img.example.com/desk.jpg', categoryLevel3: '电脑桌', embeddingVectorJson: JSON.stringify([1, 0]), }]; }; try { const candidates = await supplementMarketReferenceCandidates( [], { level1: '电脑桌' }, 100, 'https://img.example.com/source.jpg' ); assert.equal(candidates.length, 1); assert.equal(candidates[0].marketItemId, 'market-电脑桌-1'); assert.match(supplementSql, /category_level1 = \? OR category_level2 = \? OR category_level3 = \?/); assert.deepEqual(supplementParams.slice(0, 3), ['电脑桌', '电脑桌', '电脑桌']); assert.ok(Number(supplementParams[3]) >= 100); } finally { planningDatabase.query = originalQuery; } }); test('selection task stays processing until a callback product ID exists', () => { const plan = mapPlan({ id: 12, scheme_code: 'FB202607231431A1B2C3', status: 'listed', auto_listed_product_id: null, }); assert.equal(plan.status, 'pending'); assert.equal(plan.autoListingStatus, '自动上架中'); assert.equal(plan.autoListingStatusCode, 'processing'); assert.equal(plan.autoListedProductId, ''); }); test('SG0088 JST SKU payload maps code, image, cost and minimum price', () => { const row = mapJstCompanySkuRow({ i_id: 'SG0088', sku_id: 'SG008830N1049V01', name: '款式一 100/30/180 北美原木纹色', category: '住宅家具 - 书柜', cost_price: 151, pic: 'https://img.example.com/SG008830N1049V01.jpg', status: '销售', stock: 55, stockWarehouses: [{ warehouseId: 15038347, warehouseName: '简尚-新苏北仓', warehouseType: '成品发货仓-真实库存', stock: 55, }], properties_value: '北美原木纹色', nonOwnerLimitPrice: 302.596, limitPriceTable: 'sku_bulk_price_limt', }); assert.equal(row.spuCode, 'SG0088'); assert.equal(row.skuCode, 'SG008830N1049V01'); assert.equal(row.skuImageUrl, 'https://img.example.com/SG008830N1049V01.jpg'); assert.equal(row.cost, 151); assert.equal(row.minimumPrice, 302.596); assert.equal(row.saleStatus, '销售'); assert.equal(row.stock, 55); assert.deepEqual(row.stockWarehouses, [{ warehouseId: '15038347', warehouseName: '简尚-新苏北仓', warehouseType: '成品发货仓-真实库存', stock: 55, }]); assert.equal(row.sourceSystem, 'getJstSkusBySpu'); }); test('SKU scene generation splits all selected rows into batches of five', () => { const rows = Array.from({ length: 12 }, (_, index) => ({ skuCode: `SKU-${index + 1}` })); const batches = splitIntoBatches(rows); assert.deepEqual(batches.map((batch) => batch.length), [5, 5, 2]); assert.deepEqual(batches.flat().map((row) => row.skuCode), rows.map((row) => row.skuCode)); }); test('SKU scene retry preserves completed rows and resets only failed targets', () => { const selectedSkuRows = [ { skuCode: 'SKU-A' }, { skuCode: 'SKU-B' }, { skuCode: 'SKU-C' }, ]; const task = { skuSnapshot: { rows: selectedSkuRows.map((row) => ({ ...row, skuImageUrl: `https://example.com/${row.skuCode}.jpg` })), }, skuSceneImages: { mode: 'source_original', rows: [ createVerifiedSkuImageRow({ skuCode: 'SKU-A', sourceImageUrl: 'https://example.com/SKU-A.jpg', generatedImageUrl: 'https://example.com/generated-a.jpg', }), createVerifiedSkuImageRow({ skuCode: 'SKU-B', sourceImageUrl: 'https://example.com/SKU-B.jpg', generatedImageUrl: '', status: 'failed', error: '接口超时', }), createVerifiedSkuImageRow({ skuCode: 'SKU-C', sourceImageUrl: 'https://example.com/SKU-C.jpg', generatedImageUrl: '', status: 'failed', error: '接口限流', }), ], }, }; const rows = prepareSkuSceneImageRows(task, selectedSkuRows, ['SKU-B']); assert.equal(rows[0].generatedImageUrl, 'https://example.com/generated-a.jpg'); assert.equal(rows[0].status, 'completed'); assert.equal(rows[1].status, 'waiting'); assert.equal(rows[1].progress, 0); assert.equal(rows[1].error, ''); assert.equal(rows[2].status, 'failed'); assert.equal(rows[2].error, '接口限流'); assert.deepEqual(summarizeSkuSceneImageRows(rows), { total: 3, completedCount: 1, failedCount: 1, processedCount: 2, progress: 67, }); }); test('SKU scene completion checks every selected SKU instead of the first five', () => { const selectedSkuCodes = Array.from({ length: 12 }, (_, index) => `SKU-${index + 1}`); const task = { skuSnapshot: { rows: selectedSkuCodes.map((skuCode) => ({ skuCode, skuImageUrl: `https://example.com/source-${skuCode}.jpg`, })), }, skuSceneImages: { mode: 'source_original', selectedSkuCodes, rows: selectedSkuCodes.map((skuCode) => createVerifiedSkuImageRow({ skuCode, sourceImageUrl: `https://example.com/source-${skuCode}.jpg`, generatedImageUrl: `https://example.com/${skuCode}.jpg`, })), }, }; assert.equal(isSelectionWorkflowTaskStepComplete(task, 4), true); task.skuSceneImages.rows[10].generatedImageUrl = ''; assert.equal(isSelectionWorkflowTaskStepComplete(task, 4), false); }); test('SKU scene completion respects the user selected subset', () => { const task = { skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuImageUrl: 'https://example.com/a.jpg' }, { skuCode: 'SKU-B', skuImageUrl: 'https://example.com/b.jpg' }, ], }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: ['SKU-A'], rows: [createVerifiedSkuImageRow({ skuCode: 'SKU-A', sourceImageUrl: 'https://example.com/a.jpg', generatedImageUrl: 'https://example.com/generated-a.jpg', })], }, }; assert.equal(isSelectionWorkflowTaskStepComplete(task, 4), true); task.skuSceneImages.rows.push({ ...createVerifiedSkuImageRow({ skuCode: 'SKU-B', sourceImageUrl: 'https://example.com/b.jpg', generatedImageUrl: 'https://example.com/generated-b.jpg', }), }); assert.equal(isSelectionWorkflowTaskStepComplete(task, 4), true); }); test('reference titles expose copied brand-model terms for title filtering', () => { const forbiddenTerms = extractReferenceBrandTerms([ { title: '钛载S3电脑桌家用卧室台式书桌', brandName: '' }, { title: '钛载S3电竞桌机械臂游戏桌子', brandName: '' }, { title: '林氏木业电脑桌现代简约书桌', brandName: '林氏木业' }, ], { categoryTerms: ['电脑桌', '电竞桌'], }); assert.ok(forbiddenTerms.includes('钛载S3')); assert.ok(forbiddenTerms.includes('林氏木业')); assert.equal(findForbiddenTitleBrandTerm('钛载S3电脑桌家用简约', forbiddenTerms), '钛载S3'); assert.equal(findForbiddenTitleBrandTerm('现代简约家用电脑桌', forbiddenTerms), ''); }); test('title filtering uses only an adopted, category-matched market reference', () => { const selectedMarketReferences = [{ title: '钛载S3电竞桌家用书桌', brandName: '钛载S3', categoryMatchLevel: 3, }]; const marketReferenceBatch = { candidates: [{ title: '林氏木业扫地机器人柜', categoryMatchLevel: 0 }], }; assert.deepEqual(getTaskForbiddenBrandTerms({ mainImageGeneration: { mode: 'source_original' }, titleKeywordStrategy: { forbiddenBrandTerms: ['旧错类品牌'], referenceMode: 'market_composite', }, selectedMarketReferences, marketReferenceBatch, categoryLevel3: '电脑桌', }), []); const adoptedTerms = getTaskForbiddenBrandTerms({ mainImageGeneration: { mode: 'market_composite' }, titleKeywordStrategy: { forbiddenBrandTerms: ['已确认参考品牌'], referenceMode: 'market_composite', }, selectedMarketReferences, marketReferenceBatch, categoryLevel3: '电脑桌', }); assert.ok(adoptedTerms.includes('钛载S3')); assert.ok(adoptedTerms.includes('已确认参考品牌')); assert.equal(adoptedTerms.includes('林氏木业'), false); }); test('legacy title strategy is hidden when it does not match the current main-image mode', () => { const staleStrategy = { category: '收纳柜', forbiddenBrandTerms: ['中古风实木扫地机器人'], referenceMode: 'market_composite', }; assert.equal(getCurrentTitleKeywordStrategy({ mainImageGeneration: { mode: 'source_original' }, titleKeywordStrategy: staleStrategy, }), null); assert.deepEqual(getCurrentTitleKeywordStrategy({ mainImageGeneration: { mode: 'market_composite' }, titleKeywordStrategy: staleStrategy, }), staleStrategy); }); test('main image target count follows the current reference selection before historical output', () => { const task = { workflowProfile: 'alibaba_1688_pending', selectedMarketReferences: [ { id: 'reference-1', imageUrl: 'https://example.com/reference-1.jpg' }, ], mainImageGeneration: { total: 10, rows: Array.from({ length: 10 }, (_, index) => ({ imageUrl: `https://example.com/generated-${index + 1}.jpg`, })), }, }; assert.equal(getConfiguredMainImageCount(task, 10), 1); assert.equal(isSelectionWorkflowTaskStepComplete(task, 3), false); }); test('market-composite main images require explicit structure review before advancing', () => { const task = { selectedMarketReferences: [{ id: 'reference-1', imageUrl: 'https://example.com/reference.jpg' }], mainImageGeneration: { mode: 'market_composite', total: 1, rows: [{ imageUrl: 'https://example.com/generated.jpg', qualityReviewRequired: true, qualityReviewStatus: 'pending', }], }, }; assert.equal(isSelectionWorkflowTaskStepComplete(task, 3), false); assert.equal(isSelectionWorkflowTaskStepComplete({ ...task, mainImageGeneration: { ...task.mainImageGeneration, rows: [{ ...task.mainImageGeneration.rows[0], qualityReviewStatus: 'approved', }], }, }, 3), true); assert.equal(isSelectionWorkflowTaskStepComplete({ ...task, mainImageGeneration: { mode: 'clean_only', total: 1, rows: [{ imageUrl: 'https://example.com/cleaned.jpg', qualityReviewRequired: true, qualityReviewStatus: 'pending', }], }, }, 3), false); }); test('AI-generated SKU images require explicit per-SKU structure review before naming', () => { const sourceImageUrl = 'https://example.com/sku-source.jpg'; const marketReferenceImageUrl = 'https://example.com/reference.jpg'; const generatedRow = { ...createVerifiedSkuImageRow({ skuCode: 'SKU-30', skuName: '30cm 三层置物架', sourceImageUrl, generatedImageUrl: 'https://example.com/sku-generated.jpg', mode: 'market_composite', marketReferenceImageUrl, }), qualityReviewRequired: true, qualityReviewStatus: 'pending', }; const task = { selectedSpuCode: 'SPU-RACK', skuSnapshot: { rows: [{ skuCode: 'SKU-30', skuName: '30cm 三层置物架', skuImageUrl: sourceImageUrl }], }, skuSceneImages: { mode: 'market_composite', marketReference: { imageUrl: marketReferenceImageUrl }, selectedSkuCodes: ['SKU-30'], rows: [generatedRow], }, }; assert.equal(isSelectionWorkflowTaskStepComplete(task, 4), false); assert.equal(isSelectionWorkflowTaskStepComplete({ ...task, skuSceneImages: { ...task.skuSceneImages, rows: [{ ...generatedRow, qualityReviewStatus: 'approved' }], }, }, 4), true); }); test('JST SKU query filters inactive barcodes and preserves upstream order when included', async () => { const originalGet = axios.get; axios.get = async (url, config) => { assert.match(url, /\/JS\/oa\/getJstSkusBySpu$/); assert.equal(config.params.spu, 'SG0088'); return { data: { code: 200000, result: [ { i_id: 'SG0088', sku_id: 'SKU-SALE', status: '销售', stock: 0 }, { i_id: 'SG0088', sku_id: 'SKU-RETIRED', status: '退市', stock: 7 }, { i_id: 'SG0088', sku_id: 'SKU-DISCONTINUED', status: '停产', stock: null }, ], }, }; }; try { const listingRows = await fetchCompanySkusFromJstApi('SG0088', 300); const calculatorRows = await fetchCompanySkusFromJstApi('SG0088', 300, { includeInactive: true, }); assert.deepEqual(listingRows.map((row) => row.skuCode), ['SKU-SALE']); assert.deepEqual(calculatorRows.map((row) => row.skuCode), [ 'SKU-SALE', 'SKU-RETIRED', 'SKU-DISCONTINUED', ]); assert.equal(calculatorRows[0].stock, 0); assert.equal(calculatorRows[2].stock, null); } finally { axios.get = originalGet; } }); test('JST SKU image remains selectable without the supplemental material API', () => { const assets = buildSkuSelectionAssetsFromSnapshot({ spuCode: 'BJ0179', source: 'getJstSkusBySpu', warnings: [], rows: [{ skuCode: 'BJ017902N160V01', skuName: '款式一 70/30/(60-75)', skuImageUrl: 'https://static.jiansun.vip/Resource/SkuImg/BJ017902N160V01.jpg', cost: 193, minimumPrice: 380.366, sourceSystem: 'getJstSkusBySpu', }], }); assert.equal(assets.total, 1); assert.equal(assets.rows[0].previewImage.previewUrl, 'https://static.jiansun.vip/Resource/SkuImg/BJ017902N160V01.jpg'); assert.equal(assets.rows[0].cost, 193); assert.equal(assets.rows[0].minimumPrice, 380.366); assert.equal(assets.source, 'getJstSkusBySpu'); }); test('material library fills only company SKUs whose original image is missing', () => { const assets = buildSkuSelectionAssetsFromSnapshot({ spuCode: 'SG0088', source: 'getJstSkusBySpu', rows: [ { skuCode: 'SKU-ORIGINAL', skuName: '有公司图', skuImageUrl: 'https://company.example.com/original.jpg', }, { skuCode: 'SKU-MISSING', skuName: '公司图缺失', skuImageUrl: '', }, ], }, { rows: [ { skuCode: 'SKU-ORIGINAL', previewImage: { previewUrl: 'https://material.example.com/original-fallback.webp' }, }, { skuCode: 'SKU-MISSING', previewImage: { previewUrl: 'https://material.example.com/missing-fallback.webp' }, }, ], }); assert.equal(assets.rows[0].previewImage.previewUrl, 'https://company.example.com/original.jpg'); assert.equal(assets.rows[0].selectedImageSource, 'company-sku'); assert.equal(assets.rows[1].previewImage.previewUrl, 'https://material.example.com/missing-fallback.webp'); assert.equal(assets.rows[1].selectedImageSource, 'material-library-auto'); }); test('SKU material detail response keeps fixed and other images with stable keys', () => { const assets = normalizeWhiteBackgroundSkuAssetDetails({ success: true, data: { sku: 'sg008830n1049v01', whiteBackground: { fixed: [ { key: 'front', label: '正面', previewUrl: 'https://material.example.com/front.webp' }, ], other: [ { key: 'other-1', label: '细节', previewUrl: 'https://material.example.com/detail.webp' }, { key: 'duplicate', label: '重复图', previewUrl: 'https://material.example.com/front.webp' }, ], }, }, }); assert.equal(assets.skuCode, 'SG008830N1049V01'); assert.deepEqual(assets.rows.map((row) => row.assetKey), ['fixed:front', 'other:other-1']); assert.equal(assets.total, 2); }); test('manually selected material image drives SKU scene generation source', () => { const imageUrl = getSkuSelectionImage({ skuSnapshot: { rows: [{ skuCode: 'SKU-1', skuImageUrl: 'https://company.example.com/sku.jpg' }], }, whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', selectedImageSource: 'material-library-manual', previewImage: { previewUrl: 'https://material.example.com/selected.webp' }, }], }, }, 'SKU-1'); assert.equal(imageUrl, 'https://material.example.com/selected.webp'); }); test('SKU image processing keeps identical source URLs isolated by SKU code', () => { const selectedSkuRows = [ { skuCode: 'SKU-A' }, { skuCode: 'SKU-B' }, { skuCode: 'SKU-C' }, ]; const task = { skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuImageUrl: 'https://example.com/shared.jpg' }, { skuCode: 'SKU-B', skuImageUrl: 'https://example.com/shared.jpg' }, { skuCode: 'SKU-C', skuImageUrl: 'https://example.com/other.jpg' }, ], }, }; const rows = prepareSkuSceneImageRows(task, selectedSkuRows, []); const groups = groupSkuSceneRowsBySourceImage(rows); assert.equal(groups.length, 3); assert.deepEqual(groups.map((group) => group.rows.map((row) => row.skuCode)), [ ['SKU-A'], ['SKU-B'], ['SKU-C'], ]); }); test('direct SKU image mode rebinds every result to its current SKU identity', () => { const rows = finalizeSourceSkuImageRows([ { skuCode: 'sku-a', skuName: '黑色 120cm', originalImageUrl: 'http://img.example.com/a.jpg', generatedImageUrl: 'https://img.example.com/stale-b.jpg', generatedForSkuCode: 'SKU-B', status: 'processing', progress: 20, }, { skuCode: 'SKU-B', skuName: '白色 140cm', originalImageUrl: 'https://img.example.com/b.jpg', }, ]); assert.deepEqual(rows.map((row) => ({ skuCode: row.skuCode, generatedForSkuCode: row.generatedForSkuCode, generatedForSkuName: row.generatedForSkuName, generatedImageUrl: row.generatedImageUrl, status: row.status, progress: row.progress, })), [ { skuCode: 'sku-a', generatedForSkuCode: 'SKU-A', generatedForSkuName: '黑色 120cm', generatedImageUrl: 'http://img.example.com/a.jpg', status: 'completed', progress: 100, }, { skuCode: 'SKU-B', generatedForSkuCode: 'SKU-B', generatedForSkuName: '白色 140cm', generatedImageUrl: 'https://img.example.com/b.jpg', status: 'completed', progress: 100, }, ]); }); test('retrying one SKU does not reset another SKU sharing the same source URL', () => { const selectedSkuRows = [{ skuCode: 'SKU-A' }, { skuCode: 'SKU-B' }, { skuCode: 'SKU-C' }]; const task = { skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuImageUrl: 'https://example.com/shared.jpg' }, { skuCode: 'SKU-B', skuImageUrl: 'https://example.com/shared.jpg' }, { skuCode: 'SKU-C', skuImageUrl: 'https://example.com/other.jpg' }, ], }, skuSceneImages: { mode: 'source_original', rows: selectedSkuRows.map((row) => ({ skuCode: row.skuCode, skuName: row.skuCode, generationMode: 'source_original', generatedForSkuCode: row.skuCode, generatedForSkuName: row.skuCode, generatedFromImageUrl: row.skuCode === 'SKU-C' ? 'https://example.com/other.jpg' : 'https://example.com/shared.jpg', generatedImageUrl: `https://example.com/${row.skuCode}.generated.jpg`, status: 'completed', })), }, }; const rows = prepareSkuSceneImageRows(task, selectedSkuRows, ['SKU-A']); assert.equal(rows[0].status, 'waiting'); assert.equal(rows[1].status, 'completed'); assert.equal(rows[0].generatedImageUrl, ''); assert.equal(rows[1].generatedImageUrl, 'https://example.com/SKU-B.generated.jpg'); assert.equal(rows[2].status, 'completed'); }); test('changing a SKU source URL invalidates its previous generated image', () => { const task = { skuSnapshot: { rows: [{ skuCode: 'SKU-A', skuImageUrl: 'https://example.com/new.jpg' }], }, skuSceneImages: { rows: [{ skuCode: 'SKU-A', originalImageUrl: 'https://example.com/old.jpg', generatedImageUrl: 'https://example.com/old-generated.jpg', status: 'completed', }], }, }; const [row] = prepareSkuSceneImageRows(task, [{ skuCode: 'SKU-A' }], []); assert.equal(row.generatedImageUrl, ''); assert.equal(row.status, 'waiting'); assert.equal(row.sourceImageKey, 'sku:SKU-A'); }); test('legacy SKU image rows without identity metadata are invalidated', () => { const task = { skuSnapshot: { rows: [{ skuCode: 'SKU-A', skuName: '黑色款', skuImageUrl: 'https://example.com/a.jpg' }], }, skuSceneImages: { mode: 'clean_only', rows: [{ skuCode: 'SKU-A', generatedImageUrl: 'https://example.com/legacy-generated.jpg', status: 'completed', }], }, }; const [row] = prepareSkuSceneImageRows(task, task.skuSnapshot.rows, []); assert.equal(row.generatedImageUrl, ''); assert.equal(row.status, 'waiting'); }); test('changing the SKU image mode invalidates results from another mode', () => { const task = { skuSnapshot: { rows: [{ skuCode: 'SKU-A', skuName: '黑色款', skuImageUrl: 'https://example.com/a.jpg' }], }, skuSceneImages: { mode: 'clean_only', rows: [createVerifiedSkuImageRow({ skuCode: 'SKU-A', skuName: '黑色款', sourceImageUrl: 'https://example.com/a.jpg', generatedImageUrl: 'https://example.com/composite.jpg', mode: 'market_composite', marketReferenceImageUrl: 'https://example.com/reference-a.jpg', })], }, }; const [row] = prepareSkuSceneImageRows(task, task.skuSnapshot.rows, []); assert.equal(row.generatedImageUrl, ''); assert.equal(row.generationMode, 'clean_only'); assert.equal(row.status, 'waiting'); }); test('changing the shared market reference invalidates every old composite result', () => { const task = { skuSnapshot: { rows: [{ skuCode: 'SKU-A', skuName: '黑色款', skuImageUrl: 'https://example.com/a.jpg' }], }, skuSceneImages: { mode: 'market_composite', marketReference: { imageUrl: 'https://example.com/reference-b.jpg' }, rows: [createVerifiedSkuImageRow({ skuCode: 'SKU-A', skuName: '黑色款', sourceImageUrl: 'https://example.com/a.jpg', generatedImageUrl: 'https://example.com/composite.jpg', mode: 'market_composite', marketReferenceImageUrl: 'https://example.com/reference-a.jpg', })], }, }; const [row] = prepareSkuSceneImageRows(task, task.skuSnapshot.rows, []); assert.equal(row.generatedImageUrl, ''); assert.equal(row.marketReferenceImageUrl, 'https://example.com/reference-b.jpg'); assert.equal(row.status, 'waiting'); }); test('main-image generation prefers an available material-library image over a raw source image', () => { const sourceImageUrl = getMainGenerationSourceImage({ selectedMainSkuCode: 'SKU-1', selectedMainSkuImageUrl: 'https://1688.example.com/raw.jpg', skuSnapshot: { rows: [{ skuCode: 'SKU-1', skuImageUrl: 'https://1688.example.com/raw.jpg' }], }, whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', selectedAssetKey: 'original', previewImage: { previewUrl: 'https://1688.example.com/raw.jpg' }, originalPreviewImage: { previewUrl: 'https://1688.example.com/raw.jpg' }, materialLibraryPreviewImage: { previewUrl: 'https://material.example.com/clean.webp' }, }], }, }); assert.equal(sourceImageUrl, 'https://material.example.com/clean.webp'); }); test('market item lookup removes only the platform prefix', () => { assert.equal( normalizeMarketItemLookupId('douyin:3670639322496893253'), '3670639322496893253' ); assert.equal(normalizeMarketItemLookupId('944734849380'), '944734849380'); }); test('listing callback accepts claim tokens from supported client payload shapes', () => { assert.equal(normalizeClaimToken({ claimToken: 'top-level' }), 'top-level'); assert.equal(normalizeClaimToken({ taskClaimToken: 'task-alias' }), 'task-alias'); assert.equal(normalizeClaimToken({ task: { claimToken: 'nested-task' } }), 'nested-task'); assert.equal(normalizeClaimToken({ claim: { claim_token: 'nested-claim' } }), 'nested-claim'); }); test('claim token validation distinguishes a missing token from a stale token', () => { const taskRow = { id: 88, task_code: 'MOCK-SF-SG0088-V1-S01-01', claim_token_hash: hashClaimToken('active-token'), claim_expires_at: new Date(Date.now() + 60_000).toISOString(), }; assert.throws( () => assertValidClaimToken(taskRow, {}), (error) => error.status === 409 && error.data?.code === 'CLAIM_TOKEN_MISSING' && error.data?.requiresReclaim === false ); assert.throws( () => assertValidClaimToken(taskRow, { claimToken: 'stale-token' }), (error) => error.status === 409 && error.data?.code === 'CLAIM_TOKEN_MISMATCH' && error.data?.requiresReclaim === true ); assert.doesNotThrow( () => assertValidClaimToken(taskRow, { task: { claimToken: 'active-token' } }) ); assert.throws( () => assertValidClaimToken({ ...taskRow, claim_expires_at: new Date(Date.now() - 60_000).toISOString(), }, { claimToken: 'active-token' }), (error) => error.status === 409 && error.data?.code === 'CLAIM_TOKEN_EXPIRED' && error.data?.requiresReclaim === true ); }); test('delivery payload exposes all generated main images in workflow order', () => { const generatedMainImageUrls = Array.from( { length: 5 }, (_, index) => `https://assets.example.com/generated-main-${index + 1}.jpg` ); const payload = buildDeliveryPayload({ id: 27, schemeCode: 'FB202608041305F50DC6', items: [], sourceItems: [], listingTasks: [{ id: 80, taskCode: 'FB202608041305F50DC6-S01-01', slotNo: 1, variantNo: 1, marketItemId: '979560294960', mainImageUrl: 'https://assets.example.com/market.jpg', replacedImageUrl: generatedMainImageUrls[0], mainImageGeneration: { rows: generatedMainImageUrls.map((imageUrl, index) => ({ index: index + 1, imageUrl })), }, }], }); assert.deepEqual(payload.marketImages[0].generatedMainImageUrls, generatedMainImageUrls); assert.equal(payload.marketImages[0].imageUrl, generatedMainImageUrls[0]); }); test('selection fission plan accepts any source count from one to five', () => { const buildItems = (count) => Array.from({ length: count }, (_, index) => ({ channelName: '淘宝', marketItemId: `ITEM-${index + 1}`, title: `商品 ${index + 1}`, })); assert.equal(normalizeSelectionFissionPlanItems(buildItems(1)).length, 1); assert.equal(normalizeSelectionFissionPlanItems(buildItems(2)).length, 2); assert.equal(normalizeSelectionFissionPlanItems(buildItems(5)).length, 5); assert.throws(() => normalizeSelectionFissionPlanItems([]), /1~5/); assert.throws(() => normalizeSelectionFissionPlanItems(buildItems(6)), /1~5/); const [item] = normalizeSelectionFissionPlanItems([{ channelName: '淘宝', marketItemId: '10001', priceBand: '100 - 200', priceBandList: ['100 - 200', '200 - 300'], }]); assert.equal(item.priceBand, '100 - 200'); assert.deepEqual(item.priceBandList, ['100 - 200', '200 - 300']); }); test('market image search candidates keep unique products with usable images', () => { const rows = collectMarketReferenceCandidates({ data: { products: [ { platform: '淘宝', product_id: 'A-1', image_url: 'https://example.com/a.jpg', title: '参考商品 A', price_band: '200-300元', analysis_sales: '1288', }, { platform: '淘宝', product_id: 'A-1', image_url: 'https://example.com/a-copy.jpg', }, { platform: '京东', product_id: 'B-2', image_url: '', }, { platform: '京东', product_id: 'C-3', image_url: 'https://example.com/c.jpg', }, ], }, }); assert.deepEqual(rows.map((row) => row.id), ['淘宝:A-1', '京东:C-3']); assert.equal(rows[0].priceBand, '200-300元'); assert.equal(rows[0].salesVolume, '1288'); assert.equal(buildMarketProductDetailUrl(rows[0]), 'https://item.taobao.com/item.htm?id=A-1'); }); test('market references keep stored rank price bands before page-price fallback', () => { assert.equal(inferMarketChannelName('', 'jd:10083267349458'), '京东'); assert.equal(inferMarketChannelName('', '1688:1024108915351'), '1688'); assert.equal(buildMarketReferencePriceBand(2.68), '0-10'); assert.equal(buildMarketReferencePriceBand(120.84), '100-200'); assert.equal(buildMarketReferencePriceBand(268), '200-300'); assert.equal(buildMarketReferencePriceBand(3699), '2000+'); const candidate = normalizeMarketReferenceCandidate({ product_id: '10001', image_url: 'https://example.com/10001.jpg', price: 19.9, price_band: '100 - 200', priceBandList: ['100 - 200', '200 - 300'], }); assert.equal(candidate.priceBand, '100 - 200'); assert.deepEqual(candidate.priceBandList, ['100 - 200', '200 - 300']); assert.deepEqual( normalizePriceBandList('["100 - 200", "200 - 300"]', '100 - 200'), ['100 - 200', '200 - 300'] ); assert.deepEqual( sortPriceBandValues(['200 - 300', '不限', '100 - 200']), ['不限', '100 - 200', '200 - 300'] ); assert.equal( buildMarketProductDetailUrl({ marketItemId: 'jd:10083267349458', channelName: '京东' }), 'https://item.jd.com/10083267349458.html' ); }); test('Market markdown keeps market IDs, links and main images', () => { const rows = parseMarketReferenceMarkdown(` | 排名 | 市场ID | 商品标题 | 详情链接 | 主图 | 相似度 | 提示 | | ---: | --- | --- | --- | --- | ---: | --- | | 1 | 637702521867 | 黑胡桃木床头柜 | [打开](https://item.example.com/637702521867) | [主图](https://img.example.com/1.jpg) | 1.000 | - | | 2 | jd:10066646075317 | 实木收纳柜 | [打开](https://item.example.com/2) | [主图](https://img.example.com/2.jpg) | 0.925 | - | `); assert.deepEqual(rows.map((row) => row.marketItemId), [ '637702521867', 'jd:10066646075317', ]); assert.equal(rows[0].mainImageUrl, 'https://img.example.com/1.jpg'); assert.equal(rows[1].score, 0.925); }); test('company SPU candidates prioritize matching categories then similarity', () => { const rows = rankSpuCandidatesByCategory([ { spuCode: 'A', category: '住宅家具 / 桌类 / 餐桌', bestScore: 0.82 }, { spuCode: 'B', category: '住宅家具 / 柜类 / 鞋柜', bestScore: 0.99 }, { spuCode: 'C', category: '', bestScore: 0.91 }, ], { level1: '住宅家具', level2: '桌类', level3: '餐桌', }); assert.deepEqual(rows.map((row) => row.spuCode), ['A', 'C', 'B']); assert.deepEqual(rows.map((row) => row.categoryMatchLevel), [3, 0, -1]); }); test('company SPU normalization never promotes a SKU code to SPU', () => { const skuOnly = normalizeSimilarCandidate({ skuCode: '973351010702', skuName: '仅有条码的候选', }); assert.equal(skuOnly.spuCode, ''); const response = normalizeSimilarResponsePayload({ ownProductMatches: [ { skuCode: '973351010702', skuName: '仅有条码的候选' }, { productCode: '976235605909', skuCode: 'SKU-2', skuName: '待验证商品编码' }, { spuCode: 'dnz0154', skuCode: 'SKU-3', skuName: '明确SPU' }, ], }); assert.deepEqual(response.spuCandidates.map((row) => row.spuCode), [ '976235605909', 'DNZ0154', ]); }); test('company SPU validation keeps only exact snapshot or JST matches', async () => { const realtimeRequests = []; const result = await validateCompanySpuCandidates([ { spuCode: '973351010702' }, { spuCode: 'DNZ0154', skuCount: 1 }, { spuCode: 'dnz0289' }, ], { querySnapshot: async (spuCodes) => { assert.deepEqual(spuCodes, ['973351010702', 'DNZ0154', 'DNZ0289']); return [{ spuCode: 'DNZ0154', skuCount: 58 }]; }, fetchSpuSkus: async (spuCode) => { realtimeRequests.push(spuCode); if (spuCode === 'DNZ0289') { return [{ spuCode: 'DNZ0289', skuCode: 'DNZ028901' }]; } return [{ spuCode: 'OTHER-SPU', skuCode: '973351010702' }]; }, }); assert.deepEqual(result.validCandidates.map((row) => row.spuCode), ['DNZ0154', 'DNZ0289']); assert.deepEqual(result.invalidCandidates.map((row) => row.spuCode), ['973351010702']); assert.deepEqual(result.unverifiedCandidates, []); assert.deepEqual(realtimeRequests, ['973351010702', 'DNZ0289']); assert.equal(result.validCandidates[0].skuCount, 58); assert.equal(result.validCandidates[1].spuVerificationSource, 'getJstSkusBySpu'); }); test('company SPU validation does not trust client verification flags', async () => { const result = await validateCompanySpuCandidates([ { spuCode: 'FAKE-SPU', spuVerified: true }, ], { querySnapshot: async () => [], fetchSpuSkus: async () => [], }); assert.deepEqual(result.validCandidates, []); assert.deepEqual(result.invalidCandidates.map((row) => row.spuCode), ['FAKE-SPU']); }); test('company SPU validation stops after a full failed realtime batch', async () => { let requestCount = 0; const result = await validateCompanySpuCandidates( Array.from({ length: 12 }, (_, index) => ({ spuCode: `SPU-${index + 1}` })), { querySnapshot: async () => [], fetchSpuSkus: async () => { requestCount += 1; throw new Error('JST timeout'); }, } ); assert.equal(requestCount, 8); assert.equal(result.unverifiedCandidates.length, 12); assert.equal(result.warnings.length, 1); assert.match(result.warnings[0], /JST timeout/); }); test('company sales trend fills six continuous calendar months', () => { assert.deepEqual(fillMonthlySalesTrend6m([ { month: '2026-03', salesAmount: 30 }, { month: '2026-05', salesAmount: 50 }, { month: '2026-07', salesAmount: 70 }, ]), [ { month: '2026-02', salesAmount: 0 }, { month: '2026-03', salesAmount: 30 }, { month: '2026-04', salesAmount: 0 }, { month: '2026-05', salesAmount: 50 }, { month: '2026-06', salesAmount: 0 }, { month: '2026-07', salesAmount: 70 }, ]); }); test('each selection fission ID advances through its own next node', () => { assert.deepEqual( getSelectionFissionItemNodeState({}), { step: 1, action: 'initialize-source' } ); assert.deepEqual( getSelectionFissionItemNodeState({ mainImageUrl: 'https://example.com/market.jpg' }), { step: 2, action: 'match-spu' } ); assert.deepEqual( getSelectionFissionItemNodeState({ mainImageUrl: 'https://example.com/market.jpg', similarResponse: { spuCandidates: [{ spuCode: 'SPU-1' }] }, }), { step: 2, action: 'select-spu', requiresUser: true } ); assert.deepEqual( getSelectionFissionItemNodeState({ selectedSpuCode: 'SPU-1', whiteBackgroundAssets: { rows: [] }, }), { step: 3, action: 'prepare-main-sku' } ); assert.deepEqual( getSelectionFissionItemNodeState({ selectedSpuCode: 'SPU-1', whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', previewImage: { previewUrl: 'https://example.com/sku.jpg' }, }], }, }), { step: 3, action: 'select-main-sku', requiresUser: true } ); assert.deepEqual( getSelectionFissionItemNodeState({ selectedSpuCode: 'SPU-1', selectedMainSkuImageUrl: 'https://example.com/sku.jpg', whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', previewImage: { previewUrl: 'https://example.com/sku.jpg' }, }], }, }), { step: 8, action: 'pricing' } ); }); test('1688 pending items reuse generation nodes with pricing as their entry', () => { const baseItem = { metadata: { workflowProfile: 'alibaba_1688_pending' }, selectedSpuCode: '1688:123456', selectedSpuName: '1688 source product', selectedMainSkuImageUrl: 'https://example.com/source-sku.jpg', skuSnapshot: { rows: [{ skuCode: 'SKU-1', skuName: 'source sku', skuImageUrl: 'https://example.com/source-sku.jpg', }], }, whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', previewImage: { previewUrl: 'https://example.com/source-sku.jpg' }, }], }, }; assert.deepEqual( getSelectionFissionItemNodeState(baseItem), { step: 8, action: 'pricing' } ); const pricedItem = { ...baseItem, productCodeStatus: 'completed', productCodeStyleCode: 'STYLE-1688-1', pricingRows: [{ skuCode: 'SKU-1', selectedSkuName: 'source sku', internalProductCode: 'STYLE-1688-1-01', groupPrice: 100, groupPrice: 99, }], activityPricingRows: [], pricingTableSavedAt: '2026-08-15 12:00:00', }; assert.deepEqual( getSelectionFissionItemNodeState(pricedItem), { step: 3, action: 'main-image' } ); const mainImagesReadyItem = { ...pricedItem, mainImageGeneration: { rows: Array.from({ length: 10 }, (_, index) => ({ imageUrl: `https://example.com/main-${index + 1}.jpg`, })), }, }; assert.deepEqual( getSelectionFissionItemNodeState(mainImagesReadyItem), { step: 4, action: 'sku-images' } ); const skuImagesReadyItem = { ...mainImagesReadyItem, skuSceneImages: { mode: 'source_original', selectedSkuCodes: ['SKU-1'], rows: [createVerifiedSkuImageRow({ skuCode: 'SKU-1', skuName: 'source sku', sourceImageUrl: 'https://example.com/source-sku.jpg', generatedImageUrl: 'https://example.com/generated-sku.jpg', })], }, }; assert.deepEqual( getSelectionFissionItemNodeState(skuImagesReadyItem), { step: 6, action: 'sku-names' } ); const skuNamesReadyItem = { ...skuImagesReadyItem, skuNameCandidates: { rows: [{ skuCode: 'SKU-1', names: ['name 1', 'name 2', 'name 3', 'name 4', 'name 5'], selectedName: 'name 1', }], }, }; assert.deepEqual( getSelectionFissionItemNodeState(skuNamesReadyItem), { step: 7, action: 'product-title' } ); assert.deepEqual( getSelectionFissionItemNodeState({ ...skuNamesReadyItem, optimizedTitleCandidates: ['title 1', 'title 2', 'title 3', 'title 4', 'title 5'], selectedProductTitle: 'title 1', }), { step: 7, action: 'completed', completed: true } ); }); test('Company SKU markdown becomes selectable SPU candidates', () => { const rows = parseCompanySkuMarkdown([ '| 排名 | SKU | 商品/规格 | SPU/货号 | 相似度 | 链接 |', '| ---: | --- | --- | --- | ---: | --- |', '| 1 | SG008832N1049V01 | 款式一 80/30/180 炭黑色 | SG0088 | 0.667 | [图片](https://example.com/sku.jpg) |', ].join('\n')); assert.deepEqual(rows, [{ rank: 1, maySkuCode: 'SG008832N1049V01', skuCode: 'SG008832N1049V01', skuName: '款式一 80/30/180 炭黑色', productCode: 'SG0088', spuCode: 'SG0088', score: 0.667, skuImageUrl: 'https://example.com/sku.jpg', source: 'image-search', }]); }); test('image generation keeps reference images available while parsing output images', async () => { const originalPost = axios.post; const originalSystemQuery = database.local.query; const originalApiKey = process.env.SELECTION_IMAGE_GEN_API_KEY; const originalImageSize = process.env.SELECTION_IMAGE_GEN_IMAGE_SIZE; const originalImageTimeout = process.env.SELECTION_IMAGE_GEN_TIMEOUT_MS; process.env.SELECTION_IMAGE_GEN_API_KEY = 'test-image-api-key'; process.env.SELECTION_IMAGE_GEN_IMAGE_SIZE = '1K'; process.env.SELECTION_IMAGE_GEN_TIMEOUT_MS = '900000'; database.local.query = async () => []; axios.post = async (_url, payload, requestConfig) => { assert.equal(payload.imageSize, '1K'); assert.equal(payload.aspectRatio, '1:1'); assert.equal(payload.width, 800); assert.equal(payload.height, 800); assert.equal(requestConfig.timeout, 900000); assert.deepEqual(payload.referenceImages, [ 'https://example.com/market.jpg', 'https://example.com/product.jpg', ]); return { data: { data: { requestId: 'request-1', images: [{ originalUrl: 'https://example.com/generated.jpg' }], }, }, }; }; try { const result = await requestSelectionGeneratedImage({ prompt: 'replace product', referenceImages: [ 'https://example.com/market.jpg', 'https://example.com/product.jpg', ], aspectRatio: '4:5', }); assert.equal(result.imageUrl, 'https://example.com/generated.jpg'); assert.equal(result.requestId, 'request-1'); } finally { axios.post = originalPost; if (originalApiKey === undefined) { delete process.env.SELECTION_IMAGE_GEN_API_KEY; } else { process.env.SELECTION_IMAGE_GEN_API_KEY = originalApiKey; } if (originalImageSize === undefined) { delete process.env.SELECTION_IMAGE_GEN_IMAGE_SIZE; } else { process.env.SELECTION_IMAGE_GEN_IMAGE_SIZE = originalImageSize; } if (originalImageTimeout === undefined) { delete process.env.SELECTION_IMAGE_GEN_TIMEOUT_MS; } else { process.env.SELECTION_IMAGE_GEN_TIMEOUT_MS = originalImageTimeout; } } }); test('image generation settings preserve independent provider keys and allow switching to Qwen', () => { const config = normalizeImageGenerationConfig({ provider: 'qwen', external: { apiKey: '' }, qwen: { apiKey: 'new-qwen-key', model: 'qwen-image-3.0-pro', imageSize: '2K', }, }, { provider: 'external', external: { apiKey: 'existing-external-key', baseUrl: 'https://image.example.com' }, qwen: { apiKey: 'old-qwen-key' }, }); assert.equal(config.provider, 'qwen'); assert.equal(config.external.apiKey, 'existing-external-key'); assert.equal(config.qwen.apiKey, 'new-qwen-key'); assert.equal(config.qwen.model, 'qwen-image-3.0-pro'); assert.equal(config.external.imageSize, '1K'); assert.equal(config.qwen.imageSize, '1K'); }); test('Qwen image generation keeps image order, fixed 1K dimensions and persists the temporary result', async () => { let request = null; let persistedUrl = ''; const result = await requestQwenImage({ baseUrl: 'https://dashscope.aliyuncs.com', apiKey: 'qwen-test-key', model: 'qwen-image-3.0-pro', imageSize: '2K', promptExtend: true, watermark: false, timeoutMs: 900000, }, { prompt: 'place the exact product in the market scene', negativePrompt: 'changed product', aspectRatio: '4:3', referenceImages: ['https://example.com/product.jpg', 'https://example.com/scene.jpg'], }, { post: async (url, payload, config) => { request = { url, payload, config }; return { data: { request_id: 'qwen-request-1', output: { choices: [{ message: { content: [{ image: 'https://temporary.example.com/result.png' }] } }], }, usage: { image_count: 1, width: 2048, height: 1536 }, }, }; }, persistImage: async (url) => { persistedUrl = url; return 'https://cdn.example.com/permanent.png'; }, }); assert.match(request.url, /\/api\/v1\/services\/aigc\/multimodal-generation\/generation$/); assert.deepEqual(request.payload.input.messages[0].content, [ { image: 'https://example.com/product.jpg' }, { image: 'https://example.com/scene.jpg' }, { text: 'place the exact product in the market scene' }, ]); assert.equal(request.payload.parameters.size, '800*800'); assert.equal(request.payload.parameters.prompt_extend, true); assert.equal(request.config.headers.Authorization, 'Bearer qwen-test-key'); assert.equal(persistedUrl, 'https://temporary.example.com/result.png'); assert.equal(result.imageUrl, 'https://cdn.example.com/permanent.png'); assert.equal(result.provider, 'qwen'); assert.equal(resolveQwenImageDimensions('9:16', '1K'), '800*800'); }); test('Qwen disables prompt expansion for identity-locked product compositing', async () => { let request = null; await requestQwenImage({ baseUrl: 'https://dashscope.aliyuncs.com', apiKey: 'qwen-test-key', model: 'qwen-image-3.0-pro', imageSize: '1K', promptExtend: true, watermark: false, timeoutMs: 900000, }, { prompt: 'keep the exact product unchanged', negativePrompt: 'product redesign', aspectRatio: '1:1', referenceImages: ['https://example.com/product.jpg', 'https://example.com/scene.jpg'], metadata: { preserveProductIdentity: true, disablePromptExtend: true }, }, { post: async (_url, payload) => { request = payload; return { data: { request_id: 'qwen-identity-lock', output: { choices: [{ message: { content: [{ image: 'https://temporary.example.com/result.png' }] } }] }, }, }; }, persistImage: async () => 'https://cdn.example.com/identity-locked.png', }); assert.equal(request.parameters.prompt_extend, false); }); test('1688 main image generation locks product identity while adopting the market camera', async () => { const originalPost = axios.post; const originalSystemQuery = database.local.query; const originalApiKey = process.env.SELECTION_IMAGE_GEN_API_KEY; process.env.SELECTION_IMAGE_GEN_API_KEY = 'test-image-api-key'; database.local.query = async () => []; axios.post = async (_url, payload) => { assert.deepEqual(payload.referenceImages, [ 'https://example.com/target-product.jpg', 'https://example.com/market-scene.jpg', ]); assert.match(payload.prompt, /exact product-compositing edit/); assert.match(payload.prompt, /Transform the whole target product as one rigid 3D object/); assert.match(payload.prompt, /exact part counts/); assert.match(payload.prompt, /Target SKU specification: Color A \/ 1200W\*600D\*730H/); assert.match(payload.prompt, /不可重新设计、不可拆分生成的刚性三维整体/); assert.doesNotMatch(payload.prompt, /公司商品主体/); assert.match(payload.negativePrompt, /missing components/); assert.equal(payload.metadata.referenceOrder, 'target-product-first'); assert.equal(payload.metadata.preserveProductIdentity, true); assert.equal(payload.metadata.disablePromptExtend, true); return { data: { data: { requestId: 'strict-request-1', images: [{ originalUrl: 'https://example.com/strict-result.jpg' }], }, }, }; }; try { const result = await generateMainReplacementImage({ marketImageUrl: 'https://example.com/market-scene.jpg', whiteBackgroundImageUrl: 'https://example.com/target-product.jpg', marketItemId: 'MARKET-1', spuCode: '1688-1', skuCode: '1688-1-SKU-1', skuName: 'Color A / 1200W*600D*730H', aspectRatio: '1:1', strictProductIdentity: true, }); assert.equal(result.imageUrl, 'https://example.com/strict-result.jpg'); assert.equal(result.requestId, 'strict-request-1'); } finally { axios.post = originalPost; database.local.query = originalSystemQuery; database.local.query = originalSystemQuery; if (originalApiKey === undefined) { delete process.env.SELECTION_IMAGE_GEN_API_KEY; } else { process.env.SELECTION_IMAGE_GEN_API_KEY = originalApiKey; } } }); test('company main image generation uses the same strict product identity lock as 1688', async () => { const originalPost = axios.post; const originalSystemQuery = database.local.query; const originalApiKey = process.env.SELECTION_IMAGE_GEN_API_KEY; process.env.SELECTION_IMAGE_GEN_API_KEY = 'test-image-api-key'; database.local.query = async () => []; axios.post = async (_url, payload) => { assert.deepEqual(payload.referenceImages, [ 'https://example.com/company-product.jpg', 'https://example.com/market-scene.jpg', ]); assert.match(payload.prompt, /exact product-compositing edit/); assert.match(payload.prompt, /Transform the whole target product as one rigid 3D object/); assert.match(payload.prompt, /不可重新设计、不可拆分生成的刚性三维整体/); assert.match(payload.prompt, /部件精确数量/); assert.match(payload.prompt, /无条件遵守本规则/); assert.doesNotMatch(payload.prompt, /参考图1是同品类市场商品图/); assert.equal(payload.metadata.referenceOrder, 'target-product-first'); assert.equal(payload.metadata.identityLockVersion, 'rigid-product-v2'); return { data: { data: { images: [{ originalUrl: 'https://example.com/company-result.jpg' }] } } }; }; try { const result = await generateMainReplacementImage({ marketImageUrl: 'https://example.com/market-scene.jpg', whiteBackgroundImageUrl: 'https://example.com/company-product.jpg', marketItemId: 'MARKET-2', spuCode: 'SPU-2', skuCode: 'SKU-2', skuName: '双抽屉四腿原木色', aspectRatio: '1:1', marketCompositionFirst: true, strictProductIdentity: true, }); assert.equal(result.imageUrl, 'https://example.com/company-result.jpg'); } finally { axios.post = originalPost; database.local.query = originalSystemQuery; if (originalApiKey === undefined) delete process.env.SELECTION_IMAGE_GEN_API_KEY; else process.env.SELECTION_IMAGE_GEN_API_KEY = originalApiKey; } }); test('SKU scene generation preserves the exact SKU and uses the market image only as scene reference', async () => { const originalPost = axios.post; const originalSystemQuery = database.local.query; const originalApiKey = process.env.SELECTION_IMAGE_GEN_API_KEY; process.env.SELECTION_IMAGE_GEN_API_KEY = 'test-image-api-key'; database.local.query = async () => []; axios.post = async (_url, payload) => { assert.deepEqual(payload.referenceImages, [ 'https://example.com/sku-blue.jpg', 'https://example.com/market-scene.jpg', ]); assert.match(payload.prompt, /第一张图是当前SKU唯一商品身份图/); assert.match(payload.prompt, /不可重新设计、不可拆分生成的刚性三维整体/); assert.match(payload.prompt, /严禁凭常识补全.*脚轮/); assert.match(payload.prompt, /尺寸、数量、颜色、款式和配件信息必须保持一致/); assert.match(payload.prompt, /不得猜测、补画或改造商品/); assert.match(payload.prompt, /内部SKU身份编码:SKU-BLUE/); assert.match(payload.negativePrompt, /新增脚轮/); assert.match(payload.negativePrompt, /尺寸变化/); assert.equal(payload.metadata.referenceOrder, 'target-sku-first'); assert.equal(payload.metadata.targetSkuReferenceIndex, 1); assert.equal(payload.metadata.marketCompositionReferenceIndex, 2); assert.equal(payload.metadata.preserveProductIdentity, true); assert.equal(payload.metadata.disablePromptExtend, true); return { data: { data: { images: [{ originalUrl: 'https://example.com/sku-result.jpg' }] } } }; }; try { const result = await generateSkuSceneImage({ marketReferenceImageUrl: 'https://example.com/market-scene.jpg', skuImageUrl: 'https://example.com/sku-blue.jpg', skuCode: 'SKU-BLUE', skuName: '蓝色双门款', title: '市场参考标题', spuCode: 'SPU-3', aspectRatio: '1:1', }); assert.equal(result.imageUrl, 'https://example.com/sku-result.jpg'); } finally { axios.post = originalPost; database.local.query = originalSystemQuery; if (originalApiKey === undefined) delete process.env.SELECTION_IMAGE_GEN_API_KEY; else process.env.SELECTION_IMAGE_GEN_API_KEY = originalApiKey; } }); test('extensionless image references are rehosted with detected image metadata', async () => { const sourceUrl = 'https://example.com/material/image_without_extension'; const jpeg = Buffer.alloc(16); Buffer.from([0xff, 0xd8, 0xff, 0xe0]).copy(jpeg); let uploaded = null; const result = await normalizeImageGenerationReferenceUrl(sourceUrl, { fetchImage: async (url) => { assert.equal(url, sourceUrl); return { data: jpeg, headers: { 'content-type': 'application/octet-stream' }, }; }, uploadImage: async (buffer, mimeType, folder, extension) => { uploaded = { buffer, mimeType, folder, extension }; return { url: 'https://images.example.com/reference.jpg' }; }, }); assert.equal(result, 'https://images.example.com/reference.jpg'); assert.deepEqual(uploaded.buffer, jpeg); assert.equal(uploaded.mimeType, 'image/jpeg'); assert.equal(uploaded.folder, 'selection-fission/reference-images'); assert.equal(uploaded.extension, 'jpg'); assert.deepEqual(detectImageContent(jpeg, 'application/octet-stream'), { mimeType: 'image/jpeg', extension: 'jpg', }); assert.equal(hasStandardImageExtension('https://example.com/a.jpg?size=800'), true); assert.equal(hasStandardImageExtension(sourceUrl), false); assert.equal(shouldRehostImageGenerationReference('https://cbu01.alicdn.com/img/ibank/example.jpg'), true); assert.equal(shouldRehostImageGenerationReference('https://images.example.com/example.jpg'), false); }); test('image generation surfaces structured upstream errors', () => { assert.equal(getExternalRequestErrorMessage({ response: { data: { error: 'reference image format is unsupported' } }, }, 'fallback'), 'reference image format is unsupported'); }); test('image dimensions become an exact reduced aspect ratio', () => { const png = Buffer.alloc(24); Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(png, 0); png.writeUInt32BE(1200, 16); png.writeUInt32BE(1500, 20); assert.deepEqual(parseImageDimensions(png), { width: 1200, height: 1500 }); assert.equal(formatImageAspectRatio({ width: 1200, height: 1500 }), '4:5'); }); test('selection pricing timestamps are normalized for MySQL DATETIME', () => { assert.equal(toMysqlDateTime('2026-07-23T15:44:10.162Z'), '2026-07-23 15:44:10'); assert.equal(toMysqlDateTime('2026-07-23 15:44:10'), '2026-07-23 15:44:10'); assert.equal(toMysqlDateTime('not-a-date'), null); }); test('workflow resume skips only nodes whose required outputs are complete', () => { const task = { replacedImageUrl: 'https://example.com/main.jpg', skuSnapshot: { rows: Array.from({ length: 5 }, (_, index) => ({ skuCode: `SKU-${index + 1}`, skuImageUrl: `https://example.com/source-sku-${index + 1}.jpg`, })), }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: Array.from({ length: 5 }, (_, index) => `SKU-${index + 1}`), rows: Array.from({ length: 5 }, (_, index) => createVerifiedSkuImageRow({ skuCode: `SKU-${index + 1}`, sourceImageUrl: `https://example.com/source-sku-${index + 1}.jpg`, generatedImageUrl: `https://example.com/sku-${index + 1}.jpg`, })), }, skuNameCandidates: { rows: Array.from({ length: 5 }, (_, index) => ({ skuCode: `SKU-${index + 1}`, names: ['名称1', '名称2', '名称3', '名称4', '名称5'], selectedName: '名称1', })), }, optimizedTitleCandidates: ['标题1', '标题2', '标题3', '标题4', '标题5'], selectedProductTitle: '标题1', pricingRows: Array.from({ length: 5 }, (_, index) => ({ skuCode: `SKU-${index + 1}`, groupPrice: 100, })), activityPricingRows: Array.from({ length: 5 }, () => ({})), pricingTableSavedAt: '2026-08-01 15:00:00', }; for (const step of [3, 4, 5, 6, 7, 8]) { assert.equal(isSelectionWorkflowTaskStepComplete(task, step), true); } assert.equal( isSelectionWorkflowTaskStepComplete({ ...task, skuSceneImages: { rows: task.skuSceneImages.rows.slice(0, 4) }, }, 4), false ); assert.equal( isSelectionWorkflowTaskStepComplete({ ...task, skuNameCandidates: { rows: task.skuNameCandidates.rows.map((row, index) => ( index === 0 ? { ...row, selectedName: '' } : row )), }, }, 6), false ); assert.equal( isSelectionWorkflowTaskStepComplete({ ...task, selectedProductTitle: '' }, 7), false ); assert.equal( isSelectionWorkflowTaskStepComplete({ ...task, pricingTableSavedAt: null }, 8), false ); }); test('remaining workflow progress never moves backwards on resume', () => { let progress = 44; const observed = [progress]; for (const candidate of [30, 31.6, 33.2, 34.8, 36.4, 38, 38, 40.8, 43.6, 46.4, 49.2, 52]) { progress = advanceWorkflowProgress(progress, candidate); observed.push(progress); } assert.equal( observed.every((value, index) => index === 0 || value >= observed[index - 1]), true ); assert.equal(progress, 52); }); test('selection task becomes completed after a callback product ID exists', () => { const plan = mapPlan({ id: 12, scheme_code: 'FB202607231431A1B2C3', status: 'pending', auto_listed_product_id: '839201746512', auto_listing_completed_at: '2026-07-23 16:20:00', }); assert.equal(plan.status, 'listed'); assert.equal(plan.autoListingStatus, '上架完成'); assert.equal(plan.autoListingStatusCode, 'completed'); assert.equal(plan.autoListedProductId, '839201746512'); assert.equal(plan.autoListingCompletedAt, '2026-07-23 16:20:00'); }); test('auto listing callback accepts common product ID field aliases', () => { assert.equal(normalizeAutoListingProductId({ productId: 'P-1' }), 'P-1'); assert.equal(normalizeAutoListingProductId({ product_id: 'P-2' }), 'P-2'); assert.equal(normalizeAutoListingProductId({ listedItemId: 'P-3' }), 'P-3'); assert.equal(normalizeAutoListingProductId({ goodsId: 12345 }), '12345'); }); test('selection task exposes claimed and failed states without a product ID', () => { const claimed = mapPlan({ id: 13, status: 'claimed', claimed_by: 'worker-01', }); const failed = mapPlan({ id: 14, status: 'failed', auto_listing_error_code: 'PLATFORM_REJECTED', auto_listing_error_message: '平台审核拒绝', }); assert.equal(claimed.status, 'claimed'); assert.equal(claimed.autoListingStatusCode, 'processing'); assert.equal(claimed.claimedBy, 'worker-01'); assert.equal(failed.status, 'failed'); assert.equal(failed.autoListingStatus, '上架失败'); assert.equal(failed.autoListingErrorCode, 'PLATFORM_REJECTED'); }); test('selection shop sync accepts common shop aliases and validates required fields', () => { assert.deepEqual( normalizeListingShop({ shop_id: 'shop-100', shop_name: '测试旗舰店', channel_name: '拼多多', }), { shopId: 'shop-100', shopCode: '', shopName: '测试旗舰店', platform: '拼多多', accountName: '', status: 'active', clientId: '', loginStatus: 'unknown', loginMessage: '', lastLoginCheckedAt: null, } ); assert.throws(() => normalizeListingShop({ shopName: '无编号店铺' }), /缺少 shopId/); assert.equal(hashClaimToken('claim-token'), hashClaimToken('claim-token')); assert.notEqual(hashClaimToken('claim-token'), hashClaimToken('other-token')); }); test('pricing row exposes original, five optimized and selected SKU names', () => { const row = mapPricingRow({ id: 7, plan_item_id: 3, sku_code: 'SG008830N1049V01', sku_name: '款式一 100/30/180 北美原木纹色', ai_sku_name_candidates_json: JSON.stringify([ '北美原木纹100cm书柜', '原木纹窄款收纳书柜', '北美原木色五层书柜', '100cm原木纹展示书柜', '北美原木纹储物书柜', ]), selected_sku_name: '北美原木色五层书柜', ai_sku_name_model: 'qwen3.7-max-2026-06-08', }); assert.equal(row.originalSkuName, '款式一 100/30/180 北美原木纹色'); assert.equal(row.skuName, '北美原木色五层书柜'); assert.equal(row.selectedSkuName, '北美原木色五层书柜'); assert.equal(row.optimizedSkuNames.length, 5); }); test('profit pricing table normalizes group price as the final auto-listing SKU price', () => { const [row] = normalizeProfitPricingRows([{ barcode: 'SG008830N1049V01', groupPrice: 399.9, price: 219.9, installFee: 30, coupon: 20, activityDiscount: 0.6, shipping: 20, commission: 0.06, targetMargin: 0.15, }]); assert.equal(row.skuCode, 'SG008830N1049V01'); assert.equal(row.groupPrice, 399.9); assert.equal(row.finalPrice, 219.9); assert.equal(row.installationFee, 30); assert.equal(row.discountAmount, 20); assert.equal(row.activityDiscount, 0.6); assert.equal(row.freightNational, 20); assert.throws( () => normalizeProfitPricingRows([{ skuCode: 'SKU-1', groupPrice: 0 }]), /拼单价必须大于0/ ); }); test('main image generation prioritizes one company product image over the market scene', () => { assert.deepEqual( buildMainImageReferenceImages( 'https://example.com/company-product.jpg', 'https://example.com/market-scene.jpg' ), [ 'https://example.com/company-product.jpg', 'https://example.com/market-scene.jpg', ] ); assert.deepEqual( buildMainImageReferenceImages( 'https://example.com/same.jpg', 'https://example.com/same.jpg' ), ['https://example.com/same.jpg'] ); assert.deepEqual( buildMainImageReferenceImages( 'https://example.com/1688-product.jpg', 'https://example.com/market-composition.jpg', true ), [ 'https://example.com/1688-product.jpg', 'https://example.com/market-composition.jpg', ] ); assert.deepEqual(normalizeMainImageRetryIndexes([3, 2, 3, 0, 6, '2']), [3, 2]); }); test('removing a pricing SKU prunes it from the auto-listing workflow payloads', () => { const scenePayload = { total: 3, selectedSkuCodes: ['SKU-A', 'SKU-B', 'SKU-C'], rows: [ { skuCode: 'SKU-A', generatedImageUrl: 'https://example.com/a.jpg' }, { skuCode: 'SKU-B', generatedImageUrl: 'https://example.com/b.jpg' }, { skuCode: 'SKU-C', generatedImageUrl: 'https://example.com/c.jpg' }, ], }; const namePayload = { rows: [ { skuCode: 'SKU-A', selectedName: 'A1' }, { skuCode: 'SKU-B', selectedName: 'B1' }, { skuCode: 'SKU-C', selectedName: 'C1' }, ], }; const nextScenePayload = pruneSelectionSkuPayload( scenePayload, ['sku-a', 'SKU-C'], { syncSelection: true } ); const nextNamePayload = pruneSelectionSkuPayload(namePayload, ['SKU-A', 'SKU-C']); assert.equal(nextScenePayload.total, 2); assert.deepEqual(nextScenePayload.selectedSkuCodes, ['SKU-A', 'SKU-C']); assert.deepEqual(nextScenePayload.rows.map((row) => row.skuCode), ['SKU-A', 'SKU-C']); assert.deepEqual(nextNamePayload.rows.map((row) => row.skuCode), ['SKU-A', 'SKU-C']); assert.equal(scenePayload.total, 3); assert.equal(namePayload.rows.length, 3); }); test('one SPU delivery package contains base and PDD activity pricing tables', () => { const baseRow = mapPricingRow({ plan_item_id: 3, sku_code: 'SG008830N1049V01', sku_name: '公司原名', ai_sku_name_candidates_json: JSON.stringify(['名称1', '名称2', '名称3', '名称4', '名称5']), selected_sku_name: '名称3', group_price: 399.9, }); baseRow.productCode = 'SPU-ACCIDENT'; const activityRow = mapActivityPricingRow({ plan_item_id: 3, sku_code: 'SG008830N1049V01', group_price: 399.9, final_group_price: 399.9, activity_prices_json: JSON.stringify(buildEmptyPddActivityPrices()), }); const payload = buildDeliveryPayload({ id: 1, schemeCode: 'FB-TEST', items: [{ id: 3, slotNo: 1, marketItemId: '10001', priceBand: '100 - 200', priceBandList: ['100 - 200', '200 - 300'], selectedSpuCode: 'SG0088', pricingRows: [baseRow], activityPricingRows: [activityRow], }], }); assert.equal(payload.pricingTables[0].tableType, 'base'); assert.equal(payload.pricingTables[0].rows[0].skuName, '名称3'); assert.equal(payload.pricingTables[0].rows[0].optimizedSkuNames.length, 5); assert.equal(payload.pricingTables[0].rows[0].finalSkuPrice, 399.9); assert.equal(payload.pricingTables[0].rows[0].skuCode, 'SG008830N1049V01'); assert.equal(payload.pricingTables[0].rows[0].sourceSkuCode, 'SG008830N1049V01'); assert.equal(payload.activityPricingTables[0].tableType, 'pdd_activity'); assert.equal(payload.activityPricingTables[0].rows[0].skuCode, 'SG008830N1049V01'); assert.ok(payload.activityPricingTables[0].rows[0].activityPrices.seckill65); assert.equal(payload.activityPricingTables[0].rows[0].finalSkuPrice, 399.9); assert.equal(payload.marketImages[0].priceBand, '100 - 200'); assert.deepEqual(payload.automaticListingTasks[0].priceBandList, ['100 - 200', '200 - 300']); }); test('one market item can create multiple unique SPU listing tasks', () => { const selections = normalizeSpuSelections({ selections: [ { spuCode: 'SG0088', spuName: '书柜A' }, { productCode: 'SG0099', spuName: '书柜B' }, { spuCode: 'sg0088', spuName: '重复项' }, ], }); assert.deepEqual(selections.map((row) => row.spuCode), ['SG0088', 'SG0099']); }); test('workflow summary exposes seven visible nodes and multi-SPU progress', () => { const workflow = buildWorkflowSummary({ workflowStatus: 'waiting', workflowStep: 3, workflowProgress: 30, items: [ { id: 1, sourceItemId: 1, slotNo: 1, variantNo: 1, selectedSpuCode: 'SG0088', workflowStatus: 'waiting' }, { id: 2, sourceItemId: 1, slotNo: 1, variantNo: 2, selectedSpuCode: 'SG0099', workflowStatus: 'waiting' }, { id: 3, sourceItemId: 3, slotNo: 2, variantNo: 1, selectedSpuCode: 'SG0100', workflowStatus: 'waiting' }, ], }); assert.equal(workflow.steps.length, 7); assert.equal(workflow.currentStepLabel, '主视觉商品智能重构'); assert.deepEqual(workflow.steps.map((step) => step.progress), [100, 100, 100, 0, 0, 0, 0]); assert.ok(workflow.steps.every((step) => step.progress >= 0 && step.progress <= 100)); assert.equal(workflow.sourceTotal, 2); assert.equal(workflow.taskTotal, 3); assert.equal(workflow.requiresUser, true); }); test('workflow summary returns older plans to an unconfirmed AI approval node', () => { const workflow = buildWorkflowSummary({ workflowStatus: 'waiting', workflowStep: 7, workflowProgress: 74, items: [{ id: 1, sourceItemId: 1, slotNo: 1, variantNo: 1, selectedSpuCode: 'CBG0054', whiteBackgroundAssets: { rows: [{ skuCode: 'SKU-1', previewImage: { previewUrl: 'https://example.com/main-sku.jpg' } }] }, selectedMainSkuImageUrl: 'https://example.com/main-sku.jpg', mainImageGeneration: { rows: Array.from({ length: 5 }, (_, index) => ({ imageUrl: `https://example.com/main-${index}.jpg` })), }, skuSnapshot: { rows: [{ skuCode: 'SKU-1' }] }, skuSceneImages: { selectedSkuCodes: ['SKU-1'], rows: [{ skuCode: 'SKU-1', generatedImageUrl: 'https://example.com/sku-1.jpg' }], }, skuNameCandidates: { rows: [{ skuCode: 'SKU-1', names: ['名称1', '名称2', '名称3', '名称4', '名称5'], selectedName: '' }], }, optimizedTitleCandidates: ['标题1', '标题2', '标题3', '标题4', '标题5'], workflowStatus: 'waiting', }], }); assert.equal(workflow.currentStep, 8); assert.equal(workflow.currentStepLabel, '全渠道价格策略编排'); assert.equal(workflow.progress, 30); assert.equal(workflow.requiresUser, true); }); test('legacy video step is treated as complete without stored video assets', () => { assert.equal(isSelectionWorkflowTaskStepComplete({}, 5), true); const workflow = buildWorkflowSummary({ workflowStatus: 'waiting', workflowStep: 5, workflowProgress: 52, items: [], }); assert.equal(workflow.currentStep, 6); assert.equal(workflow.steps.some((step) => step.step === 5), false); }); test('automatic-listing delivery hydrates video assets by SPU', async () => { const originalGet = axios.get; const originalBaseUrl = process.env.SELECTION_VIDEO_ASSETS_BASE_URL; const originalApiKey = process.env.SELECTION_VIDEO_ASSETS_API_KEY; process.env.SELECTION_VIDEO_ASSETS_BASE_URL = 'https://video.example.com'; process.env.SELECTION_VIDEO_ASSETS_API_KEY = 'test-video-key'; axios.get = async (url, config) => { assert.equal(url, 'https://video.example.com/api/external/video-assets'); assert.equal(config.params.keyword, 'SG0088'); assert.equal(config.headers['X-API-Key'], 'test-video-key'); return { data: { data: { items: [{ materialId: 'video-1', downloadUrl: 'https://example.com/video-1.mp4' }], }, }, }; }; try { const hydratedPlan = await hydrateDeliveryVideoAssets({ items: [{ id: 1, selectedSpuCode: 'SG0088' }], listingTasks: [{ id: 1, selectedSpuCode: 'SG0088' }], }); const mainVideo = buildDeliveryVideoPayload(hydratedPlan.listingTasks[0]); assert.equal(mainVideo.spuCode, 'SG0088'); assert.equal(mainVideo.videos.length, 1); assert.equal(mainVideo.videos[0].materialId, 'video-1'); assert.equal(mainVideo.warning, null); } finally { axios.get = originalGet; if (originalBaseUrl === undefined) delete process.env.SELECTION_VIDEO_ASSETS_BASE_URL; else process.env.SELECTION_VIDEO_ASSETS_BASE_URL = originalBaseUrl; if (originalApiKey === undefined) delete process.env.SELECTION_VIDEO_ASSETS_API_KEY; else process.env.SELECTION_VIDEO_ASSETS_API_KEY = originalApiKey; } }); test('desktop delivery materials are fetched by company SPU on the backend', async () => { const originalGet = axios.get; const originalApiKey = process.env.SELECTION_MATERIAL_ASSETS_API_KEY; process.env.SELECTION_MATERIAL_ASSETS_API_KEY = 'server-only-material-key'; axios.get = async (_url, config) => { assert.equal(config.params.spu, 'SG0082'); assert.equal(config.headers['X-API-Key'], 'server-only-material-key'); if (config.params.resourceType === 'white-background') { return { data: { data: { materialId: 'white-1', skuList: [] } } }; } assert.equal(config.params.resourceType, 'task-detail'); return { data: { data: { resourceType: 'task-detail', candidateCount: 1, candidates: [{ materialId: 'detail-1', downloadUrls: { detail: ['https://example.com/company-detail.jpg'] }, }], }, }, }; }; try { const materials = await buildDeliveryMaterials({ selectedSpuCode: 'sg0082' }); assert.equal(materials.length, 1); assert.equal(materials[0].source, 'company-material-service'); assert.deepEqual( materials[0].taskDetail.candidates[0].downloadUrls.detail, ['https://example.com/company-detail.jpg'] ); } finally { axios.get = originalGet; if (originalApiKey === undefined) delete process.env.SELECTION_MATERIAL_ASSETS_API_KEY; else process.env.SELECTION_MATERIAL_ASSETS_API_KEY = originalApiKey; } }); test('desktop app claim payload contains only the claimed SPU child task', () => { const firstTask = { id: 101, taskCode: 'FB-TEST-S01-01', sourceItemId: 11, slotNo: 1, variantNo: 1, marketItemId: 'market-1', mainImageUrl: 'https://example.com/market-1.jpg', replacedImageUrl: 'https://example.com/generated-1.jpg', selectedSpuCode: 'SG0088', selectedSpuName: '书柜A', selectedMainSkuCode: 'SG0088-A', selectedMainSkuImageUrl: 'https://example.com/white-a.jpg', selectedProductTitle: 'AI标题A', optimizedTitleCandidates: ['标题1', '标题2', '标题3', '标题4', '标题5'], pricingTableSavedAt: '2026-08-01 09:00:00', pricingTableVersion: 1, pricingRows: [mapPricingRow({ plan_item_id: 101, sku_code: 'SG0088-A', sku_name: '公司原名A', ai_sku_name_candidates_json: JSON.stringify(['名称1', '名称2', '名称3', '名称4', '名称5']), selected_sku_name: '名称1', generated_image_url: 'https://example.com/sku-a.jpg', group_price: 399.9, })], activityPricingRows: [mapActivityPricingRow({ plan_item_id: 101, sku_code: 'SG0088-A', group_price: 399.9, final_group_price: 399.9, activity_prices_json: JSON.stringify(buildEmptyPddActivityPrices()), })], skuSnapshot: { rows: [{ skuCode: 'SG0088-A', skuName: 'SG0088-A', skuImageUrl: 'https://example.com/white-a.jpg', }] }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: ['SG0088-A'], rows: [createVerifiedSkuImageRow({ skuCode: 'SG0088-A', sourceImageUrl: 'https://example.com/white-a.jpg', generatedImageUrl: 'https://example.com/sku-a.jpg', })], }, }; const secondTask = { ...firstTask, id: 102, taskCode: 'FB-TEST-S01-02', variantNo: 2, selectedSpuCode: 'SG0099', selectedSpuName: '书柜B', }; const plan = { id: 9, schemeCode: 'FB-TEST', dingtalkUserName: '张三', sourceItems: Array.from({ length: 5 }, (_, index) => ({ id: 11 + index, sourceItemId: 11 + index, marketItemId: `market-${index + 1}`, })), items: [firstTask, secondTask], listingTasks: [firstTask, secondTask], }; const taskPayload = buildDeliveryTaskPayload(plan, secondTask); assert.equal(taskPayload.taskId, 102); assert.equal(taskPayload.taskCode, 'FB-TEST-S01-02'); assert.equal(taskPayload.spuCode, 'SG0099'); assert.equal(taskPayload.pricingTable.taskId, 102); assert.equal(taskPayload.activityPricingTable.taskId, 102); assert.equal(taskPayload.marketImage.taskId, 102); assert.deepEqual(taskPayload.marketImages.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.selectedSpus.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.productTitles.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.skuImageSets.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.mainVideos.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.pricingTables.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.activityPricingTables.map((row) => row.taskId), [102]); assert.deepEqual(taskPayload.automaticListingTasks.map((row) => row.taskId), [102]); assert.equal(taskPayload.readiness.complete, true, JSON.stringify(taskPayload.readiness.missing)); assert.deepEqual(taskPayload.mainVideo.videos, []); assert.equal(taskPayload.skuFinalPrices[0].finalSkuPrice, 399.9); assert.equal( taskPayload.skuFinalPrices[0].finalSkuPriceSource, 'profitPricingTable.groupPrice' ); }); test('five market IDs keep independent main-image and SKU-image sets', () => { const listingTasks = Array.from({ length: 5 }, (_, index) => { const number = index + 1; const taskId = 200 + number; const marketItemId = `market-${number}`; const spuCode = `SPU-${number}`; const skuCodes = [`${spuCode}-A`, `${spuCode}-B`]; return { id: taskId, taskCode: `FB-FIVE-S${String(number).padStart(2, '0')}-01`, sourceItemId: taskId, slotNo: number, variantNo: 1, marketItemId, mainImageUrl: `https://assets.example.com/${marketItemId}/market.jpg`, replacedImageUrl: `https://assets.example.com/${marketItemId}/main-1.jpg`, mainImageGeneration: { rows: Array.from({ length: 5 }, (_, imageIndex) => ({ imageUrl: `https://assets.example.com/${marketItemId}/main-${imageIndex + 1}.jpg`, })), }, selectedSpuCode: spuCode, selectedSpuName: `商品 ${number}`, selectedMainSkuCode: skuCodes[0], selectedMainSkuImageUrl: `https://assets.example.com/${marketItemId}/white.jpg`, selectedProductTitle: `商品标题 ${number}`, optimizedTitleCandidates: Array.from({ length: 5 }, (_, titleIndex) => `标题 ${number}-${titleIndex + 1}`), pricingTableSavedAt: '2026-08-14 10:00:00', skuSnapshot: { rows: skuCodes.map((skuCode) => ({ skuCode, skuName: skuCode, skuImageUrl: `https://assets.example.com/${marketItemId}/${skuCode}-source.jpg`, })) }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: skuCodes, rows: skuCodes.map((skuCode) => createVerifiedSkuImageRow({ skuCode, sourceImageUrl: `https://assets.example.com/${marketItemId}/${skuCode}-source.jpg`, generatedImageUrl: `https://assets.example.com/${marketItemId}/${skuCode}-generated.jpg`, })), }, pricingRows: skuCodes.map((skuCode) => ({ skuCode, skuName: `原始名称 ${skuCode}`, selectedSkuName: `上架名称 ${skuCode}`, optimizedSkuNames: Array.from({ length: 5 }, (_, nameIndex) => `名称 ${skuCode}-${nameIndex + 1}`), skuImageUrl: `https://assets.example.com/${marketItemId}/${skuCode}-source.jpg`, generatedImageUrl: `https://assets.example.com/${marketItemId}/${skuCode}-generated.jpg`, groupPrice: 100 + number, })), activityPricingRows: skuCodes.map((skuCode) => ({ skuCode, groupPrice: 100 + number, })), }; }); const plan = { id: 20, schemeCode: 'FB-FIVE', sourceItems: listingTasks.map((task) => ({ id: task.id, sourceItemId: task.id, marketItemId: task.marketItemId, })), items: listingTasks, listingTasks, }; const payload = buildDeliveryPayload(plan); assert.equal(payload.marketImages.length, 5); assert.equal(payload.skuImageSets.length, 5); listingTasks.forEach((task, index) => { const number = index + 1; const marketItemId = `market-${number}`; const mainImageSet = payload.marketImages.find((row) => row.taskId === task.id); const skuImageSet = payload.skuImageSets.find((row) => row.taskId === task.id); assert.equal(mainImageSet.marketItemId, marketItemId); assert.equal(mainImageSet.generatedMainImageUrls.length, 5); assert.ok(mainImageSet.generatedMainImageUrls.every((url) => url.includes(`/${marketItemId}/`))); assert.equal(skuImageSet.marketItemId, marketItemId); assert.equal(skuImageSet.skus.length, 2); assert.ok(skuImageSet.skus.every((sku) => ( sku.skuCode.startsWith(`SPU-${number}-`) && sku.generatedImageUrl.includes(`/${marketItemId}/`) ))); const claimedPayload = buildDeliveryTaskPayload(plan, task); assert.equal(claimedPayload.marketItemId, marketItemId); assert.equal(claimedPayload.marketImage.generatedMainImageUrls.length, 5); assert.ok(claimedPayload.marketImage.generatedMainImageUrls.every((url) => url.includes(`/${marketItemId}/`))); assert.ok(claimedPayload.skuImageSet.skus.every((sku) => sku.generatedImageUrl.includes(`/${marketItemId}/`))); }); }); test('desktop app delivery does not require a shop selected by the web app', () => { const plan = { schemeCode: 'FB-DESKTOP-SHOP', sourceItems: [{ id: 1, marketItemId: 'market-1' }], listingTasks: [{ id: 1, marketItemId: 'market-1', listingStatus: 'pending', mainImageUrl: 'https://example.com/market.jpg', replacedImageUrl: 'https://example.com/generated.jpg', selectedSpuCode: 'SG0088', selectedMainSkuCode: 'SG0088-A', selectedMainSkuImageUrl: 'https://example.com/white.jpg', selectedProductTitle: '测试商品标题', optimizedTitleCandidates: ['标题1', '标题2', '标题3', '标题4', '标题5'], pricingTableSavedAt: '2026-08-11 10:00:00', skuSnapshot: { rows: [{ skuCode: 'SG0088-A', skuName: 'SG0088-A', skuImageUrl: 'https://example.com/sku-source.jpg', }] }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: ['SG0088-A'], rows: [createVerifiedSkuImageRow({ skuCode: 'SG0088-A', sourceImageUrl: 'https://example.com/sku-source.jpg', generatedImageUrl: 'https://example.com/sku.jpg', })], }, pricingRows: [{ skuCode: 'SG0088-A', selectedSkuName: '测试SKU', optimizedSkuNames: ['名称1', '名称2', '名称3', '名称4', '名称5'], generatedImageUrl: 'https://example.com/sku.jpg', groupPrice: 399, }], activityPricingRows: [{ skuCode: 'SG0088-A', groupPrice: 399 }], }], }; const readiness = buildDeliveryReadiness(plan, { requireSubmitted: true }); assert.equal(readiness.complete, true, JSON.stringify(readiness.missing)); assert.equal(readiness.missing.some((item) => item.field === 'listingShop'), false); const beforeSubmission = buildDeliveryReadiness({ ...plan, listingTasks: plan.listingTasks.map((item) => ({ ...item, listingStatus: 'draft' })), }, { requireSubmitted: true }); assert.equal(beforeSubmission.complete, false); assert.equal(beforeSubmission.missing.some((item) => item.field === 'listingStatus'), true); }); test('delivery accepts a confirmed SKU name without retaining AI name candidates', () => { const skuCode = 'SG0088-A'; const task = { id: 1, marketItemId: 'market-1', mainImageUrl: 'https://example.com/market.jpg', replacedImageUrl: 'https://example.com/generated.jpg', selectedSpuCode: 'SG0088', selectedMainSkuCode: skuCode, selectedMainSkuImageUrl: 'https://example.com/white.jpg', selectedProductTitle: '测试商品标题', optimizedTitleCandidates: ['标题1', '标题2', '标题3', '标题4', '标题5'], pricingTableSavedAt: '2026-08-21 10:00:00', skuSnapshot: { rows: [{ skuCode, skuName: '公司原始SKU名', skuImageUrl: 'https://example.com/sku-source.jpg', }] }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: [skuCode], rows: [createVerifiedSkuImageRow({ skuCode, skuName: '公司原始SKU名', sourceImageUrl: 'https://example.com/sku-source.jpg', generatedImageUrl: 'https://example.com/sku-source.jpg', })], }, pricingRows: [{ skuCode, selectedSkuName: '用户确认的上架SKU名', optimizedSkuNames: [], generatedImageUrl: 'https://example.com/sku-source.jpg', groupPrice: 399, }], activityPricingRows: [{ skuCode, groupPrice: 399 }], }; const readiness = buildDeliveryReadiness({ schemeCode: 'FB-CONFIRMED-NAME', sourceItems: [{ id: 1, marketItemId: 'market-1' }], listingTasks: [task], }); assert.equal(readiness.complete, true, JSON.stringify(readiness.missing)); }); test('1688 delivery validates and exports only the SKU subset selected for this listing', () => { const pricingRows = ['SKU-A', 'SKU-B'].map((skuCode) => ({ skuCode, selectedSkuName: `上架名称 ${skuCode}`, color: '白色', size: '80cm', internalProductCode: `STYLE-1688-${skuCode}`, productCodeProductName: `编码名称 ${skuCode}`, productCodeColorSpec: `颜色规格 ${skuCode}`, optimizedSkuNames: Array.from({ length: 5 }, (_, index) => `AI 名称 ${skuCode}-${index + 1}`), generatedImageUrl: `https://example.com/${skuCode}.jpg`, groupPrice: 99, })); const currentSchemaPricingRows = pricingRows.map((row) => { const next = { ...row }; delete next.optimizedSkuNames; delete next.generatedImageUrl; return next; }); const item = { id: 1688, workflowProfile: 'alibaba_1688_pending', marketItemId: '1688:123456', title: '1688 原始标题', listingStatus: 'pending', mainImageUrl: 'https://example.com/source.jpg', replacedImageUrl: 'https://example.com/main.jpg', selectedSpuCode: '1688:123456', selectedMainSkuCode: 'SKU-A', selectedMainSkuImageUrl: 'https://example.com/source.jpg', selectedProductTitle: '1688 原始标题', optimizedTitleCandidates: ['AI标题1', 'AI标题2', 'AI标题3', 'AI标题4', 'AI标题5'], pricingTableSavedAt: '2026-08-15 12:00:00', productCodeStatus: 'completed', productCodeStyleCode: 'STYLE-1688', metadata: { sourceRow: { product: { detailImages: ['https://example.com/detail.jpg'], skus: [ { skuId: 'SKU-A', name: '白色', price: 60 }, { skuId: 'SKU-B', name: '黑色', price: 65 }, ], }, }, }, skuSnapshot: { rows: [ { skuCode: 'SKU-A', skuName: '白色', skuImageUrl: 'https://example.com/SKU-A-source.jpg', purchasePrice: 60 }, { skuCode: 'SKU-B', skuName: '黑色', purchasePrice: 65 }, ], }, mainImageGeneration: { rows: Array.from({ length: 10 }, (_, index) => ({ index: index + 1, imageUrl: `https://example.com/main-${index + 1}.jpg`, })), }, skuSceneImages: { mode: 'source_original', selectedSkuCodes: ['SKU-A'], rows: [createVerifiedSkuImageRow({ skuCode: 'SKU-A', skuName: '白色', sourceImageUrl: 'https://example.com/SKU-A-source.jpg', generatedImageUrl: 'https://example.com/SKU-A.jpg', })], }, skuNameCandidates: { rows: ['SKU-A', 'SKU-B'].map((skuCode) => ({ skuCode, names: Array.from({ length: 5 }, (_, index) => `AI-${skuCode}-${index + 1}`), selectedName: `上架名称 ${skuCode}`, })), }, pricingRows: currentSchemaPricingRows, activityPricingRows: [], }; const plan = { id: 1688, schemeCode: '1688-SUBSET', workflowProfile: 'alibaba_1688_pending', sourceItems: [{ id: item.id, marketItemId: item.marketItemId }], listingTasks: [item], }; assert.deepEqual(filterWorkflowRowsBySelectedSkus(item, pricingRows).map((row) => row.skuCode), ['SKU-A']); assert.equal(buildDeliveryReadiness(plan, { requireSubmitted: true }).complete, true); const payload = buildDeliveryPayload(plan); assert.deepEqual(payload.pricingTables[0].rows.map((row) => row.skuCode), ['STYLE-1688-SKU-A']); assert.deepEqual(payload.activityPricingTables, []); assert.deepEqual(payload.skuImageSets[0].skus.map((row) => row.skuCode), ['STYLE-1688-SKU-A']); assert.deepEqual(payload.skuImageSets[0].skus[0].specs, [ { name: '颜色', value: '白色' }, { name: '尺寸', value: '80cm' }, ]); assert.deepEqual(payload.pricingTables[0].rows[0].specs, [ { name: '颜色', value: '白色' }, { name: '尺寸', value: '80cm' }, ]); const expectedProductCodeFields = { sourceSkuCode: 'SKU-A', internalProductCode: 'STYLE-1688-SKU-A', productCodeProductName: '编码名称 SKU-A', productCodeColorSpec: '颜色规格 SKU-A', productCode: 'STYLE-1688-SKU-A', listingSkuCode: 'STYLE-1688-SKU-A', }; for (const row of [ payload.pricingTables[0].rows[0], payload.skuImageSets[0].skus[0], payload.automaticListingTasks[0].skuProductCodes[0], ]) { assert.deepEqual( Object.fromEntries(Object.keys(expectedProductCodeFields).map((key) => [key, row[key]])), expectedProductCodeFields ); } const taskPayload = buildDeliveryTaskPayload(plan, item); assert.deepEqual(taskPayload.skuProductCodes[0], { skuCode: 'STYLE-1688-SKU-A', ...expectedProductCodeFields, }); assert.deepEqual( Object.fromEntries(Object.keys(expectedProductCodeFields).map((key) => [ key, taskPayload.skuFinalPrices[0][key], ])), expectedProductCodeFields ); for (const missingField of [ 'internalProductCode', 'productCodeProductName', 'productCodeColorSpec', ]) { const incompleteItem = { ...item, listingCompliance: { complete: true, blockers: [], warnings: [] }, pricingRows: pricingRows.map((row) => ( row.skuCode === 'SKU-A' ? { ...row, [missingField]: '' } : row )), }; const incompletePlan = { ...plan, listingTasks: [incompleteItem] }; const compliance = buildAlibaba1688ListingCompliance(incompleteItem, incompletePlan); assert.equal(compliance.checks.encodedSkuCount, 0); assert.ok(compliance.blockers.some((row) => row.field === 'productCodes')); const readiness = buildDeliveryReadiness(incompletePlan, { requireSubmitted: true }); assert.equal(readiness.complete, false); assert.ok(readiness.missing.some((row) => row.field === 'productCodes')); } }); test('1688 title candidates remain one original title plus five distinct AI alternatives', () => { const item = { workflowProfile: 'alibaba_1688_pending', title: '1688 原始标题', }; const generated = validateAdditionalProductTitleCandidates(item, [ 'AI 标题 1', 'AI 标题 2', 'AI 标题 3', 'AI 标题 4', 'AI 标题 5', ]); assert.equal(generated.length, 5); assert.deepEqual(getWorkflowProductTitleOptions({ ...item, optimizedTitleCandidates: generated, }), [ '1688 原始标题', 'AI 标题 1', 'AI 标题 2', 'AI 标题 3', 'AI 标题 4', 'AI 标题 5', ]); assert.throws(() => validateAdditionalProductTitleCandidates(item, [ '1688 原始标题', 'AI 标题 1', 'AI 标题 2', 'AI 标题 3', 'AI 标题 4', ]), /必须返回5条不同于原始标题/); }); test('relisting selects only successfully listed child tasks', () => { const tasks = getRelistableSelectionTasks({ listingTasks: [ { id: 1, listingStatus: 'listed', listedItemId: '10001' }, { id: 2, listingStatus: 'pending', listedItemId: '' }, { id: 3, listingStatus: 'failed', listedItemId: '' }, { id: 4, listingStatus: 'pending', listedItemId: 'legacy-10004' }, ], }); assert.deepEqual(tasks.map((item) => item.id), [1, 4]); });