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) }