在保持 DLC 默认零破坏的前提下,支持 priority/仅乐玩/仅 DLC 履约策略与后台 GUI 配置;同步补齐 Prepare 渠道落库、乐玩订单取消、Webhook 验签与失败重试。 Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
5.1 KiB
Go
125 lines
5.1 KiB
Go
package lewan
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestOfficialVectors(t *testing.T) {
|
|
// 固定密钥与时间戳
|
|
accessKey := "ak_test_fixed_20260905"
|
|
secretKey := "sk_test_fixed_20260905"
|
|
timestamp := "1788566400"
|
|
_ = accessKey
|
|
|
|
// Vector 1: GET 查询
|
|
t.Run("Vector1_GET_Query", func(t *testing.T) {
|
|
method := "GET"
|
|
path := "/openapi/v1/products"
|
|
rawQuery := "pageSize=20&page=1"
|
|
nonce := "nonce_get_20260905_fixed"
|
|
bodySHA256 := EmptyBodySHA256
|
|
expectedSig := "54ea478585e61230be670d664745c81ffb4907b87c8592bf082c9cc894232adf"
|
|
|
|
signingStr := BuildSigningString(method, path, rawQuery, bodySHA256, timestamp, nonce)
|
|
expectedSigningStr := "GET\n/openapi/v1/products\npage=1&pageSize=20\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n1788566400\nnonce_get_20260905_fixed"
|
|
if signingStr != expectedSigningStr {
|
|
t.Fatalf("signing string mismatch:\ngot:\n%s\nwant:\n%s", signingStr, expectedSigningStr)
|
|
}
|
|
|
|
sig := SignRequest(secretKey, signingStr)
|
|
if sig != expectedSig {
|
|
t.Fatalf("signature mismatch:\ngot: %s\nwant: %s", sig, expectedSig)
|
|
}
|
|
})
|
|
|
|
// Vector 2: POST JSON
|
|
t.Run("Vector2_POST_JSON", func(t *testing.T) {
|
|
method := "POST"
|
|
path := "/openapi/v1/orders"
|
|
rawQuery := ""
|
|
body := []byte(`{"callerOrderId":"DOC-20260905-001","roleId":"1148711973","items":[{"productId":"00000000-0000-4000-8000-000000000101","quantity":2}]}`)
|
|
nonce := "nonce_post_20260905_fixed"
|
|
expectedBodySHA := "146e687459b70c7fdf2781f72593306a8041aa0d5212207459dc71cd76e59032"
|
|
expectedSig := "1e1d3df46b885e76b1e992585215e488c29315e7207e62df4aa26b0124d04968"
|
|
|
|
bodySHA := SHA256Hex(body)
|
|
if bodySHA != expectedBodySHA {
|
|
t.Fatalf("body SHA256 mismatch:\ngot: %s\nwant: %s", bodySHA, expectedBodySHA)
|
|
}
|
|
|
|
signingStr := BuildSigningString(method, path, rawQuery, bodySHA, timestamp, nonce)
|
|
expectedSigningStr := "POST\n/openapi/v1/orders\n\n146e687459b70c7fdf2781f72593306a8041aa0d5212207459dc71cd76e59032\n1788566400\nnonce_post_20260905_fixed"
|
|
if signingStr != expectedSigningStr {
|
|
t.Fatalf("signing string mismatch:\ngot:\n%s\nwant:\n%s", signingStr, expectedSigningStr)
|
|
}
|
|
|
|
sig := SignRequest(secretKey, signingStr)
|
|
if sig != expectedSig {
|
|
t.Fatalf("signature mismatch:\ngot: %s\nwant: %s", sig, expectedSig)
|
|
}
|
|
})
|
|
|
|
// Vector 3: 重复查询参数
|
|
t.Run("Vector3_Duplicate_Query", func(t *testing.T) {
|
|
method := "GET"
|
|
path := "/openapi/v1/orders"
|
|
rawQuery := "status=SUCCEEDED&keyword=%E4%BA%91%20%E8%A7%A6%E8%A7%92&status=FAILED"
|
|
nonce := "nonce_repeat_20260905_fixed"
|
|
expectedSig := "c1c2be18372e3ac89e8042f3477fff8287126e31777a18a593c0cce7028626d8"
|
|
|
|
normQuery := NormalizeQuery(rawQuery)
|
|
expectedNormQuery := "keyword=%E4%BA%91%20%E8%A7%A6%E8%A7%92&status=FAILED&status=SUCCEEDED"
|
|
if normQuery != expectedNormQuery {
|
|
t.Fatalf("normalized query mismatch:\ngot: %s\nwant: %s", normQuery, expectedNormQuery)
|
|
}
|
|
|
|
signingStr := BuildSigningString(method, path, rawQuery, EmptyBodySHA256, timestamp, nonce)
|
|
sig := SignRequest(secretKey, signingStr)
|
|
if sig != expectedSig {
|
|
t.Fatalf("signature mismatch:\ngot: %s\nwant: %s", sig, expectedSig)
|
|
}
|
|
})
|
|
|
|
// Vector 4: POST 空请求体
|
|
t.Run("Vector4_POST_Empty_Body", func(t *testing.T) {
|
|
method := "POST"
|
|
path := "/openapi/v1/orders/00000000-0000-4000-8000-000000000201/account/release"
|
|
rawQuery := ""
|
|
nonce := "nonce_empty_20260905_fixed"
|
|
expectedSig := "167f6d7c116fc7e51b0d2418e65a0705f08076940025f2c013adbd8f27648908"
|
|
|
|
signingStr := BuildSigningString(method, path, rawQuery, EmptyBodySHA256, timestamp, nonce)
|
|
sig := SignRequest(secretKey, signingStr)
|
|
if sig != expectedSig {
|
|
t.Fatalf("signature mismatch:\ngot: %s\nwant: %s", sig, expectedSig)
|
|
}
|
|
})
|
|
|
|
// Vector 5: Webhook 固定向量
|
|
t.Run("Vector5_Webhook", func(t *testing.T) {
|
|
webhookSecret := "whsec_test_fixed_20260905"
|
|
eventID := "00000000-0000-4000-8000-000000000302"
|
|
rawBody := []byte(`{"applicationId":"00000000-0000-4000-8000-000000000301","data":{"callerOrderId":"DOC-20260905-001","failedQuantity":1,"failureCodes":["SUPPLIER_REJECTED"],"orderId":"00000000-0000-4000-8000-000000000303","pendingQuantity":0,"releasedQuantity":0,"status":"PARTIAL_SUCCESS","succeededQuantity":1},"eventId":"00000000-0000-4000-8000-000000000302","occurredAt":"2026-09-05T00:00:00.000Z","orderVersion":7,"schemaVersion":"1.0","type":"ORDER_COMPLETED"}`)
|
|
expectedSig := "v1=ee3968ef467a4f9c19c0a33e7af1bb3118fab8d02c5ebf9cbd8001cc34ebe6be"
|
|
|
|
sig := SignWebhook(webhookSecret, timestamp, eventID, rawBody)
|
|
if sig != expectedSig {
|
|
t.Fatalf("webhook signature mismatch:\ngot: %s\nwant: %s", sig, expectedSig)
|
|
}
|
|
|
|
// 测试验证函数(不限时限)
|
|
if !VerifyWebhook(webhookSecret, sig, timestamp, eventID, rawBody, 0) {
|
|
t.Fatalf("VerifyWebhook should succeed")
|
|
}
|
|
// 测试错误 secret
|
|
if VerifyWebhook("wrong_secret", sig, timestamp, eventID, rawBody, 0) {
|
|
t.Fatalf("VerifyWebhook should fail with wrong secret")
|
|
}
|
|
// 测试时间窗口超限
|
|
if VerifyWebhook(webhookSecret, sig, timestamp, eventID, rawBody, 5*time.Minute) {
|
|
t.Fatalf("VerifyWebhook should fail for expired timestamp")
|
|
}
|
|
})
|
|
}
|