feature/order_base #1
			
				
			
		
		
		
	|  | @ -1,19 +1,13 @@ | |||
| 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({ | ||||
|     "uid": 1, | ||||
|     "order_type": 1, | ||||
|     "order_status": 1, | ||||
|     "create_time": 1, | ||||
| }) | ||||
| 
 | ||||
| db.order.createIndex({ | ||||
|     "business_id": 1, | ||||
| }) | ||||
| 
 | ||||
| // 查詢時全部要 1&2 有幾個類用幾個累才會中索引 | ||||
| db.order.createIndex({ | ||||
|     "uid": 1, | ||||
|     "direction_type": 1, | ||||
|     "crypto_type": 1 | ||||
|     "create_time": -1, | ||||
| }) | ||||
|  | @ -152,8 +152,6 @@ service OrderService { | |||
|   rpc CreateOrder(CreateOrderReq) returns (OKResp); | ||||
|   // CancelOrder 取消訂單 | ||||
|   rpc CancelOrder(CancelOrderReq) returns (OKResp); | ||||
|   // ModifyOrder 修改訂單 | ||||
|   rpc ModifyOrder(ModifyOrderReq) returns (OKResp); | ||||
|   // ModifyOrderStatus 修改訂單狀態 | ||||
|   rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp); | ||||
|   // DeleteOrder 刪除訂單(軟刪除) | ||||
|  |  | |||
							
								
								
									
										4
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										4
									
								
								go.mod
								
								
								
								
							|  | @ -5,7 +5,9 @@ go 1.22.3 | |||
| require ( | ||||
| 	code.30cm.net/digimon/library-go/errs v1.2.5 | ||||
| 	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/stretchr/testify v1.9.0 | ||||
| 	github.com/zeromicro/go-zero v1.7.2 | ||||
| 	go.mongodb.org/mongo-driver v1.16.1 | ||||
| 	go.uber.org/mock v0.4.0 | ||||
|  | @ -31,7 +33,6 @@ require ( | |||
| 	github.com/go-openapi/swag v0.22.4 // indirect | ||||
| 	github.com/go-playground/locales v0.14.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/golang/mock v1.6.0 // 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/openzipkin/zipkin-go v0.4.3 // 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_model v0.6.1 // indirect | ||||
| 	github.com/prometheus/common v0.55.0 // indirect | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -3,6 +3,7 @@ package orderservicelogic | |||
| import ( | ||||
| 	"app-cloudep-order-server/gen_result/pb/order" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"github.com/shopspring/decimal" | ||||
|  | @ -45,6 +46,7 @@ func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.GetOrderResp, er | |||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	fmt.Println(o) | ||||
| 
 | ||||
| 	return &order.GetOrderResp{ | ||||
| 		UpdateTime:  o.UpdateTime, | ||||
|  | @ -58,37 +60,55 @@ func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.GetOrderResp, er | |||
| 		Count:       o.Count.String(), | ||||
| 		OrderFee:    o.OrderFee.String(), | ||||
| 		Amount:      o.Amount.String(), | ||||
| 		// 下面的是未來擴充用,加密貨幣用,或者幣別轉換用,普通訂單用不到
 | ||||
| 		ReferenceBrand:       o.ReferenceBrand, | ||||
| 		ReferenceUid:         o.ReferenceUID, | ||||
| 		WalletStatus:         o.WalletStatus, | ||||
| 		ThreePartyStatus:     o.ThreePartyStatus, | ||||
| 		DirectionType:        o.DirectionType, | ||||
| 		CryptoType:           o.CryptoType, | ||||
| 		ThirdPartyFee:        decimalToString(*o.ThirdPartyFee), | ||||
| 		CryptoToUsdtRate:     decimalToString(*o.CryptoToUSDTRate), | ||||
| 		FiatToUsdRate:        decimalToString(*o.FiatToUSDRate), | ||||
| 		FeeCryptoToUsdtRate:  decimalToString(*o.FeeCryptoToUSDTRate), | ||||
| 		UsdtToCryptoTypeRate: decimalToString(*o.USDTToCryptoTypeRate), | ||||
| 		PaymentFiat:          o.PaymentFiat, | ||||
| 		PaymentUnitPrice:     decimalToString(*o.PaymentUnitPrice), | ||||
| 		PaymentTemplateId:    o.PaymentTemplateID, | ||||
| 		OrderArrivalTime:     o.OrderArrivalTime, | ||||
| 		OrderPaymentTime:     o.OrderPaymentTime, | ||||
| 		UnpaidTimeoutSecond:  o.UnpaidTimeoutSecond, | ||||
| 		ChainType:            o.ChainType, | ||||
| 		TxHash:               o.TxHash, | ||||
| 		FromAddress:          o.FromAddress, | ||||
| 		ToAddress:            o.ToAddress, | ||||
| 		ChainFee:             decimalToString(*o.ChainFee), | ||||
| 		ChainFeeCrypto:       o.ChainFeeCrypto, | ||||
| 		Memo:                 o.Memo, | ||||
| 		OrderNote:            o.OrderNote, | ||||
| 		// 下面的為未來擴充用的欄位
 | ||||
| 		ReferenceBrand:       nilString(o.ReferenceBrand), | ||||
| 		ReferenceUid:         nilString(o.ReferenceUID), | ||||
| 		WalletStatus:         nilInt64(o.WalletStatus), | ||||
| 		ThreePartyStatus:     nilInt64(o.ThreePartyStatus), | ||||
| 		DirectionType:        nilInt64(o.DirectionType), | ||||
| 		CryptoType:           nilString(o.CryptoType), | ||||
| 		ThirdPartyFee:        decimalToString(o.ThirdPartyFee), | ||||
| 		CryptoToUsdtRate:     decimalToString(o.CryptoToUSDTRate), | ||||
| 		FiatToUsdRate:        decimalToString(o.FiatToUSDRate), | ||||
| 		FeeCryptoToUsdtRate:  decimalToString(o.FeeCryptoToUSDTRate), | ||||
| 		UsdtToCryptoTypeRate: decimalToString(o.USDTToCryptoTypeRate), | ||||
| 		PaymentFiat:          nilString(o.PaymentFiat), | ||||
| 		PaymentUnitPrice:     decimalToString(o.PaymentUnitPrice), | ||||
| 		PaymentTemplateId:    nilString(o.PaymentTemplateID), | ||||
| 		OrderArrivalTime:     nilInt64(o.OrderArrivalTime), | ||||
| 		OrderPaymentTime:     nilInt64(o.OrderPaymentTime), | ||||
| 		UnpaidTimeoutSecond:  nilInt64(o.UnpaidTimeoutSecond), | ||||
| 		ChainType:            nilString(o.ChainType), | ||||
| 		TxHash:               nilString(o.TxHash), | ||||
| 		FromAddress:          nilString(o.FromAddress), | ||||
| 		ToAddress:            nilString(o.ToAddress), | ||||
| 		ChainFee:             decimalToString(o.ChainFee), | ||||
| 		ChainFeeCrypto:       nilString(o.ChainFeeCrypto), | ||||
| 		Memo:                 nilString(o.Memo), | ||||
| 		OrderNote:            nilString(o.OrderNote), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func decimalToString(amount decimal.Decimal) *string { | ||||
| func decimalToString(amount *decimal.Decimal) *string { | ||||
| 	if amount == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	a := amount.String() | ||||
| 
 | ||||
| 	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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -160,3 +160,8 @@ func ConvertOrdersToGetOrderResp(orders []model.Order) []*order.GetOrderResp { | |||
| 
 | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| // Helper functions
 | ||||
| func ptrString(s string) *string { | ||||
| 	return &s | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -143,8 +143,10 @@ func (m *customOrderModel) DeleteByBusinessID(ctx context.Context, id string) (* | |||
| 
 | ||||
| func (m *defaultOrderModel) FindOneBusinessID(ctx context.Context, id string) (*Order, error) { | ||||
| 	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 { | ||||
| 	case err == nil: | ||||
| 		return &data, nil | ||||
|  |  | |||
|  | @ -34,11 +34,6 @@ func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *order.CancelOr | |||
| 	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 修改訂單狀態
 | ||||
| func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *order.ModifyOrderStatusReq) (*order.OKResp, error) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue