package cmd import ( "context" "crypto/sha256" "encoding/json" "fmt" "net/url" "os" "strings" "testing" "time" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gcfg" v1 "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/api/cardquery/v1" _ "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/simKeyFill" "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamapi" "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/logic/upstreamimpl" "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model" "git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/service" ) func TestAPNHTTPHarness(t *testing.T) { if os.Getenv("APN_HTTP_CHAIN_HELPER") != "1" { t.Skip("未启用本机HTTP模拟链") } gateway, err := url.Parse(os.Getenv("APN_HTTP_CHAIN_CHAN")) if err != nil || gateway.Scheme != "http" || gateway.Hostname() != "127.0.0.1" || gateway.Port() == "" { t.Fatal("只允许本机模拟chan") } adapter, err := gcfg.NewAdapterContent(fmt.Sprintf(`{"gateway":{"address":%q,"timeout":"2s"}}`, gateway.Host)) if err != nil { t.Fatal(err) } g.Cfg().SetAdapter(adapter) api := upstreamapi.NewUpstreamAPI() api.RegisterChan("fake-channel", "1", &model.SimCardChannelCache{Instance: upstreamimpl.NewSimYD()}) service.RegisterUpstreamAPI(api) server := ghttp.GetServer(t.Name()) server.SetAddr("127.0.0.1:0") bindHTTPRoutes(server) stop := make(chan struct{}) server.BindHandler("POST:/__test/stop", func(r *ghttp.Request) { close(stop) }) if err := server.Start(); err != nil { t.Fatal(err) } defer server.Shutdown() fmt.Printf("APN_CHAIN_READY=http://127.0.0.1:%d\n", server.GetListenedPort()) select { case <-stop: case <-time.After(3 * time.Minute): t.Error("HTTP模拟链未正常结束") } } type apnBindingTestFill struct { service.ISimKeyFill captured chan model.SimBaseReqJson } func (s *apnBindingTestFill) SimKeyFill(_ context.Context, input *model.SimBaseReqJson) (model.SimBaseResJson, error) { s.captured <- *input return model.SimBaseResJson{}, nil } func TestAPNProductionHTTPBindingPreservesRawPayload(t *testing.T) { old := service.SimKeyFill() capture := &apnBindingTestFill{ISimKeyFill: old, captured: make(chan model.SimBaseReqJson, 1)} service.RegisterSimKeyFill(capture) t.Cleanup(func() { service.RegisterSimKeyFill(old) }) server := ghttp.GetServer(t.Name()) server.SetAddr("127.0.0.1:0") bindHTTPRoutes(server) server.BindHandler("POST:/__test/legacy-binding", func(r *ghttp.Request) { var req v1.SimBaseReq if err := r.Parse(&req); err != nil { t.Error(err) r.Response.WriteStatus(400) return } capture.captured <- req.SimBaseReqJson }) if err := server.Start(); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = server.Shutdown() }) for _, data := range []string{ `{"operationId":"frozen-operation","submitOnly":true,"operType":"1","apnName":"CMIOT"}`, `{"operationId":"frozen-operation","queryJobId":"saved-job","operType":"1","apnName":"CMIOT"}`, `{"operationId":"frozen-operation","recoverOnly":true,"operType":"1","apnName":"CMIOT"}`, } { for _, path := range []string{"/sim/change/sim-apn", "/sim/query/sim-apn-capability", "/sim/query/sim-session"} { input := model.SimBaseReqJson{SimKeys: []*model.SimKey{{ChanKey: "fake", ICCID: "fake-card", MSISDN: "frozen-number"}}, ReqData: json.RawMessage(data)} response, err := g.Client().ContentJson().Post(context.Background(), fmt.Sprintf("http://127.0.0.1:%d%s", server.GetListenedPort(), path), input) if err != nil { t.Fatal(err) } body := response.ReadAll() _ = response.Close() select { case got := <-capture.captured: var wantData, gotData map[string]any _ = json.Unmarshal(input.ReqData, &wantData) _ = json.Unmarshal(got.ReqData, &gotData) wantJSON, _ := json.Marshal(wantData) gotJSON, _ := json.Marshal(gotData) if path == "/sim/query/sim-session" { baselineResponse, err := g.Client().ContentJson().Post(context.Background(), fmt.Sprintf("http://127.0.0.1:%d/__test/legacy-binding", server.GetListenedPort()), input) if err != nil { t.Fatal(err) } _ = baselineResponse.Close() baseline := <-capture.captured if string(got.ReqData) != string(baseline.ReqData) { t.Fatalf("非APN旧绑定行为改变:got=%q baseline=%q", got.ReqData, baseline.ReqData) } } else if sha256.Sum256(wantJSON) != sha256.Sum256(gotJSON) { t.Fatalf("APN冻结请求摘要改变:path=%s want=%s got=%s", path, wantJSON, gotJSON) } if len(got.SimKeys) != 1 || *got.SimKeys[0] != *input.SimKeys[0] { t.Fatalf("代理生产绑定丢字段:%+v", got) } default: t.Fatalf("未经过生产代理controller:%s", body) } } } } func TestBindHTTPRoutesRegistersChannelControlRoutes(t *testing.T) { server := ghttp.GetServer("proxy-channel-refresh-route-test") server.SetAddr("127.0.0.1:0") bindHTTPRoutes(server) if err := server.Start(); err != nil { t.Fatalf("start HTTP server: %v", err) } t.Cleanup(func() { if err := server.Shutdown(); err != nil { t.Errorf("shutdown HTTP server: %v", err) } }) want := map[string]bool{ "/internal/channel/refresh": false, "/internal/channel/remove": false, "/internal/app-diagnosis/sim/*any": false, } for _, route := range server.GetRoutes() { if _, ok := want[route.Route]; ok && strings.EqualFold(route.Method, "POST") { want[route.Route] = true } } for route, found := range want { if !found { t.Fatalf("POST %s route was not registered: %+v", route, server.GetRoutes()) } } }