This commit is contained in:
daniel.w 2024-10-22 17:12:05 +08:00
parent 73f91be649
commit 352f11a792
14 changed files with 1333 additions and 79 deletions

View File

@ -1,19 +1,13 @@
use digimon_order; use digimon_order;
db.order.createIndex({ "business_id": 1, "order_status": 1, "create_time": -1 })
db.orders.createIndex({ "reference_id": 1, "reference_uid": 1, "business_id": 1, "uid": 1 })
db.orders.createIndex({ "order_type": 1, "direction_type": 1, "order_status": 1 })
db.orders.createIndex({ "create_time": -1, "update_time": -1, "order_arrival_time": -1, "order_payment_time": -1 })
db.order.createIndex({ db.order.createIndex({
"uid": 1, "uid": 1,
"order_type": 1, "order_type": 1,
"order_status": 1, "order_status": 1,
"create_time": 1, "create_time": -1,
})
db.order.createIndex({
"business_id": 1,
})
// 查詢時全部要 1&2 有幾個類用幾個累才會中索引
db.order.createIndex({
"uid": 1,
"direction_type": 1,
"crypto_type": 1
}) })

View File

@ -152,8 +152,6 @@ service OrderService {
rpc CreateOrder(CreateOrderReq) returns (OKResp); rpc CreateOrder(CreateOrderReq) returns (OKResp);
// CancelOrder // CancelOrder
rpc CancelOrder(CancelOrderReq) returns (OKResp); rpc CancelOrder(CancelOrderReq) returns (OKResp);
// ModifyOrder
rpc ModifyOrder(ModifyOrderReq) returns (OKResp);
// ModifyOrderStatus // ModifyOrderStatus
rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp); rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp);
// DeleteOrder // DeleteOrder

4
go.mod
View File

@ -5,7 +5,9 @@ go 1.22.3
require ( require (
code.30cm.net/digimon/library-go/errs v1.2.5 code.30cm.net/digimon/library-go/errs v1.2.5
code.30cm.net/digimon/library-go/validator v1.0.0 code.30cm.net/digimon/library-go/validator v1.0.0
github.com/go-playground/validator/v10 v10.22.0
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
github.com/stretchr/testify v1.9.0
github.com/zeromicro/go-zero v1.7.2 github.com/zeromicro/go-zero v1.7.2
go.mongodb.org/mongo-driver v1.16.1 go.mongodb.org/mongo-driver v1.16.1
go.uber.org/mock v0.4.0 go.uber.org/mock v0.4.0
@ -31,7 +33,6 @@ require (
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
@ -54,6 +55,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.55.0 // indirect

View File

@ -0,0 +1,109 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
model "app-cloudep-order-server/internal/model/mongo"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/mock/gomock"
"testing"
mockmodel "app-cloudep-order-server/internal/mock/model"
)
func TestCancelOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據
cancelReq := &order.CancelOrderReq{
BusinessId: "12345",
Status: 1,
}
// 測試數據集
tests := []struct {
name string
input *order.CancelOrderReq
prepare func()
expectErr bool
}{
{
name: "成功取消訂單",
input: cancelReq,
prepare: func() {
// 模擬驗證通過
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬更新狀態成功
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{
BusinessID: "12345",
Status: 1,
}).Return(&mongo.UpdateResult{MatchedCount: 1}, nil).Times(1)
},
expectErr: false,
},
{
name: "訂單更新狀態失敗",
input: cancelReq,
prepare: func() {
// 模擬驗證通過
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬更新狀態失敗
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{
BusinessID: "12345",
Status: 1,
}).Return(nil, errors.New("update failed")).Times(1)
},
expectErr: true,
},
{
name: "驗證失敗",
input: cancelReq,
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid input")).Times(1)
},
expectErr: true,
},
}
// 執行測試
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 CancelOrderLogic
logic := CancelOrderLogic{
svcCtx: svcCtx,
ctx: context.TODO(),
}
// 執行 CancelOrder
resp, err := logic.CancelOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}

View File

@ -0,0 +1,406 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"time"
)
func TestDecimalPtrFromString(t *testing.T) {
tests := []struct {
name string
input string
expected *decimal.Decimal
}{
{
name: "valid decimal string",
input: "10.5",
expected: decimalPtr("10.5"), // 使用輔助函數將字串轉換為指針
},
{
name: "empty string returns nil",
input: "",
expected: nil,
},
{
name: "invalid decimal string returns nil",
input: "invalid-decimal",
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := decimalPtrFromString(tt.input) // 調用要測試的函數
if tt.expected == nil {
assert.Nil(t, result)
} else {
assert.NotNil(t, result)
assert.True(t, tt.expected.Equal(*result), "Expected %v, got %v", tt.expected, result)
}
})
}
}
func TestBuildCreateOrderReq(t *testing.T) {
tests := []struct {
name string
input *order.CreateOrderReq
expectErr bool
expected *createOrderReq
}{
{
name: "所有必填字段存在,可選字段為空",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
},
},
{
name: "包含可選字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
},
},
{
name: "無效的十進制字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "invalid-decimal", // 無效的數值
},
expectErr: true,
},
{
name: "空字符串的可選欄位應返回 nil",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ThirdPartyFee: ptr(""), // 空字符串
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
ThirdPartyFee: nil, // 空字符串應返回 nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := buildCreateOrderReq(tt.input)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestCreateOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.CreateOrderReq
prepare func()
expectErr bool
}{
{
name: "成功建立訂單",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "建立訂單時驗證失敗",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "插入資料庫失敗",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬插入資料庫失敗
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert failed")).Times(1)
},
expectErr: true,
},
{
name: "成功建立訂單,必填字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "成功建立訂單,可選字段有值",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
CryptoType: ptr("BTC"),
PaymentFiat: ptr("USD"),
OrderArrivalTime: ptrInt64(time.Now().UTC().Unix()),
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "部分可選字段有值,其他為空",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
CryptoType: nil, // 空
PaymentFiat: nil, // 空
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "無效的十進制數字",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "invalid", // 無效的數值
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 無需調用資料庫操作,因為會在驗證或轉換時失敗
},
expectErr: true,
},
{
name: "可選欄位為空字符串",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ThirdPartyFee: ptr(""), // 空字符串
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 CreateOrderLogic
logic := NewCreateOrderLogic(context.TODO(), svcCtx)
// 執行 CreateOrder
resp, err := logic.CreateOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}
// ================================ 輔助函數 ================================
func ptr(s string) *string {
return &s
}
func ptrInt64(i int64) *int64 {
return &i
}
// decimalPtr 是一個輔助函數,用來將字串轉換為 *decimal.Decimal 指針
func decimalPtr(val string) *decimal.Decimal {
d, _ := decimal.NewFromString(val)
return &d
}

View File

@ -0,0 +1,96 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
)
func TestDeleteOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.DeleteOrderReq
prepare func()
expectErr bool
}{
{
name: "成功刪除訂單",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫刪除成功
mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, nil).Times(1)
},
expectErr: false,
},
{
name: "驗證失敗",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "刪除資料庫失敗",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫刪除失敗
mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, errors.New("delete failed")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 DeleteOrderLogic
logic := NewDeleteOrderLogic(context.TODO(), svcCtx)
// 執行 DeleteOrder
resp, err := logic.DeleteOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}

View File

@ -3,6 +3,7 @@ package orderservicelogic
import ( import (
"app-cloudep-order-server/gen_result/pb/order" "app-cloudep-order-server/gen_result/pb/order"
"context" "context"
"fmt"
ers "code.30cm.net/digimon/library-go/errs" ers "code.30cm.net/digimon/library-go/errs"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@ -45,6 +46,7 @@ func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.GetOrderResp, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Println(o)
return &order.GetOrderResp{ return &order.GetOrderResp{
UpdateTime: o.UpdateTime, UpdateTime: o.UpdateTime,
@ -58,37 +60,55 @@ func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.GetOrderResp, er
Count: o.Count.String(), Count: o.Count.String(),
OrderFee: o.OrderFee.String(), OrderFee: o.OrderFee.String(),
Amount: o.Amount.String(), Amount: o.Amount.String(),
// 下面的是未來擴充用,加密貨幣用,或者幣別轉換用,普通訂單用不到 // 下面的為未來擴充用的欄位
ReferenceBrand: o.ReferenceBrand, ReferenceBrand: nilString(o.ReferenceBrand),
ReferenceUid: o.ReferenceUID, ReferenceUid: nilString(o.ReferenceUID),
WalletStatus: o.WalletStatus, WalletStatus: nilInt64(o.WalletStatus),
ThreePartyStatus: o.ThreePartyStatus, ThreePartyStatus: nilInt64(o.ThreePartyStatus),
DirectionType: o.DirectionType, DirectionType: nilInt64(o.DirectionType),
CryptoType: o.CryptoType, CryptoType: nilString(o.CryptoType),
ThirdPartyFee: decimalToString(*o.ThirdPartyFee), ThirdPartyFee: decimalToString(o.ThirdPartyFee),
CryptoToUsdtRate: decimalToString(*o.CryptoToUSDTRate), CryptoToUsdtRate: decimalToString(o.CryptoToUSDTRate),
FiatToUsdRate: decimalToString(*o.FiatToUSDRate), FiatToUsdRate: decimalToString(o.FiatToUSDRate),
FeeCryptoToUsdtRate: decimalToString(*o.FeeCryptoToUSDTRate), FeeCryptoToUsdtRate: decimalToString(o.FeeCryptoToUSDTRate),
UsdtToCryptoTypeRate: decimalToString(*o.USDTToCryptoTypeRate), UsdtToCryptoTypeRate: decimalToString(o.USDTToCryptoTypeRate),
PaymentFiat: o.PaymentFiat, PaymentFiat: nilString(o.PaymentFiat),
PaymentUnitPrice: decimalToString(*o.PaymentUnitPrice), PaymentUnitPrice: decimalToString(o.PaymentUnitPrice),
PaymentTemplateId: o.PaymentTemplateID, PaymentTemplateId: nilString(o.PaymentTemplateID),
OrderArrivalTime: o.OrderArrivalTime, OrderArrivalTime: nilInt64(o.OrderArrivalTime),
OrderPaymentTime: o.OrderPaymentTime, OrderPaymentTime: nilInt64(o.OrderPaymentTime),
UnpaidTimeoutSecond: o.UnpaidTimeoutSecond, UnpaidTimeoutSecond: nilInt64(o.UnpaidTimeoutSecond),
ChainType: o.ChainType, ChainType: nilString(o.ChainType),
TxHash: o.TxHash, TxHash: nilString(o.TxHash),
FromAddress: o.FromAddress, FromAddress: nilString(o.FromAddress),
ToAddress: o.ToAddress, ToAddress: nilString(o.ToAddress),
ChainFee: decimalToString(*o.ChainFee), ChainFee: decimalToString(o.ChainFee),
ChainFeeCrypto: o.ChainFeeCrypto, ChainFeeCrypto: nilString(o.ChainFeeCrypto),
Memo: o.Memo, Memo: nilString(o.Memo),
OrderNote: o.OrderNote, OrderNote: nilString(o.OrderNote),
}, nil }, nil
} }
func decimalToString(amount decimal.Decimal) *string { func decimalToString(amount *decimal.Decimal) *string {
if amount == nil {
return nil
}
a := amount.String() a := amount.String()
return &a return &a
} }
func nilString(s *string) *string {
if s == nil {
return nil
}
return s
}
func nilInt64(i *int64) *int64 {
if i == nil {
return nil
}
return i
}

View File

@ -0,0 +1,127 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
model "app-cloudep-order-server/internal/model/mongo"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
)
func TestGetOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 模擬返回的訂單數據
mockOrder := &model.Order{
UpdateTime: 1630000000,
CreateTime: 1620000000,
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
}
// 測試數據集
tests := []struct {
name string
input *order.GetOrderReq
prepare func()
expectErr bool
expected *order.GetOrderResp
}{
{
name: "成功取得訂單",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬查詢訂單成功
mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(mockOrder, nil).Times(1)
},
expectErr: false,
expected: &order.GetOrderResp{
UpdateTime: 1630000000,
CreateTime: 1620000000,
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
},
{
name: "驗證失敗",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "查詢訂單失敗",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬查詢訂單失敗
mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(nil, errors.New("order not found")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 GetOrderLogic
logic := NewGetOrderLogic(context.TODO(), svcCtx)
// 執行 GetOrder
resp, err := logic.GetOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, resp)
}
})
}
}

View File

@ -160,3 +160,8 @@ func ConvertOrdersToGetOrderResp(orders []model.Order) []*order.GetOrderResp {
return res return res
} }
// Helper functions
func ptrString(s string) *string {
return &s
}

View File

@ -0,0 +1,458 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
model "app-cloudep-order-server/internal/model/mongo"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
)
func TestConvertOrdersToGetOrderResp(t *testing.T) {
tests := []struct {
name string
input []model.Order
expected []*order.GetOrderResp
}{
{
name: "空訂單列表",
input: []model.Order{},
expected: []*order.GetOrderResp{},
},
{
name: "單筆訂單",
input: []model.Order{
{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
},
},
expected: []*order.GetOrderResp{
{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
},
},
{
name: "多筆訂單",
input: []model.Order{
{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
},
{
BusinessID: "B456",
OrderType: 2,
OrderStatus: 3,
Brand: "OtherBrand",
OrderUID: "UID456",
ReferenceID: "REF456",
Count: decimal.RequireFromString("20"),
OrderFee: decimal.RequireFromString("1"),
Amount: decimal.RequireFromString("200"),
},
},
expected: []*order.GetOrderResp{
{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
{
BusinessId: "B456",
OrderType: 2,
OrderStatus: 3,
Brand: "OtherBrand",
OrderUid: "UID456",
ReferenceId: "REF456",
Count: "20",
OrderFee: "1",
Amount: "200",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 執行 ConvertOrdersToGetOrderResp 函數
result := ConvertOrdersToGetOrderResp(tt.input)
// 驗證結果
assert.Equal(t, tt.expected, result)
})
}
}
func TestOptionalString(t *testing.T) {
t.Run("非 nil 字串", func(t *testing.T) {
str := "hello"
result := optionalString(&str)
assert.NotNil(t, result)
assert.Equal(t, &str, result)
})
t.Run("nil 字串", func(t *testing.T) {
result := optionalString(nil)
assert.Nil(t, result)
})
}
func TestOptionalInt64(t *testing.T) {
t.Run("非 nil int64", func(t *testing.T) {
num := int64(123)
result := optionalInt64(&num)
assert.NotNil(t, result)
assert.Equal(t, &num, result)
})
t.Run("nil int64", func(t *testing.T) {
result := optionalInt64(nil)
assert.Nil(t, result)
})
}
func TestOptionalDecimalToString(t *testing.T) {
t.Run("非 nil decimal", func(t *testing.T) {
dec := decimal.NewFromInt(123)
expected := "123"
result := optionalDecimalToString(&dec)
assert.NotNil(t, result)
assert.Equal(t, &expected, result)
})
t.Run("nil decimal", func(t *testing.T) {
result := optionalDecimalToString(nil)
assert.Nil(t, result)
})
}
func TestConvertOrderToGetOrderResp(t *testing.T) {
tests := []struct {
name string
input model.Order
expected *order.GetOrderResp
}{
{
name: "所有欄位都有值",
input: model.Order{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
ReferenceBrand: ptrString("OtherBrand"),
ReferenceUID: ptrString("REFUID"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
DirectionType: ptrInt64(1),
CryptoType: ptrString("BTC"),
ThirdPartyFee: decimalPtrFromString("0.01"),
CryptoToUSDTRate: decimalPtrFromString("50000"),
FiatToUSDRate: decimalPtrFromString("1"),
FeeCryptoToUSDTRate: decimalPtrFromString("0.02"),
USDTToCryptoTypeRate: decimalPtrFromString("0.00002"),
PaymentFiat: ptrString("USD"),
PaymentUnitPrice: decimalPtrFromString("50000"),
PaymentTemplateID: ptrString("TEMPLATE123"),
OrderArrivalTime: ptrInt64(1630000000),
OrderPaymentTime: ptrInt64(1620000000),
UnpaidTimeoutSecond: ptrInt64(3600),
ChainType: ptrString("ETH"),
TxHash: ptrString("0xABC123"),
FromAddress: ptrString("0xFROM123"),
ToAddress: ptrString("0xTO123"),
ChainFee: decimalPtrFromString("0.001"),
ChainFeeCrypto: ptrString("ETH"),
Memo: ptrString("Test Memo"),
OrderNote: ptrString("Test Note"),
CreateTime: 1620000000,
UpdateTime: 1630000000,
},
expected: &order.GetOrderResp{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptrString("OtherBrand"),
ReferenceUid: ptrString("REFUID"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
DirectionType: ptrInt64(1),
CryptoType: ptrString("BTC"),
ThirdPartyFee: ptrString("0.01"),
CryptoToUsdtRate: ptrString("50000"),
FiatToUsdRate: ptrString("1"),
FeeCryptoToUsdtRate: ptrString("0.02"),
UsdtToCryptoTypeRate: ptrString("0.00002"),
PaymentFiat: ptrString("USD"),
PaymentUnitPrice: ptrString("50000"),
PaymentTemplateId: ptrString("TEMPLATE123"),
OrderArrivalTime: ptrInt64(1630000000),
OrderPaymentTime: ptrInt64(1620000000),
UnpaidTimeoutSecond: ptrInt64(3600),
ChainType: ptrString("ETH"),
TxHash: ptrString("0xABC123"),
FromAddress: ptrString("0xFROM123"),
ToAddress: ptrString("0xTO123"),
ChainFee: ptrString("0.001"),
ChainFeeCrypto: ptrString("ETH"),
Memo: ptrString("Test Memo"),
OrderNote: ptrString("Test Note"),
CreateTime: 1620000000,
UpdateTime: 1630000000,
},
},
{
name: "部分欄位為 nil",
input: model.Order{
BusinessID: "B456",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID456",
ReferenceID: "REF456",
Count: decimal.RequireFromString("10"),
OrderFee: decimal.RequireFromString("1"),
Amount: decimal.RequireFromString("100"),
ReferenceBrand: nil,
ReferenceUID: nil,
WalletStatus: nil,
ThreePartyStatus: nil,
DirectionType: nil,
CryptoType: nil,
ThirdPartyFee: nil,
CryptoToUSDTRate: nil,
FiatToUSDRate: nil,
FeeCryptoToUSDTRate: nil,
USDTToCryptoTypeRate: nil,
PaymentFiat: nil,
PaymentUnitPrice: nil,
PaymentTemplateID: nil,
OrderArrivalTime: nil,
OrderPaymentTime: nil,
UnpaidTimeoutSecond: nil,
ChainType: nil,
TxHash: nil,
FromAddress: nil,
ToAddress: nil,
ChainFee: nil,
ChainFeeCrypto: nil,
Memo: nil,
OrderNote: nil,
CreateTime: 1620000000,
UpdateTime: 1630000000,
},
expected: &order.GetOrderResp{
BusinessId: "B456",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID456",
ReferenceId: "REF456",
Count: "10",
OrderFee: "1",
Amount: "100",
ReferenceBrand: nil,
ReferenceUid: nil,
WalletStatus: nil,
ThreePartyStatus: nil,
DirectionType: nil,
CryptoType: nil,
ThirdPartyFee: nil,
CryptoToUsdtRate: nil,
FiatToUsdRate: nil,
FeeCryptoToUsdtRate: nil,
UsdtToCryptoTypeRate: nil,
PaymentFiat: nil,
PaymentUnitPrice: nil,
PaymentTemplateId: nil,
OrderArrivalTime: nil,
OrderPaymentTime: nil,
UnpaidTimeoutSecond: nil,
ChainType: nil,
TxHash: nil,
FromAddress: nil,
ToAddress: nil,
ChainFee: nil,
ChainFeeCrypto: nil,
Memo: nil,
OrderNote: nil,
CreateTime: 1620000000,
UpdateTime: 1630000000,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 執行 ConvertOrderToGetOrderResp 函數
result := ConvertOrderToGetOrderResp(tt.input)
// 驗證結果
assert.Equal(t, tt.expected, result)
})
}
}
func TestListOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.ListOrderReq
prepare func()
expectErr bool
expectedRes *order.ListOrderResp
}{
{
name: "成功取得訂單列表",
input: &order.ListOrderReq{
PageIndex: 1,
PageSize: 10,
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬返回訂單列表
mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return([]model.Order{
{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
},
}, int64(1), nil).Times(1)
},
expectErr: false,
expectedRes: &order.ListOrderResp{
Data: []*order.GetOrderResp{
{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
},
Page: &order.Pager{
Total: 1,
Index: 1,
Size: 10,
},
},
},
{
name: "訂單查詢失敗",
input: &order.ListOrderReq{
PageIndex: 1,
PageSize: 10,
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬返回查詢錯誤
mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("query failed")).Times(1)
},
expectErr: true,
},
{
name: "分頁錯誤",
input: &order.ListOrderReq{
PageIndex: 0,
PageSize: 10,
},
prepare: func() {
// 模擬驗證失敗,因為 PageIndex 為 0
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid pagination")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 準備測試環境
tt.prepare()
// 初始化 ListOrderLogic
logic := NewListOrderLogic(context.TODO(), svcCtx)
// 執行 ListOrder
resp, err := logic.ListOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, resp)
}
})
}
}

View File

@ -1,31 +0,0 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"context"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ModifyOrderLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewModifyOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyOrderLogic {
return &ModifyOrderLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// ModifyOrder 修改訂單
func (l *ModifyOrderLogic) ModifyOrder(in *order.ModifyOrderReq) (*order.OKResp, error) {
// todo: add your logic here and delete this line
return &order.OKResp{}, nil
}

View File

@ -0,0 +1,73 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./validate.go
//
// Generated by this command:
//
// mockgen -source=./validate.go -destination=../../mock/lib/validate.go -package=lib
//
// Package lib is a generated GoMock package.
package lib
import (
reflect "reflect"
required "code.30cm.net/digimon/library-go/validator"
gomock "go.uber.org/mock/gomock"
)
// MockValidate is a mock of Validate interface.
type MockValidate struct {
ctrl *gomock.Controller
recorder *MockValidateMockRecorder
}
// MockValidateMockRecorder is the mock recorder for MockValidate.
type MockValidateMockRecorder struct {
mock *MockValidate
}
// NewMockValidate creates a new mock instance.
func NewMockValidate(ctrl *gomock.Controller) *MockValidate {
mock := &MockValidate{ctrl: ctrl}
mock.recorder = &MockValidateMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidate) EXPECT() *MockValidateMockRecorder {
return m.recorder
}
// BindToValidator mocks base method.
func (m *MockValidate) BindToValidator(opts ...required.Option) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "BindToValidator", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// BindToValidator indicates an expected call of BindToValidator.
func (mr *MockValidateMockRecorder) BindToValidator(opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindToValidator", reflect.TypeOf((*MockValidate)(nil).BindToValidator), opts...)
}
// ValidateAll mocks base method.
func (m *MockValidate) ValidateAll(obj any) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateAll", obj)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateAll indicates an expected call of ValidateAll.
func (mr *MockValidateMockRecorder) ValidateAll(obj any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAll", reflect.TypeOf((*MockValidate)(nil).ValidateAll), obj)
}

View File

@ -143,8 +143,10 @@ func (m *customOrderModel) DeleteByBusinessID(ctx context.Context, id string) (*
func (m *defaultOrderModel) FindOneBusinessID(ctx context.Context, id string) (*Order, error) { func (m *defaultOrderModel) FindOneBusinessID(ctx context.Context, id string) (*Order, error) {
var data Order var data Order
filter := bson.M{"delete_time": bson.M{"$in": []any{0, nil}}}
filter["business_id"] = id
err := m.conn.FindOne(ctx, &data, bson.M{"business_id": id}) err := m.conn.FindOne(ctx, &data, filter)
switch { switch {
case err == nil: case err == nil:
return &data, nil return &data, nil

View File

@ -34,11 +34,6 @@ func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *order.CancelOr
return l.CancelOrder(in) return l.CancelOrder(in)
} }
// ModifyOrder 修改訂單
func (s *OrderServiceServer) ModifyOrder(ctx context.Context, in *order.ModifyOrderReq) (*order.OKResp, error) {
l := orderservicelogic.NewModifyOrderLogic(ctx, s.svcCtx)
return l.ModifyOrder(in)
}
// ModifyOrderStatus 修改訂單狀態 // ModifyOrderStatus 修改訂單狀態
func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *order.ModifyOrderStatusReq) (*order.OKResp, error) { func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *order.ModifyOrderStatusReq) (*order.OKResp, error) {