324 lines
8.8 KiB
Go
324 lines
8.8 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNewClient(t *testing.T) {
|
|
client := NewClient("https://test.example.com", "testAppID", "testSecret")
|
|
|
|
if client.baseURL != "https://test.example.com" {
|
|
t.Errorf("baseURL = %s, want https://test.example.com", client.baseURL)
|
|
}
|
|
if client.appID != "testAppID" {
|
|
t.Errorf("appID = %s, want testAppID", client.appID)
|
|
}
|
|
if client.appSecret != "testSecret" {
|
|
t.Errorf("appSecret = %s, want testSecret", client.appSecret)
|
|
}
|
|
if client.timeout != 30*time.Second {
|
|
t.Errorf("timeout = %v, want 30s", client.timeout)
|
|
}
|
|
if client.httpClient == nil {
|
|
t.Error("httpClient should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestWithTimeout(t *testing.T) {
|
|
client := NewClient("https://test.example.com", "testAppID", "testSecret").
|
|
WithTimeout(60 * time.Second)
|
|
|
|
if client.timeout != 60*time.Second {
|
|
t.Errorf("timeout = %v, want 60s", client.timeout)
|
|
}
|
|
}
|
|
|
|
func TestWithTimeout_Chain(t *testing.T) {
|
|
// 验证链式调用返回同一个 Client 实例
|
|
client := NewClient("https://test.example.com", "testAppID", "testSecret")
|
|
returned := client.WithTimeout(45 * time.Second)
|
|
|
|
if returned != client {
|
|
t.Error("WithTimeout should return the same Client instance for chaining")
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_Success(t *testing.T) {
|
|
// 创建 mock HTTP 服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// 验证请求方法
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("Method = %s, want POST", r.Method)
|
|
}
|
|
|
|
// 验证 Content-Type
|
|
if r.Header.Get("Content-Type") != "application/json;charset=utf-8" {
|
|
t.Errorf("Content-Type = %s, want application/json;charset=utf-8", r.Header.Get("Content-Type"))
|
|
}
|
|
|
|
// 验证请求体格式
|
|
var reqBody map[string]interface{}
|
|
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
|
|
t.Fatalf("解析请求体失败: %v", err)
|
|
}
|
|
|
|
// 验证必需字段
|
|
if _, ok := reqBody["appId"]; !ok {
|
|
t.Error("请求体缺少 appId 字段")
|
|
}
|
|
if _, ok := reqBody["data"]; !ok {
|
|
t.Error("请求体缺少 data 字段")
|
|
}
|
|
if _, ok := reqBody["sign"]; !ok {
|
|
t.Error("请求体缺少 sign 字段")
|
|
}
|
|
if _, ok := reqBody["timestamp"]; !ok {
|
|
t.Error("请求体缺少 timestamp 字段")
|
|
}
|
|
|
|
// 返回 mock 响应
|
|
resp := GatewayResponse{
|
|
Code: 200,
|
|
Msg: "成功",
|
|
Data: json.RawMessage(`{"test":"data"}`),
|
|
TraceID: "test-trace-id",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
ctx := context.Background()
|
|
businessData := map[string]interface{}{
|
|
"params": map[string]string{
|
|
"cardNo": "898608070422D0010269",
|
|
},
|
|
}
|
|
|
|
data, err := client.doRequest(ctx, "/test", businessData)
|
|
if err != nil {
|
|
t.Fatalf("doRequest() error = %v", err)
|
|
}
|
|
|
|
if string(data) != `{"test":"data"}` {
|
|
t.Errorf("data = %s, want {\"test\":\"data\"}", string(data))
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_BusinessError(t *testing.T) {
|
|
// 创建返回业务错误的 mock 服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
resp := GatewayResponse{
|
|
Code: 500,
|
|
Msg: "业务处理失败",
|
|
Data: nil,
|
|
TraceID: "error-trace-id",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
ctx := context.Background()
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected business error")
|
|
}
|
|
|
|
// 验证错误信息包含业务错误内容
|
|
if !strings.Contains(err.Error(), "业务错误") {
|
|
t.Errorf("error should contain '业务错误', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_Timeout(t *testing.T) {
|
|
// 创建延迟响应的服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(500 * time.Millisecond) // 延迟 500ms
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret").
|
|
WithTimeout(100 * time.Millisecond) // 设置 100ms 超时
|
|
|
|
ctx := context.Background()
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected timeout error")
|
|
}
|
|
|
|
// 验证是超时错误
|
|
if !strings.Contains(err.Error(), "超时") {
|
|
t.Errorf("error should contain '超时', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_HTTPStatusError(t *testing.T) {
|
|
// 创建返回 500 状态码的服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Internal Server Error"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
ctx := context.Background()
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected HTTP status error")
|
|
}
|
|
|
|
// 验证错误信息包含 HTTP 状态码
|
|
if !strings.Contains(err.Error(), "500") {
|
|
t.Errorf("error should contain '500', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_InvalidResponse(t *testing.T) {
|
|
// 创建返回无效 JSON 的服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte("invalid json"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
ctx := context.Background()
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected JSON parse error")
|
|
}
|
|
|
|
// 验证错误信息包含解析失败提示
|
|
if !strings.Contains(err.Error(), "解析") {
|
|
t.Errorf("error should contain '解析', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_ContextCanceled(t *testing.T) {
|
|
// 创建正常响应的服务器(但会延迟)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(500 * time.Millisecond)
|
|
resp := GatewayResponse{Code: 200, Msg: "成功"}
|
|
json.NewEncoder(w).Encode(resp)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
// 创建已取消的 context
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // 立即取消
|
|
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected context canceled error")
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_NetworkError(t *testing.T) {
|
|
// 使用无效的服务器地址
|
|
client := NewClient("http://127.0.0.1:1", "testAppID", "testSecret").
|
|
WithTimeout(1 * time.Second)
|
|
|
|
ctx := context.Background()
|
|
_, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err == nil {
|
|
t.Fatal("doRequest() expected network error")
|
|
}
|
|
}
|
|
|
|
func TestDoRequest_EmptyBusinessData(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
resp := GatewayResponse{
|
|
Code: 200,
|
|
Msg: "成功",
|
|
Data: json.RawMessage(`{}`),
|
|
}
|
|
json.NewEncoder(w).Encode(resp)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "testAppID", "testSecret")
|
|
|
|
ctx := context.Background()
|
|
data, err := client.doRequest(ctx, "/test", map[string]interface{}{})
|
|
if err != nil {
|
|
t.Fatalf("doRequest() error = %v", err)
|
|
}
|
|
|
|
if string(data) != `{}` {
|
|
t.Errorf("data = %s, want {}", string(data))
|
|
}
|
|
}
|
|
|
|
func TestIntegration_QueryCardStatus(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("跳过集成测试")
|
|
}
|
|
|
|
baseURL := "https://lplan.whjhft.com/openapi"
|
|
appID := "60bgt1X8i7AvXqkd"
|
|
appSecret := "BZeQttaZQt0i73moF"
|
|
|
|
client := NewClient(baseURL, appID, appSecret).WithTimeout(30 * time.Second)
|
|
ctx := context.Background()
|
|
|
|
resp, err := client.QueryCardStatus(ctx, &CardStatusReq{
|
|
CardNo: "898608070422D0010269",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("QueryCardStatus() error = %v", err)
|
|
}
|
|
|
|
if resp.ICCID == "" {
|
|
t.Error("ICCID should not be empty")
|
|
}
|
|
if resp.CardStatus == "" {
|
|
t.Error("CardStatus should not be empty")
|
|
}
|
|
|
|
t.Logf("Integration test passed: ICCID=%s, Status=%s", resp.ICCID, resp.CardStatus)
|
|
}
|
|
|
|
func TestIntegration_QueryFlow(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("跳过集成测试")
|
|
}
|
|
|
|
baseURL := "https://lplan.whjhft.com/openapi"
|
|
appID := "60bgt1X8i7AvXqkd"
|
|
appSecret := "BZeQttaZQt0i73moF"
|
|
|
|
client := NewClient(baseURL, appID, appSecret).WithTimeout(30 * time.Second)
|
|
ctx := context.Background()
|
|
|
|
resp, err := client.QueryFlow(ctx, &FlowQueryReq{
|
|
CardNo: "898608070422D0010269",
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("QueryFlow() error = %v", err)
|
|
}
|
|
|
|
if resp.UsedFlow < 0 {
|
|
t.Error("UsedFlow should not be negative")
|
|
}
|
|
|
|
t.Logf("Integration test passed: UsedFlow=%d %s", resp.UsedFlow, resp.Unit)
|
|
}
|