后端补充高风险链路测试
This commit is contained in:
@@ -169,3 +169,105 @@ test('redeemClaimTaskWithInventoryFallbackWithDeps invalidates missing CDKEY and
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
test('redeemClaimTaskWithInventoryFallbackWithDeps waits for inventory when used code has no replacement', async () => {
|
||||
const events = []
|
||||
const consumed = []
|
||||
const reserveCalls = []
|
||||
|
||||
const context = {
|
||||
task: {
|
||||
id: 57,
|
||||
browser_session_id: 'txbs-used',
|
||||
},
|
||||
orderItem: {
|
||||
sku_code: 'df-cdk-used',
|
||||
},
|
||||
}
|
||||
const initialInventoryItem = {
|
||||
id: 7,
|
||||
display_value: 'USED1234CODE5678',
|
||||
credential_type: 'tencent_code',
|
||||
inventory_group_code: 'B组',
|
||||
}
|
||||
|
||||
let thrown = null
|
||||
try {
|
||||
await redeemClaimTaskWithInventoryFallbackWithDeps(
|
||||
context,
|
||||
initialInventoryItem,
|
||||
{
|
||||
redeemTencentBrowserSession: async () => ({
|
||||
redeem: {
|
||||
final: {
|
||||
redeem: {
|
||||
popup: {
|
||||
text: '兑换结果',
|
||||
detail: '兑换码已使用。',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
markInventoryItemConsumed: async (inventoryItemId, reason, updatedAt) => {
|
||||
consumed.push({ inventoryItemId, reason, updatedAt })
|
||||
return { id: inventoryItemId, status: 'consumed', invalid_reason: reason, updated_at: updatedAt }
|
||||
},
|
||||
reserveInventoryForTask: async (payload) => {
|
||||
reserveCalls.push(payload)
|
||||
return null
|
||||
},
|
||||
createTaskEvent: async (taskId, eventType, payload, createdAt) => {
|
||||
events.push({ taskId, eventType, payload, createdAt })
|
||||
},
|
||||
nowIso: () => '2026-04-14T12:05:00.000Z',
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
thrown = error
|
||||
}
|
||||
|
||||
assert.ok(thrown)
|
||||
assert.equal(thrown.errorCode, 'claim_inventory_replacement_exhausted')
|
||||
assert.equal(thrown.redeemTaskState.taskStatus, 'waiting_inventory')
|
||||
assert.equal(thrown.redeemTaskState.inventoryStatus, 'pending')
|
||||
assert.equal(thrown.redeemTaskState.deliveryStatus, 'pending')
|
||||
assert.equal(thrown.redeemTaskState.attempts.length, 1)
|
||||
assert.deepEqual(consumed, [
|
||||
{
|
||||
inventoryItemId: 7,
|
||||
reason: '兑换码已使用。',
|
||||
updatedAt: '2026-04-14T12:05:00.000Z',
|
||||
},
|
||||
])
|
||||
assert.deepEqual(reserveCalls, [
|
||||
{
|
||||
skuCode: 'df-cdk-used',
|
||||
taskId: 57,
|
||||
credentialType: 'tencent_code',
|
||||
roleKey: 'primary_code',
|
||||
inventoryGroupCodes: ['B组'],
|
||||
},
|
||||
])
|
||||
assert.deepEqual(
|
||||
events.map((event) => ({
|
||||
taskId: event.taskId,
|
||||
eventType: event.eventType,
|
||||
payload: event.payload,
|
||||
createdAt: event.createdAt,
|
||||
})),
|
||||
[
|
||||
{
|
||||
taskId: 57,
|
||||
eventType: 'claim_redeem_code_used',
|
||||
payload: {
|
||||
inventoryItemId: 7,
|
||||
codeMasked: 'USED****5678',
|
||||
resultCode: '',
|
||||
resultMessage: '兑换码已使用。',
|
||||
},
|
||||
createdAt: '2026-04-14T12:05:00.000Z',
|
||||
},
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import crypto from 'node:crypto'
|
||||
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import {
|
||||
buildWebhookEventInput,
|
||||
parseAgisoTradeRequest,
|
||||
resolveAgisoTradeIgnoreReason,
|
||||
shouldEnrichAgisoXianyuTrade,
|
||||
@@ -147,3 +148,35 @@ test('verifyAgisoSignatureWithSecret supports documented and legacy signatures',
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
test('buildWebhookEventInput preserves raw request payload for later replay', () => {
|
||||
const input = buildWebhookEventInput(
|
||||
{
|
||||
headers: { 'x-request-id': 'req-1' },
|
||||
query: { timestamp: '1713097840', sign: 'bad-sign' },
|
||||
body: { json: '{"biz_order_id":"4502259793206016214"}' },
|
||||
},
|
||||
{
|
||||
provider: 'agiso',
|
||||
platform: 'xianyu',
|
||||
shopId: 'shop-9',
|
||||
shopName: '咸鱼店铺',
|
||||
eventType: 'payment_success',
|
||||
eventKey: 'agiso:xianyu:4502259793206016214:payment_success',
|
||||
signatureValid: false,
|
||||
},
|
||||
)
|
||||
|
||||
assert.equal(input.provider, 'agiso')
|
||||
assert.equal(input.platform, 'xianyu')
|
||||
assert.equal(input.eventType, 'payment_success')
|
||||
assert.equal(input.eventKey, 'agiso:xianyu:4502259793206016214:payment_success')
|
||||
assert.equal(input.signatureValid, false)
|
||||
assert.equal(input.processed, false)
|
||||
assert.equal(input.processError, '')
|
||||
assert.equal(input.relatedOrderId, null)
|
||||
assert.deepEqual(JSON.parse(input.headersJson), { 'x-request-id': 'req-1' })
|
||||
assert.deepEqual(JSON.parse(input.queryJson), { timestamp: '1713097840', sign: 'bad-sign' })
|
||||
assert.deepEqual(JSON.parse(input.bodyJson), { json: '{"biz_order_id":"4502259793206016214"}' })
|
||||
assert.match(input.createdAt, /^\d{4}-\d{2}-\d{2}T/)
|
||||
})
|
||||
|
||||
@@ -32,6 +32,18 @@ test('hasAgisoAutoDeliverySucceeded detects successful context on any task', ()
|
||||
]), false)
|
||||
})
|
||||
|
||||
test('hasAgisoAutoDeliverySucceeded handles string and invalid task context payloads', () => {
|
||||
assert.equal(hasAgisoAutoDeliverySucceeded([
|
||||
{ context_json: '{bad-json' },
|
||||
{ context_json: JSON.stringify({ agisoAutoDelivery: { status: 'success' } }) },
|
||||
]), true)
|
||||
|
||||
assert.equal(hasAgisoAutoDeliverySucceeded([
|
||||
{ context_json: '{bad-json' },
|
||||
{ context_json: JSON.stringify({ agisoAutoDelivery: { status: 'skipped' } }) },
|
||||
]), false)
|
||||
})
|
||||
|
||||
test('isAgisoAutoDeliverySuccess accepts empty successful payloads and explicit success codes', () => {
|
||||
assert.equal(isAgisoAutoDeliverySuccess(200, {}), true)
|
||||
assert.equal(isAgisoAutoDeliverySuccess(200, { IsSuccess: true }), true)
|
||||
|
||||
@@ -247,13 +247,22 @@
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
- `npm test`
|
||||
23. 高风险链路补测试:
|
||||
- webhook 事件入库 payload 可稳定保留原始 headers/query/body,便于后续 replay
|
||||
- 库存换码在“兑换码已使用且无替换库存”时进入等待库存状态,并记录已使用事件
|
||||
- Agiso 自动发货成功标记可从字符串 context 中识别,且会忽略无效 JSON
|
||||
24. Docker 内验证通过:
|
||||
- 相关 3 个测试文件共 14 个用例通过
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
- `npm test` 共 125 个用例通过
|
||||
|
||||
## 下一步建议
|
||||
|
||||
第一批继续推进时,建议按这个顺序:
|
||||
|
||||
1. 为 webhook、库存换码、自动发货补测试
|
||||
2. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块
|
||||
1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块
|
||||
2. 逐步移除服务层 `@ts-nocheck`,优先处理自动发货、claim session、webhook service
|
||||
|
||||
## 执行原则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user