后端补充自动发货服务测试
This commit is contained in:
@@ -28,6 +28,18 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
task,
|
||||
trigger = 'task_delivered',
|
||||
} = {}) {
|
||||
return ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps({ order, task, trigger })
|
||||
}
|
||||
|
||||
export async function ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps({
|
||||
order,
|
||||
task,
|
||||
trigger = 'task_delivered',
|
||||
} = {}, deps = {}) {
|
||||
const listOrderTasks = deps.listTasksByOrderId || listTasksByOrderId
|
||||
const sendRequest = deps.fetch || fetch
|
||||
const confirmShipped = deps.confirmAgisoXianyuAutoDeliveryShipped || confirmAgisoXianyuAutoDeliveryShipped
|
||||
|
||||
if (!isAgisoXianyuOrder(order) || !task?.id) {
|
||||
return { sent: false, skipped: true, reason: 'not_supported', task }
|
||||
}
|
||||
@@ -36,7 +48,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
return { sent: false, skipped: true, reason: 'task_not_delivered', task }
|
||||
}
|
||||
|
||||
const orderTasks = await listTasksByOrderId(order.id)
|
||||
const orderTasks = await listOrderTasks(order.id)
|
||||
if (hasAgisoAutoDeliverySucceeded(orderTasks)) {
|
||||
return { sent: false, skipped: true, reason: 'already_sent', task }
|
||||
}
|
||||
@@ -64,7 +76,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
trigger,
|
||||
reason: 'auto_delivery_disabled',
|
||||
order,
|
||||
})
|
||||
}, deps)
|
||||
}
|
||||
|
||||
if (!config.endpoint || !config.accessToken || !config.appSecret) {
|
||||
@@ -78,7 +90,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
hasAccessToken: Boolean(config.accessToken),
|
||||
hasAppSecret: Boolean(config.appSecret),
|
||||
},
|
||||
})
|
||||
}, deps)
|
||||
}
|
||||
|
||||
const requestHeaders = buildRequestHeaders({
|
||||
@@ -100,7 +112,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await fetch(config.endpoint, {
|
||||
const response = await sendRequest(config.endpoint, {
|
||||
method: 'POST',
|
||||
headers: requestHeaders,
|
||||
body: new URLSearchParams(requestBody).toString(),
|
||||
@@ -113,7 +125,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
const requestId = String(parsed?.RequestId || '').trim()
|
||||
|
||||
// 发货接口返回成功后,再查一次 Order/Detail 确认订单真的进入已发货状态 meow~
|
||||
const confirmResult = await confirmAgisoXianyuAutoDeliveryShipped({
|
||||
const confirmResult = await confirmShipped({
|
||||
shopId: order.shop_id,
|
||||
platformOrderId: order.platform_order_id,
|
||||
requestId,
|
||||
@@ -146,7 +158,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
},
|
||||
})
|
||||
}, deps)
|
||||
}
|
||||
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', 'Agiso 咸鱼自动发货成功,订单已确认进入已发货状态', {
|
||||
@@ -173,10 +185,10 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
},
|
||||
})
|
||||
}, deps)
|
||||
|
||||
// 发货确认成功后,发送自定义消息通知 meow~
|
||||
await sendAutoDeliveryMessage({ order, task })
|
||||
await sendAutoDeliveryMessage({ order, task }, deps)
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -201,7 +213,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
responseStatus: response.status,
|
||||
response: parsed,
|
||||
errorMessage,
|
||||
})
|
||||
}, deps)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error || 'Agiso 咸鱼自动发货失败')
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', 'Agiso 咸鱼自动发货异常', {
|
||||
@@ -221,7 +233,7 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
responseStatus: 0,
|
||||
response: {},
|
||||
errorMessage,
|
||||
})
|
||||
}, deps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,8 +345,11 @@ async function persistAgisoAutoDeliveryResult(task, {
|
||||
response = {},
|
||||
errorMessage = '',
|
||||
detail = {},
|
||||
} = {}) {
|
||||
const now = nowIso()
|
||||
} = {}, deps = {}) {
|
||||
const getNowIso = deps.nowIso || nowIso
|
||||
const patchTask = deps.updateTask || updateTask
|
||||
const insertTaskEvent = deps.createTaskEvent || createTaskEvent
|
||||
const now = getNowIso()
|
||||
const currentContext = parseTaskContext(task)
|
||||
const nextContext = {
|
||||
...currentContext,
|
||||
@@ -350,13 +365,13 @@ async function persistAgisoAutoDeliveryResult(task, {
|
||||
...detail,
|
||||
},
|
||||
}
|
||||
const updatedTask = await updateTask(task.id, {
|
||||
const updatedTask = await patchTask(task.id, {
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
if (status === 'success' || status === 'failed' || status === 'skipped') {
|
||||
await createTaskEvent(task.id, `agiso_auto_delivery_${status}`, {
|
||||
await insertTaskEvent(task.id, `agiso_auto_delivery_${status}`, {
|
||||
trigger: String(trigger || '').trim(),
|
||||
reason: String(reason || '').trim(),
|
||||
platformOrderId: String(order?.platform_order_id || '').trim(),
|
||||
@@ -471,9 +486,11 @@ async function confirmAgisoXianyuAutoDeliveryShipped({ shopId = '', platformOrde
|
||||
/**
|
||||
* 自动发货成功后发送消息通知 meow~
|
||||
*/
|
||||
async function sendAutoDeliveryMessage({ order, task } = {}) {
|
||||
async function sendAutoDeliveryMessage({ order, task } = {}, deps = {}) {
|
||||
try {
|
||||
const result = await ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask({ order, task })
|
||||
const deliverMessage = deps.ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask
|
||||
|| ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask
|
||||
const result = await deliverMessage({ order, task })
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', '自动发货消息通知结果', {
|
||||
orderId: order?.id,
|
||||
taskId: task?.id,
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { runtimeConfig } from '../../../../config/runtime.js'
|
||||
import {
|
||||
hasAgisoAutoDeliverySucceeded,
|
||||
ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps,
|
||||
isOrderReadyForAgisoAutoDelivery,
|
||||
isAgisoAutoDeliverySuccess,
|
||||
resolveAgisoAutoDeliveryEndpoint,
|
||||
resolveAgisoAutoDeliveryErrorMessage,
|
||||
} from './auto-delivery-service.js'
|
||||
|
||||
const agisoOrder = {
|
||||
id: 101,
|
||||
provider: 'agiso',
|
||||
platform: 'xianyu',
|
||||
shop_id: 'shop-auto-delivery-test',
|
||||
shop_name: '自动发货测试店',
|
||||
platform_order_id: 'P-AUTO-10001',
|
||||
}
|
||||
|
||||
function createDeliveredTask(patch = {}) {
|
||||
return {
|
||||
id: 201,
|
||||
order_id: agisoOrder.id,
|
||||
task_status: 'completed',
|
||||
delivery_status: 'delivered',
|
||||
context_json: '{}',
|
||||
...patch,
|
||||
}
|
||||
}
|
||||
|
||||
test('isOrderReadyForAgisoAutoDelivery requires every task to be delivered', () => {
|
||||
assert.equal(isOrderReadyForAgisoAutoDelivery([]), false)
|
||||
assert.equal(isOrderReadyForAgisoAutoDelivery([
|
||||
@@ -83,3 +105,160 @@ test('resolveAgisoAutoDeliveryEndpoint falls back to DummySend', () => {
|
||||
'https://gw-api.agiso.com/aldsIdle/Order/DummySend',
|
||||
)
|
||||
})
|
||||
|
||||
test('ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps skips while other order tasks are not delivered', async () => {
|
||||
const calls = {
|
||||
update: 0,
|
||||
fetch: 0,
|
||||
}
|
||||
|
||||
const result = await ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps({
|
||||
order: agisoOrder,
|
||||
task: createDeliveredTask(),
|
||||
}, {
|
||||
listTasksByOrderId: async () => [
|
||||
createDeliveredTask(),
|
||||
createDeliveredTask({ id: 202, delivery_status: 'processing' }),
|
||||
],
|
||||
updateTask: async () => {
|
||||
calls.update += 1
|
||||
return null
|
||||
},
|
||||
fetch: async () => {
|
||||
calls.fetch += 1
|
||||
return { status: 200, text: async () => '{}' }
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(result.sent, false)
|
||||
assert.equal(result.skipped, true)
|
||||
assert.equal(result.reason, 'waiting_other_tasks')
|
||||
assert.equal(calls.update, 0)
|
||||
assert.equal(calls.fetch, 0)
|
||||
})
|
||||
|
||||
test('ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps persists missing config as skipped', async () => {
|
||||
const originalAppSecret = runtimeConfig.platforms.agiso.appSecret
|
||||
const originalShops = runtimeConfig.platforms.agiso.messaging.shops
|
||||
const updates = []
|
||||
const events = []
|
||||
|
||||
runtimeConfig.platforms.agiso.appSecret = ''
|
||||
runtimeConfig.platforms.agiso.messaging.shops = {}
|
||||
|
||||
try {
|
||||
const task = createDeliveredTask({ context_json: JSON.stringify({ existing: true }) })
|
||||
const result = await ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps({
|
||||
order: {
|
||||
...agisoOrder,
|
||||
shop_id: 'missing-auto-delivery-config-shop',
|
||||
},
|
||||
task,
|
||||
trigger: 'unit_test',
|
||||
}, {
|
||||
listTasksByOrderId: async () => [task],
|
||||
updateTask: async (taskId, patch) => {
|
||||
updates.push({ taskId, patch })
|
||||
return { ...task, ...patch }
|
||||
},
|
||||
createTaskEvent: async (taskId, eventType, payload, createdAt) => {
|
||||
events.push({ taskId, eventType, payload, createdAt })
|
||||
return { id: 1 }
|
||||
},
|
||||
fetch: async () => {
|
||||
throw new Error('fetch should not be called without config')
|
||||
},
|
||||
nowIso: () => '2026-05-21T12:00:00.000Z',
|
||||
})
|
||||
|
||||
assert.equal(result.sent, false)
|
||||
assert.equal(result.skipped, true)
|
||||
assert.equal(result.reason, 'missing_config')
|
||||
assert.equal(updates.length, 1)
|
||||
assert.equal(events[0]?.eventType, 'agiso_auto_delivery_skipped')
|
||||
|
||||
const context = JSON.parse(updates[0].patch.context_json)
|
||||
assert.equal(context.existing, true)
|
||||
assert.equal(context.agisoAutoDelivery.status, 'skipped')
|
||||
assert.equal(context.agisoAutoDelivery.reason, 'missing_config')
|
||||
assert.deepEqual({
|
||||
hasEndpoint: context.agisoAutoDelivery.hasEndpoint,
|
||||
hasAccessToken: context.agisoAutoDelivery.hasAccessToken,
|
||||
hasAppSecret: context.agisoAutoDelivery.hasAppSecret,
|
||||
}, {
|
||||
hasEndpoint: true,
|
||||
hasAccessToken: false,
|
||||
hasAppSecret: false,
|
||||
})
|
||||
} finally {
|
||||
runtimeConfig.platforms.agiso.appSecret = originalAppSecret
|
||||
runtimeConfig.platforms.agiso.messaging.shops = originalShops
|
||||
}
|
||||
})
|
||||
|
||||
test('ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps fails when accepted delivery is not confirmed as shipped', async () => {
|
||||
const originalAppSecret = runtimeConfig.platforms.agiso.appSecret
|
||||
const originalShops = runtimeConfig.platforms.agiso.messaging.shops
|
||||
const updates = []
|
||||
const events = []
|
||||
const messages = []
|
||||
|
||||
runtimeConfig.platforms.agiso.appSecret = 'runtime-secret'
|
||||
runtimeConfig.platforms.agiso.messaging.shops = {
|
||||
[agisoOrder.shop_id]: {
|
||||
accessToken: 'access-token',
|
||||
appSecret: 'shop-secret',
|
||||
apiVersion: '1',
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
const task = createDeliveredTask()
|
||||
const result = await ensureAgisoXianyuAutoDeliveryForDeliveredTaskWithDeps({
|
||||
order: agisoOrder,
|
||||
task,
|
||||
trigger: 'unit_test',
|
||||
}, {
|
||||
listTasksByOrderId: async () => [task],
|
||||
fetch: async () => ({
|
||||
status: 200,
|
||||
text: async () => JSON.stringify({ IsSuccess: true, RequestId: 'req-1' }),
|
||||
}),
|
||||
confirmAgisoXianyuAutoDeliveryShipped: async (input) => ({
|
||||
shipped: false,
|
||||
orderStatus: 2,
|
||||
shipTime: 0,
|
||||
reason: `not shipped: ${input.requestId}`,
|
||||
}),
|
||||
updateTask: async (taskId, patch) => {
|
||||
updates.push({ taskId, patch })
|
||||
return { ...task, ...patch }
|
||||
},
|
||||
createTaskEvent: async (taskId, eventType, payload, createdAt) => {
|
||||
events.push({ taskId, eventType, payload, createdAt })
|
||||
return { id: 2 }
|
||||
},
|
||||
ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask: async (payload) => {
|
||||
messages.push(payload)
|
||||
return { sent: true }
|
||||
},
|
||||
nowIso: () => '2026-05-21T12:01:00.000Z',
|
||||
})
|
||||
|
||||
assert.equal(result.sent, false)
|
||||
assert.equal(result.skipped, false)
|
||||
assert.equal(result.reason, 'delivery_not_confirmed')
|
||||
assert.equal(messages.length, 0)
|
||||
assert.equal(events[0]?.eventType, 'agiso_auto_delivery_failed')
|
||||
|
||||
const context = JSON.parse(updates[0].patch.context_json)
|
||||
assert.equal(context.agisoAutoDelivery.status, 'failed')
|
||||
assert.equal(context.agisoAutoDelivery.reason, 'delivery_not_confirmed')
|
||||
assert.equal(context.agisoAutoDelivery.requestId, 'req-1')
|
||||
assert.equal(context.agisoAutoDelivery.confirmOrderStatus, 2)
|
||||
assert.match(context.agisoAutoDelivery.errorMessage, /订单仍未进入已发货/)
|
||||
} finally {
|
||||
runtimeConfig.platforms.agiso.appSecret = originalAppSecret
|
||||
runtimeConfig.platforms.agiso.messaging.shops = originalShops
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user