524 lines
18 KiB
Go
524 lines
18 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
func TestClassifyRockDeliveryError_TokenMessageWithoutSpaces(t *testing.T) {
|
|
category, _, _ := classifyRockDeliveryError(errors.New("上游返回:该Token已兑换"))
|
|
if category != model.RockIncidentCategoryTokenRedeemed {
|
|
t.Fatalf("应识别无空格 Token 已兑换错误,实际 %s", category)
|
|
}
|
|
if !strings.Contains(category, "token") {
|
|
t.Fatalf("分类内部值应保持稳定,实际 %s", category)
|
|
}
|
|
}
|
|
|
|
func TestRockDeliveryIncident_NoEntitlementLifecycle(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "incident-1",
|
|
Token: "jwt-incident-1",
|
|
RedeemCode: "CODE-INCIDENT-1",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-INCIDENT-1",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
cause := &RockUpstreamStatusError{
|
|
StatusCode: 400,
|
|
Body: `{"error":"用户不存在可以兑换的可用权益"}`,
|
|
}
|
|
incident, err := svc.RecordRockDeliveryIncident(ctx, &token, token.AssignedOrderNo, cause)
|
|
if err != nil {
|
|
t.Fatalf("记录异常失败: %v", err)
|
|
}
|
|
if incident.Category != model.RockIncidentCategoryNoEntitlement || incident.Occurrences != 1 {
|
|
t.Fatalf("异常分类或次数错误: %+v", incident)
|
|
}
|
|
if _, err := svc.RecordRockDeliveryIncident(ctx, &token, token.AssignedOrderNo, cause); err != nil {
|
|
t.Fatalf("重复记录异常失败: %v", err)
|
|
}
|
|
|
|
var stored model.RockToken
|
|
if err := db.First(&stored, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if stored.RedeemLastError == "" {
|
|
t.Fatal("Token 应记录最近一次发货错误")
|
|
}
|
|
blocking, err := svc.HasBlockingRockIncident(ctx, token.AssignedOrderNo)
|
|
if err != nil || blocking == nil || blocking.Category != model.RockIncidentCategoryNoEntitlement {
|
|
t.Fatalf("应查询到阻塞重试的异常: incident=%+v err=%v", blocking, err)
|
|
}
|
|
var open model.RockDeliveryIncident
|
|
if err := db.Where("token_id = ? AND status = ?", token.ID, model.RockIncidentStatusOpen).
|
|
First(&open).Error; err != nil {
|
|
t.Fatalf("读取开放异常失败: %v", err)
|
|
}
|
|
if open.Occurrences != 2 {
|
|
t.Fatalf("重复错误应累加次数,实际 %d", open.Occurrences)
|
|
}
|
|
|
|
list, total, err := svc.ListTokens(ctx, 1, 20, "no_entitlement", "")
|
|
if err != nil {
|
|
t.Fatalf("筛选无权益失败: %v", err)
|
|
}
|
|
if total != 1 || len(list) != 1 || !list[0].HasOpenIncident {
|
|
t.Fatalf("无权益筛选结果错误: total=%d list=%+v", total, list)
|
|
}
|
|
anomalyList, anomalyTotal, err := svc.ListTokens(ctx, 1, 20, model.RockStatusAnomaly, "")
|
|
if err != nil {
|
|
t.Fatalf("筛选异常失败: %v", err)
|
|
}
|
|
if anomalyTotal != 0 || len(anomalyList) != 0 {
|
|
t.Fatalf("无权益不能重复归入异常: total=%d list=%+v", anomalyTotal, anomalyList)
|
|
}
|
|
stats, err := svc.GetStats(ctx)
|
|
if err != nil {
|
|
t.Fatalf("读取统计失败: %v", err)
|
|
}
|
|
if stats.NoEntitlementStock != 1 {
|
|
t.Fatalf("无权益统计错误: %d", stats.NoEntitlementStock)
|
|
}
|
|
if stats.AnomalyStock != 0 || stats.UnresolvedAnomalyStock != 0 {
|
|
t.Fatalf("无权益不应计入异常统计: anomaly=%d unresolved=%d",
|
|
stats.AnomalyStock, stats.UnresolvedAnomalyStock)
|
|
}
|
|
|
|
actor := uint(9)
|
|
if err := svc.ResolveRockDeliveryIncident(ctx, open.ID, "已人工核对", &actor); err != nil {
|
|
t.Fatalf("处理异常失败: %v", err)
|
|
}
|
|
blocking, err = svc.HasBlockingRockIncident(ctx, token.AssignedOrderNo)
|
|
if err != nil {
|
|
t.Fatalf("处理后查询异常失败: %v", err)
|
|
}
|
|
if blocking != nil {
|
|
t.Fatalf("处理后不应继续阻塞重试: %+v", blocking)
|
|
}
|
|
}
|
|
|
|
func TestRockDeliveryIncident_OtherErrorsUseAnomalyFilter(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "incident-2",
|
|
Token: "jwt-incident-2",
|
|
RedeemCode: "CODE-INCIDENT-2",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-INCIDENT-2",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if _, err := svc.RecordRockDeliveryIncident(ctx, &token, token.AssignedOrderNo,
|
|
errors.New("上游兑换发货接口返回错误码 400: 同一角色无法重复兑换已兑换过的道具")); err != nil {
|
|
t.Fatalf("记录异常失败: %v", err)
|
|
}
|
|
noEntitlement, totalNoEntitlement, err := svc.ListTokens(ctx, 1, 20, "no_entitlement", "")
|
|
if err != nil {
|
|
t.Fatalf("筛选无权益失败: %v", err)
|
|
}
|
|
if totalNoEntitlement != 0 || len(noEntitlement) != 0 {
|
|
t.Fatalf("其他错误不应进入无权益: %+v", noEntitlement)
|
|
}
|
|
anomaly, totalAnomaly, err := svc.ListTokens(ctx, 1, 20, model.RockStatusAnomaly, "")
|
|
if err != nil {
|
|
t.Fatalf("筛选异常失败: %v", err)
|
|
}
|
|
if totalAnomaly != 1 || len(anomaly) != 1 || anomaly[0].IncidentCategory != model.RockIncidentCategoryDuplicateItem {
|
|
t.Fatalf("其他错误应进入异常: total=%d list=%+v", totalAnomaly, anomaly)
|
|
}
|
|
}
|
|
|
|
func TestRockIncidentRetry_RequiresRetryableCategoryAndResolvesAfterApproval(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "incident-retry-1",
|
|
Token: "jwt-incident-retry-1",
|
|
RedeemCode: "CODE-INCIDENT-RETRY-1",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-INCIDENT-RETRY-1",
|
|
ExpectedUID: "5550001",
|
|
ActualRoleID: "5550001",
|
|
ActualRoleName: "测试角色",
|
|
}
|
|
order := model.FulfillmentOrder{
|
|
OrderNo: token.AssignedOrderNo,
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusShipFailed,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatalf("创建订单失败: %v", err)
|
|
}
|
|
incident, err := svc.RecordRockIncident(ctx, &token, order.OrderNo,
|
|
model.RockIncidentCategoryUpstreamTimeout, "上游超时", 504, "")
|
|
if err != nil {
|
|
t.Fatalf("记录异常失败: %v", err)
|
|
}
|
|
actor := uint(9)
|
|
plan, err := svc.ApproveRockIncidentRetry(ctx, incident.ID, "已核对上游,允许一次重试", &actor)
|
|
if err != nil {
|
|
t.Fatalf("批准人工重试失败: %v", err)
|
|
}
|
|
if plan.Token.ID != token.ID || plan.Order.OrderNo != order.OrderNo {
|
|
t.Fatalf("重试计划关联错误: %+v", plan)
|
|
}
|
|
var unblocked model.RockDeliveryIncident
|
|
if err := db.First(&unblocked, incident.ID).Error; err != nil {
|
|
t.Fatalf("读取异常失败: %v", err)
|
|
}
|
|
if unblocked.Status != model.RockIncidentStatusOpen || unblocked.BlockingRetry {
|
|
t.Fatalf("批准重试后应暂时解除阻塞: %+v", unblocked)
|
|
}
|
|
if err := svc.CompleteRockIncidentRetry(ctx, token.ID, order.OrderNo, "重试成功", &actor); err != nil {
|
|
t.Fatalf("完成人工重试失败: %v", err)
|
|
}
|
|
var resolved model.RockDeliveryIncident
|
|
if err := db.First(&resolved, incident.ID).Error; err != nil {
|
|
t.Fatalf("读取已处理异常失败: %v", err)
|
|
}
|
|
if resolved.Status != model.RockIncidentStatusResolved ||
|
|
resolved.ResolutionAction != "approve_retry" ||
|
|
resolved.ResolvedBy == nil || *resolved.ResolvedBy != actor {
|
|
t.Fatalf("异常关闭结果错误: %+v", resolved)
|
|
}
|
|
}
|
|
|
|
func TestRockIncidentRetry_RejectsNoEntitlement(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "incident-retry-2",
|
|
Token: "jwt-incident-retry-2",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-INCIDENT-RETRY-2",
|
|
ExpectedUID: "5550002",
|
|
}
|
|
order := model.FulfillmentOrder{
|
|
OrderNo: token.AssignedOrderNo,
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusShipFailed,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatalf("创建订单失败: %v", err)
|
|
}
|
|
incident, err := svc.RecordRockIncident(ctx, &token, order.OrderNo,
|
|
model.RockIncidentCategoryNoEntitlement, "用户不存在可以兑换的可用权益", 400, "")
|
|
if err != nil {
|
|
t.Fatalf("记录异常失败: %v", err)
|
|
}
|
|
if _, err := svc.ApproveRockIncidentRetry(ctx, incident.ID, "尝试重试", nil); err == nil {
|
|
t.Fatal("无权益异常不允许直接重试")
|
|
}
|
|
}
|
|
|
|
func TestRockProviderCancel_UsingTokenReturnsToStock(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
provider := NewRockProvider(svc)
|
|
ctx := context.Background()
|
|
usingAt := time.Now().Add(-10 * time.Minute)
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "cancel-using",
|
|
Token: "jwt-cancel-using",
|
|
Status: model.RockStatusUsing,
|
|
AssignedOrderNo: "ORDER-CANCEL-USING",
|
|
ExpectedUID: "5550003",
|
|
UsingAt: &usingAt,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if err := provider.Cancel(ctx, &model.FulfillmentOrder{OrderNo: token.AssignedOrderNo}); err != nil {
|
|
t.Fatalf("取消未绑定订单失败: %v", err)
|
|
}
|
|
var saved model.RockToken
|
|
if err := db.First(&saved, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if saved.Status != model.RockStatusAvailable || saved.AssignedOrderNo != "" ||
|
|
saved.ExpectedUID != "" || saved.UsingAt != nil {
|
|
t.Fatalf("未绑定 Token 应安全回库存: %+v", saved)
|
|
}
|
|
var incidentCount int64
|
|
if err := db.Model(&model.RockDeliveryIncident{}).
|
|
Where("token_id = ? AND status = ?", token.ID, model.RockIncidentStatusOpen).
|
|
Count(&incidentCount).Error; err != nil {
|
|
t.Fatalf("统计异常失败: %v", err)
|
|
}
|
|
if incidentCount != 0 {
|
|
t.Fatalf("未绑定订单取消不应生成异常,实际 %d", incidentCount)
|
|
}
|
|
}
|
|
|
|
func TestRockProviderCancel_BoundAndRedeemedCreateOrderCancelledIncident(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
status string
|
|
}{
|
|
{name: "bound", status: model.RockStatusBound},
|
|
{name: "redeemed", status: model.RockStatusRedeemed},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
provider := NewRockProvider(svc)
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "cancel-" + tc.name,
|
|
Token: "jwt-cancel-" + tc.name,
|
|
Status: tc.status,
|
|
AssignedOrderNo: "ORDER-CANCEL-" + tc.name,
|
|
ExpectedUID: "5550004",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if err := provider.Cancel(context.Background(),
|
|
&model.FulfillmentOrder{OrderNo: token.AssignedOrderNo}); err != nil {
|
|
t.Fatalf("取消订单失败: %v", err)
|
|
}
|
|
var stored model.RockToken
|
|
if err := db.First(&stored, token.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if stored.Status != tc.status || stored.AssignedOrderNo != token.AssignedOrderNo {
|
|
t.Fatalf("已绑定或已发货 Token 不应自动释放: %+v", stored)
|
|
}
|
|
var incident model.RockDeliveryIncident
|
|
if err := db.Where(
|
|
"token_id = ? AND order_no = ? AND status = ? AND category = ?",
|
|
token.ID,
|
|
token.AssignedOrderNo,
|
|
model.RockIncidentStatusOpen,
|
|
model.RockIncidentCategoryOrderCancelled,
|
|
).First(&incident).Error; err != nil {
|
|
t.Fatalf("应生成订单取消冲突异常: %v", err)
|
|
}
|
|
if !incident.BlockingRetry || incident.Message == "" {
|
|
t.Fatalf("订单取消冲突异常应阻塞重试并带说明: %+v", incident)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReconcileRockOrders_DoesNotBackfillHistoricalIncidents(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
tokens := []model.RockToken{
|
|
{
|
|
UpstreamTokenID: "reconcile-old-failed",
|
|
Token: "jwt-reconcile-old-failed",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-RECONCILE-OLD-FAILED",
|
|
},
|
|
{
|
|
UpstreamTokenID: "reconcile-old-cancel",
|
|
Token: "jwt-reconcile-old-cancel",
|
|
Status: model.RockStatusRedeemed,
|
|
AssignedOrderNo: "ORDER-RECONCILE-OLD-CANCEL",
|
|
},
|
|
}
|
|
orders := []model.FulfillmentOrder{
|
|
{
|
|
OrderNo: tokens[0].AssignedOrderNo,
|
|
ClientOrderNo: "CLIENT-RECONCILE-OLD-FAILED",
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusShipFailed,
|
|
FailureReason: "用户不存在可以兑换的可用权益",
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
},
|
|
{
|
|
OrderNo: tokens[1].AssignedOrderNo,
|
|
ClientOrderNo: "CLIENT-RECONCILE-OLD-CANCEL",
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusCancelled,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
},
|
|
}
|
|
for i := range tokens {
|
|
if err := db.Create(&tokens[i]).Error; err != nil {
|
|
t.Fatalf("创建历史 Token 失败: %v", err)
|
|
}
|
|
if err := db.Create(&orders[i]).Error; err != nil {
|
|
t.Fatalf("创建历史订单失败: %v", err)
|
|
}
|
|
}
|
|
svc.SetRedeemConfirmHook(func(context.Context, *model.FulfillmentOrder, *model.RockToken) error {
|
|
return nil
|
|
})
|
|
if err := svc.ReconcileRockOrders(ctx); err != nil {
|
|
t.Fatalf("执行对账失败: %v", err)
|
|
}
|
|
var count int64
|
|
if err := db.Model(&model.RockDeliveryIncident{}).Count(&count).Error; err != nil {
|
|
t.Fatalf("统计异常失败: %v", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("历史失败/取消记录不应自动回填异常,实际 %d", count)
|
|
}
|
|
}
|
|
|
|
func TestPendingOrderDoesNotEnterShipTimeout(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewFulfillmentService(db, nil)
|
|
old := time.Now().Add(-time.Hour)
|
|
order := model.FulfillmentOrder{
|
|
OrderNo: "ORDER-PENDING-TIMEOUT",
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusDelivering,
|
|
ResultData: `{"provider_order_stage":"redeem_pending","rock_redeem_pending":true}`,
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatalf("创建订单失败: %v", err)
|
|
}
|
|
if err := db.Model(&model.FulfillmentOrder{}).Where("id = ?", order.ID).
|
|
UpdateColumn("updated_at", old).Error; err != nil {
|
|
t.Fatalf("调整订单时间失败: %v", err)
|
|
}
|
|
changed, err := svc.MarkProcessingTimeouts(30*time.Minute, 10)
|
|
if err != nil {
|
|
t.Fatalf("执行超时扫描失败: %v", err)
|
|
}
|
|
if changed != 0 {
|
|
t.Fatalf("pending 订单不应被超时任务标记失败,实际变更 %d", changed)
|
|
}
|
|
var stored model.FulfillmentOrder
|
|
if err := db.First(&stored, order.ID).Error; err != nil {
|
|
t.Fatalf("读取订单失败: %v", err)
|
|
}
|
|
if stored.OrderStatus != model.OrderStatusDelivering {
|
|
t.Fatalf("pending 订单应保持 delivering,实际 %s", stored.OrderStatus)
|
|
}
|
|
}
|
|
|
|
func TestBlockingIncidentPreventsDeliveryRetry(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
provider := NewRockProvider(svc)
|
|
ctx := context.Background()
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "incident-block",
|
|
Token: "jwt-incident-block",
|
|
RedeemCode: "CODE-INCIDENT-BLOCK",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-INCIDENT-BLOCK",
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if _, err := svc.RecordRockDeliveryIncident(ctx, &token, token.AssignedOrderNo,
|
|
errors.New("用户不存在可以兑换的可用权益")); err != nil {
|
|
t.Fatalf("记录异常失败: %v", err)
|
|
}
|
|
state, err := provider.PrepareDelivery(ctx, &model.FulfillmentOrder{
|
|
OrderNo: token.AssignedOrderNo,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("准备发货失败: %v", err)
|
|
}
|
|
if state.CanShip {
|
|
t.Fatal("存在开放异常时不得再次调用上游发货")
|
|
}
|
|
}
|
|
|
|
func TestReconcileRockOrdersRepairsTokenAndOrderStatus(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
svc := NewRockService(db, NewRockClient("https://example.com"))
|
|
ctx := context.Background()
|
|
redeemedAt := time.Now().Add(-time.Hour)
|
|
token := model.RockToken{
|
|
UpstreamTokenID: "reconcile-1",
|
|
Token: "jwt-reconcile-1",
|
|
RedeemCode: "CODE-RECONCILE-1",
|
|
Status: model.RockStatusRedeemed,
|
|
AssignedOrderNo: "ORDER-RECONCILE-1",
|
|
RedeemedAt: &redeemedAt,
|
|
}
|
|
order := model.FulfillmentOrder{
|
|
OrderNo: token.AssignedOrderNo,
|
|
ClientOrderNo: "CLIENT-RECONCILE-1",
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusPaid,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
}
|
|
if err := db.Create(&token).Error; err != nil {
|
|
t.Fatalf("创建 Token 失败: %v", err)
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatalf("创建订单失败: %v", err)
|
|
}
|
|
svc.SetRedeemConfirmHook(func(ctx context.Context, order *model.FulfillmentOrder, token *model.RockToken) error {
|
|
return db.Model(order).Update("order_status", model.OrderStatusDelivered).Error
|
|
})
|
|
if err := svc.ReconcileRockOrders(ctx); err != nil {
|
|
t.Fatalf("订单对账失败: %v", err)
|
|
}
|
|
var repairedOrder model.FulfillmentOrder
|
|
if err := db.First(&repairedOrder, order.ID).Error; err != nil {
|
|
t.Fatalf("读取订单失败: %v", err)
|
|
}
|
|
if repairedOrder.OrderStatus != model.OrderStatusDelivered {
|
|
t.Fatalf("redeemed Token 应修复订单为 delivered,实际 %s", repairedOrder.OrderStatus)
|
|
}
|
|
|
|
deliveredAt := time.Now()
|
|
legacyToken := model.RockToken{
|
|
UpstreamTokenID: "reconcile-2",
|
|
Token: "jwt-reconcile-2",
|
|
RedeemCode: "CODE-RECONCILE-2",
|
|
Status: model.RockStatusBound,
|
|
AssignedOrderNo: "ORDER-RECONCILE-2",
|
|
RedeemedAt: &redeemedAt,
|
|
}
|
|
deliveredOrder := model.FulfillmentOrder{
|
|
OrderNo: legacyToken.AssignedOrderNo,
|
|
ClientOrderNo: "CLIENT-RECONCILE-2",
|
|
MerchantID: 1,
|
|
OrderStatus: model.OrderStatusDelivered,
|
|
DeliveredAt: &deliveredAt,
|
|
ProductSKU: model.RockSKUBlackTshirt,
|
|
}
|
|
if err := db.Create(&legacyToken).Error; err != nil {
|
|
t.Fatalf("创建历史 Token 失败: %v", err)
|
|
}
|
|
if err := db.Create(&deliveredOrder).Error; err != nil {
|
|
t.Fatalf("创建已交付订单失败: %v", err)
|
|
}
|
|
if err := svc.ReconcileRockOrders(ctx); err != nil {
|
|
t.Fatalf("Token 对账失败: %v", err)
|
|
}
|
|
var repairedToken model.RockToken
|
|
if err := db.First(&repairedToken, legacyToken.ID).Error; err != nil {
|
|
t.Fatalf("读取 Token 失败: %v", err)
|
|
}
|
|
if repairedToken.Status != model.RockStatusRedeemed {
|
|
t.Fatalf("已交付订单的 bound Token 应修复为 redeemed,实际 %s", repairedToken.Status)
|
|
}
|
|
}
|