Files
order_site/apps/backend/src/services/order/delivery-task-service.test.ts
T
yml2213andCursor b3aad0044c refactor(fulfillment): 移除飞飞/乐玩通道,仅保留 affiliate_dash
新单只路由 affiliate_dash(未命中走 manual_dispatch);历史飞飞/乐玩任务保留只读 stub,避免订单重同步失败。同步清理 cloudtentacles 调度、Admin 写路径与相关 env。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 17:29:47 +08:00

366 lines
10 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { syncDeliveryTasksForOrderWithDeps } from './delivery-task-service.js'
import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js'
import type { JsonObject } from '../../types/json.js'
type CreatedTaskInput = {
orderId?: number
orderItemId?: number
unitIndex?: number
contextJson?: string
taskStatus?: string
executorKey?: string
[key: string]: unknown
}
function stubOrder(
patch: Partial<OrderRow> & Pick<OrderRow, 'id' | 'platform_order_id'>,
): OrderRow {
return {
id: patch.id,
order_id: patch.id,
provider: patch.provider || 'open_91',
platform: patch.platform || 'kuaishou',
shop_id: patch.shop_id || 'shop-1',
shop_name: patch.shop_name || '测试店铺',
platform_order_id: patch.platform_order_id,
order_status: patch.order_status || 'created',
pay_status: patch.pay_status || 'pending_payment',
buyer_id: '',
buyer_name: '',
receiver_contact: '',
total_amount: 0,
currency: 'CNY',
raw_payload_json: '{}',
paid_at: null,
created_at: '2026-05-29T00:00:00.000Z',
updated_at: '2026-05-29T00:00:00.000Z',
...patch,
} as OrderRow
}
function stubOrderItem(
patch: Partial<OrderItemRow> & Pick<OrderItemRow, 'id' | 'order_id' | 'sku_code'>,
): OrderItemRow {
return {
id: patch.id,
order_id: patch.order_id,
sku_code: patch.sku_code,
sku_name: patch.sku_name || patch.sku_code,
quantity: patch.quantity || 1,
spec_json: '{}',
item_snapshot_json: patch.item_snapshot_json || {},
created_at: '2026-05-29T00:00:00.000Z',
updated_at: '2026-05-29T00:00:00.000Z',
...patch,
} as OrderItemRow
}
function stubCreatedTask(id: number, input: CreatedTaskInput): TaskRow {
return {
id,
order_id: Number(input.orderId || 0),
order_item_id: Number(input.orderItemId || 0),
unit_index: Number(input.unitIndex || 1),
platform_order_id: '',
profile_id: 0,
task_no: 'DT-test',
executor_key: String(input.executorKey || ''),
task_status: String(input.taskStatus || 'pending'),
delivery_status: 'pending',
result_code: '',
result_message: '',
automation_mode: 'manual',
requires_claim: true,
user_action_status: 'pending_claim',
attempt_count: 0,
runtime_session_id: '',
login_type: '',
nickname: '',
role_name: '',
role_id: '',
area: '',
partition_name: '',
claim_token: '',
primary_claim_token: '',
primary_claim_token_id: null,
primary_claim_token_status: '',
artifacts_json: '{}',
context_json: String(input.contextJson || '{}'),
screenshot_path: '',
last_error: '',
retry_count: 0,
created_at: '2026-05-29T00:00:00.000Z',
updated_at: '2026-05-29T00:00:00.000Z',
claimed_at: null,
role_confirmed_at: null,
redeemed_at: null,
} as TaskRow
}
test('syncDeliveryTasksForOrder 为 affiliate_dash 商品创建任务上下文', async () => {
const createdInputs: CreatedTaskInput[] = []
await syncDeliveryTasksForOrderWithDeps(
stubOrder({
id: 1,
pay_status: 'pending_payment',
platform_order_id: '2614900602069169',
}),
[
stubOrderItem({
id: 10,
order_id: 1,
sku_code: 'AFF-1001',
sku_name: '幸运币90个',
quantity: 1,
item_snapshot_json: buildAffiliateSnapshot('AFF-1001', '幸运币90个'),
}),
],
{
listTasksByOrderId: async () => [],
getFulfillmentProfileByKey: async () => buildAffiliateProfile(),
createTask: async (input) => {
createdInputs.push(input as CreatedTaskInput)
return stubCreatedTask(100, input as CreatedTaskInput)
},
nowIso: () => '2026-05-29T09:05:46.658Z',
randomId: () => 'DT-test',
createTaskEvent: async () => null,
},
)
assert.equal(createdInputs[0]?.executorKey, 'affiliate_dash')
const context = JSON.parse(String(createdInputs[0]?.contextJson || '{}')) as JsonObject
const affiliateDash = context.affiliateDash as JsonObject
assert.equal(affiliateDash.sku, 'AFF-1001')
assert.equal(affiliateDash.productName, '幸运币90个')
assert.equal(affiliateDash.flowType, 'affiliate_dash')
})
test('syncDeliveryTasksForOrder 已有部分任务时按订单项补建缺失任务', async () => {
const existingTask = stubCreatedTask(201, {
orderId: 3,
orderItemId: 20,
unitIndex: 1,
executorKey: 'affiliate_dash',
})
const createdInputs: CreatedTaskInput[] = []
const taskEvents: Array<{ taskId: number; eventType: string }> = []
const tasks = await syncDeliveryTasksForOrderWithDeps(
stubOrder({ id: 3, platform_order_id: 'partial-order' }),
[
stubOrderItem({
id: 20,
order_id: 3,
sku_code: 'SKU-existing',
item_snapshot_json: buildAffiliateSnapshot('AFF-A', '商品 A'),
}),
stubOrderItem({
id: 21,
order_id: 3,
sku_code: 'SKU-missing',
item_snapshot_json: buildAffiliateSnapshot('AFF-B', '商品 B'),
}),
],
{
listTasksByOrderId: async () => [existingTask],
getFulfillmentProfileByKey: async () => buildAffiliateProfile(),
createTask: async (input) => {
createdInputs.push(input as CreatedTaskInput)
return stubCreatedTask(202, input as CreatedTaskInput)
},
createTaskEvent: async (taskId, eventType) => {
taskEvents.push({ taskId: Number(taskId), eventType })
return null
},
logInfo: () => null,
logWarn: () => null,
nowIso: () => '2026-05-29T09:05:46.658Z',
randomId: () => 'DT-partial',
},
)
assert.deepEqual(
createdInputs.map((input) => [input.orderItemId, input.unitIndex]),
[[21, 1]],
)
assert.deepEqual(
tasks.map((task) => task.id),
[201, 202],
)
assert.deepEqual(taskEvents, [{ taskId: 202, eventType: 'fulfillment_task_planned' }])
})
test('syncDeliveryTasksForOrder 数量增加时只补建缺失单元', async () => {
const existingTask = stubCreatedTask(301, {
orderId: 4,
orderItemId: 30,
unitIndex: 1,
executorKey: 'affiliate_dash',
})
const createdInputs: CreatedTaskInput[] = []
const tasks = await syncDeliveryTasksForOrderWithDeps(
stubOrder({ id: 4, platform_order_id: 'quantity-increased' }),
[
stubOrderItem({
id: 30,
order_id: 4,
sku_code: 'SKU-quantity',
quantity: 2,
item_snapshot_json: buildAffiliateSnapshot('AFF-A', '商品 A'),
}),
],
{
listTasksByOrderId: async () => [existingTask],
getFulfillmentProfileByKey: async () => buildAffiliateProfile(),
createTask: async (input) => {
createdInputs.push(input as CreatedTaskInput)
return stubCreatedTask(302, input as CreatedTaskInput)
},
createTaskEvent: async () => null,
logInfo: () => null,
logWarn: () => null,
nowIso: () => '2026-05-29T09:05:46.658Z',
randomId: () => 'DT-quantity',
},
)
assert.deepEqual(
createdInputs.map((input) => input.unitIndex),
[2],
)
assert.deepEqual(
tasks.map((task) => task.unit_index),
[1, 2],
)
})
test('syncDeliveryTasksForOrder 规划失败时记录可定位的路由诊断', async () => {
const warnings: Array<{ message: string; detail: Record<string, unknown> }> = []
const tasks = await syncDeliveryTasksForOrderWithDeps(
stubOrder({ id: 5, platform_order_id: 'planning-failed' }),
[
stubOrderItem({
id: 40,
order_id: 5,
sku_code: 'SKU-broken',
item_snapshot_json: {
isConfigured: true,
fulfillmentRoute: {
selectedExecutorKey: 'affiliate_dash',
reason: '规则指定 affiliate',
},
affiliateDash: {
skuName: '商品 A',
},
} as JsonObject,
}),
],
{
listTasksByOrderId: async () => [],
getFulfillmentProfileByKey: async () => buildAffiliateProfile(),
createTaskEvent: async () => null,
logInfo: () => null,
logWarn: (_scope, message, detail) => {
warnings.push({
message: String(message),
detail: detail as Record<string, unknown>,
})
return null
},
},
)
assert.deepEqual(tasks, [])
assert.equal(warnings.length, 1)
assert.equal(warnings[0]?.message, '订单项已配置履约路由但无法生成任务计划')
assert.equal(warnings[0]?.detail.orderItemId, 40)
assert.deepEqual(warnings[0]?.detail.missingUnitIndexes, [1])
assert.deepEqual(warnings[0]?.detail.planningDiagnostic, {
snapshotIsConfigured: true,
selectedExecutorKey: 'affiliate_dash',
routeReason: '规则指定 affiliate',
candidateAvailability: [],
sourceItemTitle: '',
sourceSkuNick: '',
cloudSkuId: 0,
cloudSkuName: '',
cloudSourceKeys: [],
feifeiProductCode: '',
affiliateDashSku: '',
})
})
test('syncDeliveryTasksForOrder 并发创建冲突时复用已落库任务', async () => {
const concurrentTask = stubCreatedTask(401, {
orderId: 6,
orderItemId: 50,
unitIndex: 1,
executorKey: 'affiliate_dash',
})
let listCount = 0
let eventCount = 0
const tasks = await syncDeliveryTasksForOrderWithDeps(
stubOrder({ id: 6, platform_order_id: 'concurrent-order' }),
[
stubOrderItem({
id: 50,
order_id: 6,
sku_code: 'SKU-concurrent',
item_snapshot_json: buildAffiliateSnapshot('AFF-A', '商品 A'),
}),
],
{
listTasksByOrderId: async () => (listCount++ === 0 ? [] : [concurrentTask]),
getFulfillmentProfileByKey: async () => buildAffiliateProfile(),
createTask: async () => {
throw new Error('duplicate key value violates unique constraint')
},
createTaskEvent: async () => {
eventCount += 1
return null
},
logInfo: () => null,
logWarn: () => null,
nowIso: () => '2026-05-29T09:05:46.658Z',
randomId: () => 'DT-concurrent',
},
)
assert.deepEqual(
tasks.map((task) => task.id),
[401],
)
assert.equal(eventCount, 0)
})
function buildAffiliateSnapshot(sku: string, productName: string): JsonObject {
return {
isConfigured: true,
fulfillmentRoute: {
selectedExecutorKey: 'affiliate_dash',
},
affiliateDash: {
sku,
skuName: productName,
matchMode: 'affiliate_dash_sku_mapping',
},
}
}
function buildAffiliateProfile() {
return {
id: 11,
profile_key: 'affiliate_dash',
profile_name: 'affiliate-dash 履约',
executor_key: 'affiliate_dash',
}
}