补商户发货API
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"affiliate_dash/internal/model"
|
||||
@@ -71,3 +75,171 @@ func TestDeliveryLinkGenerateAuthorizeAndRevoke(t *testing.T) {
|
||||
t.Fatalf("restored link should be accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliveryMerchantApiBindAndSubmit(t *testing.T) {
|
||||
db := newServiceTestDB(t)
|
||||
merchantID, _ := seedFulfillmentMerchant(t, db, "delivery-api", 5000, 5, 100)
|
||||
product := model.Product{Code: "delivery-api-alt-product", Name: "测试发货商品", Status: model.ProductStatusActive}
|
||||
if err := db.Create(&product).Error; err != nil {
|
||||
t.Fatalf("create product: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.MerchantProduct{
|
||||
MerchantID: merchantID,
|
||||
ProductID: product.ID,
|
||||
SKU: "suit_alan_walker",
|
||||
DisplayName: "测试发货商品",
|
||||
PriceAmount: 100,
|
||||
Currency: "POINT",
|
||||
Stock: 5,
|
||||
Status: model.ProductStatusActive,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create merchant product: %v", err)
|
||||
}
|
||||
|
||||
var bindCalls, boundCalls, queueCreateCalls, queuePatchCalls, upstreamCalls int32
|
||||
var requestLog []string
|
||||
mockBFF := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/sign-proxy" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var payload struct {
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
requestLog = append(requestLog, payload.Method+" "+payload.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch payload.Path {
|
||||
case "/public/goods/detail":
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"good": map[string]interface{}{
|
||||
"title": "测试发货商品",
|
||||
"image": "https://img.example/good.png",
|
||||
},
|
||||
},
|
||||
})
|
||||
case "/public/games/bind-account":
|
||||
atomic.AddInt32(&bindCalls, 1)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"bind_uuid": "bind-123",
|
||||
"url": "https://bind.example/qrcode",
|
||||
},
|
||||
})
|
||||
case "/public/games/account-bound":
|
||||
atomic.AddInt32(&boundCalls, 1)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"gameAccount": map[string]interface{}{
|
||||
"game_account": "4808146277",
|
||||
"game_account_role_name": "巫师哈丁12",
|
||||
"game_account_area": "安卓",
|
||||
"game_account_plat": "QQ",
|
||||
"game_account_channel": "安卓-QQ",
|
||||
},
|
||||
},
|
||||
})
|
||||
case "/public/users/orders-queue":
|
||||
if payload.Method == http.MethodPost {
|
||||
atomic.AddInt32(&queueCreateCalls, 1)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"orders": []map[string]interface{}{
|
||||
{"_id": "queue-1"},
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if payload.Method == http.MethodPatch {
|
||||
atomic.AddInt32(&queuePatchCalls, 1)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{"code": 0, "data": map[string]interface{}{}})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
case "/public/users/orders":
|
||||
atomic.AddInt32(&upstreamCalls, 1)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"order": map[string]interface{}{
|
||||
"order_no": "provider-1",
|
||||
},
|
||||
},
|
||||
})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer mockBFF.Close()
|
||||
|
||||
fulfillmentSvc := NewFulfillmentService(db, nil)
|
||||
deliverySvc := NewDeliveryService(fulfillmentSvc, mockBFF.URL, "dlc", "https://shop.example", "link-secret", 60)
|
||||
|
||||
created, err := fulfillmentSvc.CreateOrder(CreateFulfillmentOrderInput{
|
||||
MerchantID: merchantID,
|
||||
APIClientID: 1,
|
||||
ClientOrderNo: "delivery-api-001",
|
||||
SKU: "suit_alan_walker",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create order: %v", err)
|
||||
}
|
||||
|
||||
info, err := deliverySvc.GetMerchantOrder(merchantID, created.Order.OrderNo)
|
||||
if err != nil {
|
||||
t.Fatalf("get merchant order: %v", err)
|
||||
}
|
||||
if !info.CanShip || info.Product == nil || info.Product.Image == "" {
|
||||
t.Fatalf("merchant order should include delivery data: %+v", info)
|
||||
}
|
||||
|
||||
bind, err := deliverySvc.BindForMerchant(merchantID, created.Order.OrderNo, "4808146277")
|
||||
if err != nil {
|
||||
t.Fatalf("bind for merchant: %v", err)
|
||||
}
|
||||
if bind.BindUUID != "bind-123" || bind.BindURL == "" || bind.QRURL == "" {
|
||||
t.Fatalf("unexpected bind result: %+v", bind)
|
||||
}
|
||||
|
||||
submitted, err := deliverySvc.SubmitForMerchant(merchantID, 88, created.Order.OrderNo, "4808146277", bind.BindUUID)
|
||||
if err != nil {
|
||||
t.Fatalf("submit for merchant: %v", err)
|
||||
}
|
||||
if submitted.Status != model.FulfillmentStatusProcessing || submitted.ProviderOrderNo != "provider-1" {
|
||||
t.Fatalf("unexpected submit result: %+v", submitted)
|
||||
}
|
||||
var afterSubmit model.FulfillmentOrder
|
||||
if err := db.Where("order_no = ?", created.Order.OrderNo).First(&afterSubmit).Error; err != nil {
|
||||
t.Fatalf("reload after submit: %v", err)
|
||||
}
|
||||
if afterSubmit.FulfillmentStatus != model.FulfillmentStatusProcessing || afterSubmit.ProviderOrderNo != "provider-1" {
|
||||
t.Fatalf("submit should persist processing state: %+v", afterSubmit)
|
||||
}
|
||||
if atomic.LoadInt32(&queueCreateCalls) != 1 || atomic.LoadInt32(&upstreamCalls) != 1 {
|
||||
t.Fatalf("first submit should only create one queue, queue=%d upstream=%d requests=%v", queueCreateCalls, upstreamCalls, requestLog)
|
||||
}
|
||||
second, err := deliverySvc.SubmitForMerchant(merchantID, 88, created.Order.OrderNo, "4808146277", bind.BindUUID)
|
||||
if err != nil {
|
||||
t.Fatalf("repeat submit for merchant: %v", err)
|
||||
}
|
||||
if second.ProviderOrderNo != "provider-1" || second.Status != model.FulfillmentStatusProcessing {
|
||||
t.Fatalf("repeat submit should reuse existing result: %+v", second)
|
||||
}
|
||||
if atomic.LoadInt32(&queueCreateCalls) != 1 || atomic.LoadInt32(&upstreamCalls) != 1 {
|
||||
t.Fatalf("submit should be idempotent, queue=%d upstream=%d requests=%v", queueCreateCalls, upstreamCalls, requestLog)
|
||||
}
|
||||
if atomic.LoadInt32(&bindCalls) != 1 || atomic.LoadInt32(&queuePatchCalls) != 1 || atomic.LoadInt32(&boundCalls) != 1 {
|
||||
t.Fatalf("unexpected mock counts bind=%d bound=%d patch=%d", bindCalls, boundCalls, queuePatchCalls)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user