后端补充订单详情服务测试
This commit is contained in:
@@ -16,6 +16,8 @@ const AGISO_SHOP_CONFIG_KEYS = [
|
|||||||
'apiVersion',
|
'apiVersion',
|
||||||
'sendMessageEndpoint',
|
'sendMessageEndpoint',
|
||||||
'tradeDetailEndpoint',
|
'tradeDetailEndpoint',
|
||||||
|
'tradeDetailApiVersion',
|
||||||
|
'tradeDetailTimeoutMs',
|
||||||
]
|
]
|
||||||
|
|
||||||
export function getAgisoShopsFilePath() {
|
export function getAgisoShopsFilePath() {
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ import { parseJsonObject } from '../../../../utils/json.js'
|
|||||||
const DEFAULT_DETAIL_TIMEOUT_MS = 5000
|
const DEFAULT_DETAIL_TIMEOUT_MS = 5000
|
||||||
|
|
||||||
export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {}) {
|
export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {}) {
|
||||||
|
return enrichAgisoXianyuTradeOrderWithDeps(parsed, { requestId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function enrichAgisoXianyuTradeOrderWithDeps(parsed, { requestId = '' } = {}, deps = {}) {
|
||||||
|
const queryOrderDetail = deps.queryAgisoXianyuOrderDetail || queryAgisoXianyuOrderDetail
|
||||||
|
|
||||||
if (!shouldHydrateAgisoXianyuTradeOrder(parsed)) {
|
if (!shouldHydrateAgisoXianyuTradeOrder(parsed)) {
|
||||||
return { parsed, enriched: false, reason: 'not_needed' }
|
return { parsed, enriched: false, reason: 'not_needed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailResult = await queryAgisoXianyuOrderDetail({
|
const detailResult = await queryOrderDetail({
|
||||||
shopId: parsed.shopId,
|
shopId: parsed.shopId,
|
||||||
platformOrderId: parsed.platformOrderId,
|
platformOrderId: parsed.platformOrderId,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -58,6 +64,14 @@ export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function queryAgisoXianyuOrderDetail({ shopId = '', platformOrderId = '', requestId = '' } = {}) {
|
export async function queryAgisoXianyuOrderDetail({ shopId = '', platformOrderId = '', requestId = '' } = {}) {
|
||||||
|
return queryAgisoXianyuOrderDetailWithDeps({ shopId, platformOrderId, requestId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryAgisoXianyuOrderDetailWithDeps(
|
||||||
|
{ shopId = '', platformOrderId = '', requestId = '' } = {},
|
||||||
|
deps = {},
|
||||||
|
) {
|
||||||
|
const requestWithTimeout = deps.fetchWithTimeout || fetchWithTimeout
|
||||||
const normalizedShopId = String(shopId || '').trim()
|
const normalizedShopId = String(shopId || '').trim()
|
||||||
const normalizedPlatformOrderId = String(platformOrderId || '').trim()
|
const normalizedPlatformOrderId = String(platformOrderId || '').trim()
|
||||||
const config = resolveAgisoXianyuTradeDetailConfig(normalizedShopId)
|
const config = resolveAgisoXianyuTradeDetailConfig(normalizedShopId)
|
||||||
@@ -94,7 +108,7 @@ export async function queryAgisoXianyuOrderDetail({ shopId = '', platformOrderId
|
|||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithTimeout(config.endpoint, {
|
const response = await requestWithTimeout(config.endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
body: new URLSearchParams(requestBody).toString(),
|
body: new URLSearchParams(requestBody).toString(),
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import { runtimeConfig } from '../../../../config/runtime.js'
|
||||||
|
import {
|
||||||
|
enrichAgisoXianyuTradeOrderWithDeps,
|
||||||
|
queryAgisoXianyuOrderDetailWithDeps,
|
||||||
|
resolveAgisoXianyuOrderDeliveryState,
|
||||||
|
} from './order-detail-service.js'
|
||||||
|
|
||||||
|
test('resolveAgisoXianyuOrderDeliveryState detects shipped order by ship time or status', () => {
|
||||||
|
assert.deepEqual(resolveAgisoXianyuOrderDeliveryState({ ship_time: 1713097840 }), {
|
||||||
|
shipped: true,
|
||||||
|
shipTime: 1713097840,
|
||||||
|
orderStatus: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(resolveAgisoXianyuOrderDeliveryState({ order_status: 3 }), {
|
||||||
|
shipped: true,
|
||||||
|
shipTime: 0,
|
||||||
|
orderStatus: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(resolveAgisoXianyuOrderDeliveryState({ order_status: 2 }), {
|
||||||
|
shipped: false,
|
||||||
|
shipTime: 0,
|
||||||
|
orderStatus: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('queryAgisoXianyuOrderDetailWithDeps parses successful detail payload and masks network dependency', async () => {
|
||||||
|
const originalShops = runtimeConfig.platforms.agiso.messaging.shops
|
||||||
|
const originalAppSecret = runtimeConfig.platforms.agiso.appSecret
|
||||||
|
const calls = []
|
||||||
|
|
||||||
|
runtimeConfig.platforms.agiso.appSecret = ''
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = {
|
||||||
|
'shop-detail-test': {
|
||||||
|
accessToken: 'access-token',
|
||||||
|
appSecret: 'shop-secret',
|
||||||
|
tradeDetailEndpoint: 'https://example.com/detail',
|
||||||
|
tradeDetailApiVersion: '2',
|
||||||
|
tradeDetailTimeoutMs: '3000',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await queryAgisoXianyuOrderDetailWithDeps({
|
||||||
|
shopId: 'shop-detail-test',
|
||||||
|
platformOrderId: 'P-DETAIL-10001',
|
||||||
|
requestId: 'req-detail',
|
||||||
|
}, {
|
||||||
|
fetchWithTimeout: async (url, options, timeoutMs) => {
|
||||||
|
calls.push({ url, options, timeoutMs })
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
text: async () => JSON.stringify({
|
||||||
|
IsSuccess: true,
|
||||||
|
Data: {
|
||||||
|
total_fee: '25.50',
|
||||||
|
pay_time: '2026-04-14 20:30:40',
|
||||||
|
ship_time: 1713097840,
|
||||||
|
order_status: 3,
|
||||||
|
item: {
|
||||||
|
item_id: 'item-1',
|
||||||
|
sku: 'dnf-cdk-a|商品名称:DNF礼包',
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(result.success, true)
|
||||||
|
assert.equal(result.responseStatus, 200)
|
||||||
|
assert.equal(result.totalAmountFen, 2550)
|
||||||
|
assert.equal(result.shipped, true)
|
||||||
|
assert.equal(result.shipTime, 1713097840)
|
||||||
|
assert.equal(result.orderStatus, 3)
|
||||||
|
assert.equal(calls[0]?.url, 'https://example.com/detail')
|
||||||
|
assert.equal(calls[0]?.timeoutMs, 3000)
|
||||||
|
assert.equal(calls[0]?.options.headers.Authorization, 'Bearer access-token')
|
||||||
|
assert.equal(calls[0]?.options.headers.ApiVersion, '2')
|
||||||
|
assert.match(String(calls[0]?.options.body), /tid=P-DETAIL-10001/)
|
||||||
|
assert.match(String(calls[0]?.options.body), /sign=/)
|
||||||
|
} finally {
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = originalShops
|
||||||
|
runtimeConfig.platforms.agiso.appSecret = originalAppSecret
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('queryAgisoXianyuOrderDetailWithDeps returns business_error with structured message', async () => {
|
||||||
|
const originalShops = runtimeConfig.platforms.agiso.messaging.shops
|
||||||
|
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = {
|
||||||
|
'shop-detail-test': {
|
||||||
|
accessToken: 'access-token',
|
||||||
|
appSecret: 'shop-secret',
|
||||||
|
tradeDetailEndpoint: 'https://example.com/detail',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await queryAgisoXianyuOrderDetailWithDeps({
|
||||||
|
shopId: 'shop-detail-test',
|
||||||
|
platformOrderId: 'P-DETAIL-10002',
|
||||||
|
}, {
|
||||||
|
fetchWithTimeout: async () => ({
|
||||||
|
status: 200,
|
||||||
|
text: async () => JSON.stringify({
|
||||||
|
IsSuccess: false,
|
||||||
|
Error_Msg: '订单不存在',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(result.success, false)
|
||||||
|
assert.equal(result.reason, 'business_error')
|
||||||
|
assert.equal(result.errorMessage, '订单不存在')
|
||||||
|
assert.equal(result.responseStatus, 200)
|
||||||
|
} finally {
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = originalShops
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('queryAgisoXianyuOrderDetailWithDeps skips missing config without fetching', async () => {
|
||||||
|
const originalShops = runtimeConfig.platforms.agiso.messaging.shops
|
||||||
|
const originalAppSecret = runtimeConfig.platforms.agiso.appSecret
|
||||||
|
let fetchCalled = false
|
||||||
|
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = {}
|
||||||
|
runtimeConfig.platforms.agiso.appSecret = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await queryAgisoXianyuOrderDetailWithDeps({
|
||||||
|
shopId: 'missing-shop',
|
||||||
|
platformOrderId: 'P-DETAIL-10003',
|
||||||
|
}, {
|
||||||
|
fetchWithTimeout: async () => {
|
||||||
|
fetchCalled = true
|
||||||
|
return { status: 200, text: async () => '{}' }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(result.success, false)
|
||||||
|
assert.equal(result.reason, 'missing_config')
|
||||||
|
assert.equal(fetchCalled, false)
|
||||||
|
} finally {
|
||||||
|
runtimeConfig.platforms.agiso.messaging.shops = originalShops
|
||||||
|
runtimeConfig.platforms.agiso.appSecret = originalAppSecret
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('enrichAgisoXianyuTradeOrderWithDeps merges detail payload into incomplete trade', async () => {
|
||||||
|
const parsed = {
|
||||||
|
provider: 'agiso',
|
||||||
|
platform: 'xianyu',
|
||||||
|
shopId: 'shop-detail-test',
|
||||||
|
shopName: '',
|
||||||
|
platformOrderId: 'P-DETAIL-10004',
|
||||||
|
totalAmount: 0,
|
||||||
|
paidAt: null,
|
||||||
|
buyerId: '',
|
||||||
|
buyerName: '',
|
||||||
|
receiverContact: '',
|
||||||
|
rawPayload: {},
|
||||||
|
items: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await enrichAgisoXianyuTradeOrderWithDeps(parsed, { requestId: 'req-enrich' }, {
|
||||||
|
queryAgisoXianyuOrderDetail: async (input) => {
|
||||||
|
assert.deepEqual(input, {
|
||||||
|
shopId: 'shop-detail-test',
|
||||||
|
platformOrderId: 'P-DETAIL-10004',
|
||||||
|
requestId: 'req-enrich',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
reason: '',
|
||||||
|
errorMessage: '',
|
||||||
|
responseStatus: 200,
|
||||||
|
payload: {},
|
||||||
|
detailPayload: {
|
||||||
|
total_fee: '19.90',
|
||||||
|
pay_time: '2026-04-14 20:30:40',
|
||||||
|
buyer_id: 'buyer-1',
|
||||||
|
buyer_name: '测试买家',
|
||||||
|
receiver_mobile: '13800138000',
|
||||||
|
shop_name: '详情店铺',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item_id: 'item-1',
|
||||||
|
sku: 'dnf-cdk-a|商品名称:DNF礼包',
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
totalAmountFen: 1990,
|
||||||
|
shipped: false,
|
||||||
|
shipTime: 0,
|
||||||
|
orderStatus: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(result.enriched, true)
|
||||||
|
assert.equal(result.totalAmountFen, 1990)
|
||||||
|
assert.equal(result.parsed.totalAmount, 1990)
|
||||||
|
assert.equal(result.parsed.buyerId, 'buyer-1')
|
||||||
|
assert.equal(result.parsed.buyerName, '测试买家')
|
||||||
|
assert.equal(result.parsed.receiverContact, '13800138000')
|
||||||
|
assert.equal(result.parsed.shopName, '详情店铺')
|
||||||
|
assert.equal(result.parsed.items.length, 1)
|
||||||
|
assert.equal(result.parsed.items[0]?.skuCode, 'dnf-cdk-a')
|
||||||
|
assert.equal(result.parsed.items[0]?.quantity, 2)
|
||||||
|
assert.deepEqual(result.parsed.rawPayload._agisoTradeDetail.total_fee, '19.90')
|
||||||
|
})
|
||||||
@@ -337,6 +337,18 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 131 个用例通过
|
- `npm test` 共 131 个用例通过
|
||||||
|
54. Agiso 订单详情服务迁移前补直接测试:
|
||||||
|
- 新增 `src/services/platforms/agiso/xianyu/order-detail-service.test.js`
|
||||||
|
- 新增 `enrichAgisoXianyuTradeOrderWithDeps` 与 `queryAgisoXianyuOrderDetailWithDeps`,便于隔离订单详情 HTTP 请求
|
||||||
|
- 覆盖发货状态识别、详情成功解析、业务失败、缺配置跳过、详情补全 webhook 订单五个关键分支
|
||||||
|
55. 修复 Agiso 店铺配置白名单:
|
||||||
|
- `tradeDetailApiVersion`
|
||||||
|
- `tradeDetailTimeoutMs`
|
||||||
|
56. Docker 内验证通过:
|
||||||
|
- `src/services/platforms/agiso/xianyu/order-detail-service.test.js` 共 5 个用例通过
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run build`
|
||||||
|
- `npm test` 共 136 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user