cangku/backend/test/planningSelectionFissionAutoListing.test.js

3460 lines
122 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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]);
});