后端补充自动发货服务测试

This commit is contained in:
yml
2026-05-21 14:42:19 +08:00
parent 0d33778d4b
commit 861b7e0034
3 changed files with 220 additions and 16 deletions
@@ -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
}
})