package upstream import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcfg" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model" ) func TestTestChannelPostsOneTenantBoundRequestToGateway(t *testing.T) { const gatewayBody = `{"code":0,"message":"success","data":{"items":[{"key":"simStatus","name":"卡状态","status":"success"}]}}` var requestBody model.UpstreamChannelTestRequest requestCount := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/internal/channel/test" || r.Method != http.MethodPost { t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) } requestCount++ if r.Header.Get("X-Internal-Token") != "" { t.Errorf("unexpected internal token header") } if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil { t.Fatal(err) } _, _ = w.Write([]byte(gatewayBody)) })) defer server.Close() oldAdapter := g.Cfg().GetAdapter() adapter, err := gcfg.NewAdapterContent(`upstream: gateway: address: "` + strings.TrimPrefix(server.URL, "http://") + `" timeout: 1s `) if err != nil { t.Fatal(err) } g.Cfg().SetAdapter(adapter) t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) }) output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{ ChannelID: 42, TenantID: "tenant-a", InputMode: "complete", ICCID: "iccid", IMSI: "imsi", MSISDN: "msisdn", Items: []string{"simStatus"}, }) if err != nil { t.Fatal(err) } if requestCount != 1 { t.Fatalf("request count=%d, want one", requestCount) } if requestBody.ChannelID != 42 || requestBody.TenantID != "tenant-a" || len(requestBody.Items) != 1 { t.Fatalf("unexpected request body: %+v", requestBody) } if output == nil || len(output.Items) != 1 || output.Items[0].Status != "success" { t.Fatalf("unexpected output: %+v", output) } if output.GatewayResponseRaw != gatewayBody { t.Fatalf("raw response=%q, want %q", output.GatewayResponseRaw, gatewayBody) } } func TestTestChannelPreservesGatewayErrorResponses(t *testing.T) { responseBody := "" responseStatus := http.StatusOK server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(responseStatus) _, _ = w.Write([]byte(responseBody)) })) defer server.Close() oldAdapter := g.Cfg().GetAdapter() adapter, err := gcfg.NewAdapterContent(`upstream: gateway: address: "` + strings.TrimPrefix(server.URL, "http://") + `" timeout: 1s `) if err != nil { t.Fatal(err) } g.Cfg().SetAdapter(adapter) t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) }) for _, tc := range []struct { name string body string status int wantMessage string }{ {"business error", `{"code":13000,"message":"系统忙","data":{"items":[]}}`, http.StatusOK, "系统忙"}, {"completion error", `{"code":50,"message":"carrier did not return complete card identifiers","data":null}`, http.StatusOK, "carrier did not return complete card identifiers"}, {"empty message", `{"code":50,"message":"","data":null}`, http.StatusOK, "上游通道测试失败"}, {"HTTP error", `{"code":13000,"message":"网关故障"}`, http.StatusBadGateway, "网关故障"}, {"invalid JSON", `gateway unavailable`, http.StatusOK, "解析上游通道测试响应失败"}, {"schema mismatch", `{"code":0,"data":{"items":"invalid"}}`, http.StatusOK, "解析上游通道测试响应失败"}, } { t.Run(tc.name, func(t *testing.T) { responseBody = tc.body responseStatus = tc.status output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{ ChannelID: 42, TenantID: "tenant-a", InputMode: "complete", ICCID: "iccid", IMSI: "imsi", MSISDN: "msisdn", }) if err == nil || output != nil { t.Fatalf("failure must remain an error: output=%+v err=%v", output, err) } var diagnostic *model.TSimChannelsTestError if !errors.As(err, &diagnostic) || diagnostic.GatewayResponseRaw != tc.body { t.Fatalf("missing raw error response: %+v", err) } if !strings.Contains(err.Error(), tc.wantMessage) || gerror.Code(err) != gcode.CodeNil { t.Fatalf("original error changed: %v code=%v", err, gerror.Code(err)) } if err.Error() != diagnostic.Err.Error() || !errors.Is(err, diagnostic.Err) { t.Fatalf("original error chain changed: %v", err) } }) } t.Run("individual failures remain results", func(t *testing.T) { responseStatus = http.StatusOK responseBody = `{"code":0,"message":"success","data":{"items":[{"key":"simStatus","status":"failed","errorCode":9999,"message":"carrier rejected"},{"key":"simSession","status":"timeout","message":"channel test item timed out"}]}}` output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{ChannelID: 42, TenantID: "tenant-a"}) if err != nil || output == nil { t.Fatalf("single-item failures must not fail the whole request: %v", err) } if output.GatewayResponseRaw != responseBody || len(output.Items) != 2 || output.Items[0].ErrorCode != 9999 || output.Items[0].Message != "carrier rejected" || output.Items[1].Status != "timeout" { t.Fatalf("item results changed: %+v", output) } }) }