Files
junhong_cmp_fiber/internal/gateway/client_test.go
2026-01-30 17:05:44 +08:00

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