From 60ab4b2c223cf7887aed4355ad8f76f6c8748dc4 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Thu, 31 Oct 2024 17:34:15 +0800 Subject: [PATCH 1/7] feat: Goctl api gogen add unit test support --story=1 --- tools/goctl/api/cmd.go | 1 + tools/goctl/api/gogen/gen.go | 12 +- tools/goctl/api/gogen/gen_test.go | 2 +- tools/goctl/api/gogen/genhandlerstest.go | 79 ++++ tools/goctl/api/gogen/genlogictest.go | 89 +++++ tools/goctl/api/gogen/handler_test.tpl | 48 +++ tools/goctl/api/gogen/logic_test.tpl | 66 ++++ tools/goctl/api/gogen/template.go | 4 + tools/goctl/api/new/newservice.go | 2 +- tools/goctl/testdata/tcip-mgr.api | 477 +++++++++++++++++++++++ 10 files changed, 775 insertions(+), 5 deletions(-) create mode 100644 tools/goctl/api/gogen/genhandlerstest.go create mode 100644 tools/goctl/api/gogen/genlogictest.go create mode 100644 tools/goctl/api/gogen/handler_test.tpl create mode 100644 tools/goctl/api/gogen/logic_test.tpl create mode 100644 tools/goctl/testdata/tcip-mgr.api diff --git a/tools/goctl/api/cmd.go b/tools/goctl/api/cmd.go index 0863805285eb..3652f0f5fc07 100644 --- a/tools/goctl/api/cmd.go +++ b/tools/goctl/api/cmd.go @@ -72,6 +72,7 @@ func init() { goCmdFlags.StringVar(&gogen.VarStringHome, "home") goCmdFlags.StringVar(&gogen.VarStringRemote, "remote") goCmdFlags.StringVar(&gogen.VarStringBranch, "branch") + goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "withtest") goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat) javaCmdFlags.StringVar(&javagen.VarStringDir, "dir") diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go index 6f568fe5b783..676cfc37601a 100644 --- a/tools/goctl/api/gogen/gen.go +++ b/tools/goctl/api/gogen/gen.go @@ -38,7 +38,8 @@ var ( // VarStringBranch describes the branch. VarStringBranch string // VarStringStyle describes the style of output files. - VarStringStyle string + VarStringStyle string + VarBoolWithTest bool ) // GoCommand gen go project files from command line @@ -49,6 +50,7 @@ func GoCommand(_ *cobra.Command, _ []string) error { home := VarStringHome remote := VarStringRemote branch := VarStringBranch + withTest := VarBoolWithTest if len(remote) > 0 { repo, _ := util.CloneIntoGitHome(remote, branch) if len(repo) > 0 { @@ -66,11 +68,11 @@ func GoCommand(_ *cobra.Command, _ []string) error { return errors.New("missing -dir") } - return DoGenProject(apiFile, dir, namingStyle) + return DoGenProject(apiFile, dir, namingStyle, withTest) } // DoGenProject gen go project files with api file -func DoGenProject(apiFile, dir, style string) error { +func DoGenProject(apiFile, dir, style string, withTest bool) error { api, err := parser.Parse(apiFile) if err != nil { return err @@ -100,6 +102,10 @@ func DoGenProject(apiFile, dir, style string) error { logx.Must(genHandlers(dir, rootPkg, cfg, api)) logx.Must(genLogic(dir, rootPkg, cfg, api)) logx.Must(genMiddleware(dir, cfg, api)) + if withTest { + logx.Must(genHandlersTest(dir, rootPkg, cfg, api)) + logx.Must(genLogicTest(dir, rootPkg, cfg, api)) + } if err := backupAndSweep(apiFile); err != nil { return err diff --git a/tools/goctl/api/gogen/gen_test.go b/tools/goctl/api/gogen/gen_test.go index 56d5f9250140..5fe8207c0954 100644 --- a/tools/goctl/api/gogen/gen_test.go +++ b/tools/goctl/api/gogen/gen_test.go @@ -348,7 +348,7 @@ func validateWithCamel(t *testing.T, api, camel string) { assert.Nil(t, err) err = initMod(dir) assert.Nil(t, err) - err = DoGenProject(api, dir, camel) + err = DoGenProject(api, dir, camel, true) assert.Nil(t, err) filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if strings.HasSuffix(path, ".go") { diff --git a/tools/goctl/api/gogen/genhandlerstest.go b/tools/goctl/api/gogen/genhandlerstest.go new file mode 100644 index 000000000000..e98a5587f710 --- /dev/null +++ b/tools/goctl/api/gogen/genhandlerstest.go @@ -0,0 +1,79 @@ +package gogen + +import ( + _ "embed" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/tools/goctl/api/spec" + "github.com/zeromicro/go-zero/tools/goctl/config" + "github.com/zeromicro/go-zero/tools/goctl/util" + "github.com/zeromicro/go-zero/tools/goctl/util/format" + "github.com/zeromicro/go-zero/tools/goctl/util/pathx" +) + +//go:embed handler_test.tpl +var handlerTestTemplate string + +func genHandlerTest(dir, rootPkg string, cfg *config.Config, group spec.Group, route spec.Route) error { + handler := getHandlerName(route) + handlerPath := getHandlerFolderPath(group, route) + pkgName := handlerPath[strings.LastIndex(handlerPath, "/")+1:] + logicName := defaultLogicPackage + if handlerPath != handlerDir { + handler = strings.Title(handler) + logicName = pkgName + } + filename, err := format.FileNamingFormat(cfg.NamingFormat, handler) + if err != nil { + return err + } + + return genFile(fileGenConfig{ + dir: dir, + subdir: getHandlerFolderPath(group, route), + filename: filename + "_test.go", + templateName: "handlerTestTemplate", + category: category, + templateFile: handlerTestTemplateFile, + builtinTemplate: handlerTestTemplate, + data: map[string]any{ + "PkgName": pkgName, + "ImportPackages": genHandlerTestImports(group, route, rootPkg), + "HandlerName": handler, + "RequestType": util.Title(route.RequestTypeName()), + "LogicName": logicName, + "LogicType": strings.Title(getLogicName(route)), + "Call": strings.Title(strings.TrimSuffix(handler, "Handler")), + "HasResp": len(route.ResponseTypeName()) > 0, + "HasRequest": len(route.RequestTypeName()) > 0, + "HasDoc": len(route.JoinedDoc()) > 0, + "Doc": getDoc(route.JoinedDoc()), + }, + }) +} + +func genHandlersTest(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error { + for _, group := range api.Service.Groups { + for _, route := range group.Routes { + if err := genHandlerTest(dir, rootPkg, cfg, group, route); err != nil { + return err + } + } + } + + return nil +} + +func genHandlerTestImports(group spec.Group, route spec.Route, parentPkg string) string { + imports := []string{ + //fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, getLogicFolderPath(group, route))), + fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir)), + fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, configDir)), + } + if len(route.RequestTypeName()) > 0 { + imports = append(imports, fmt.Sprintf("\"%s\"\n", pathx.JoinPackages(parentPkg, typesDir))) + } + + return strings.Join(imports, "\n\t") +} diff --git a/tools/goctl/api/gogen/genlogictest.go b/tools/goctl/api/gogen/genlogictest.go new file mode 100644 index 000000000000..ca1be1bc02dd --- /dev/null +++ b/tools/goctl/api/gogen/genlogictest.go @@ -0,0 +1,89 @@ +package gogen + +import ( + _ "embed" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/tools/goctl/api/spec" + "github.com/zeromicro/go-zero/tools/goctl/config" + "github.com/zeromicro/go-zero/tools/goctl/util/format" + "github.com/zeromicro/go-zero/tools/goctl/util/pathx" +) + +//go:embed logic_test.tpl +var logicTestTemplate string + +func genLogicTest(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) error { + for _, g := range api.Service.Groups { + for _, r := range g.Routes { + err := genLogicTestByRoute(dir, rootPkg, cfg, g, r) + if err != nil { + return err + } + } + } + return nil +} + +func genLogicTestByRoute(dir, rootPkg string, cfg *config.Config, group spec.Group, route spec.Route) error { + logic := getLogicName(route) + goFile, err := format.FileNamingFormat(cfg.NamingFormat, logic) + if err != nil { + return err + } + + imports := genLogicTestImports(route, rootPkg) + var responseString string + var returnString string + var requestString string + var requestType string + if len(route.ResponseTypeName()) > 0 { + resp := responseGoTypeName(route, typesPacket) + responseString = "(resp " + resp + ", err error)" + returnString = "return" + } else { + responseString = "error" + returnString = "return nil" + } + if len(route.RequestTypeName()) > 0 { + requestString = "req *" + requestGoTypeName(route, typesPacket) + requestType = requestGoTypeName(route, typesPacket) + } + + subDir := getLogicFolderPath(group, route) + return genFile(fileGenConfig{ + dir: dir, + subdir: subDir, + filename: goFile + "_test.go", + templateName: "logicTestTemplate", + category: category, + templateFile: logicTestTemplateFile, + builtinTemplate: logicTestTemplate, + data: map[string]any{ + "pkgName": subDir[strings.LastIndex(subDir, "/")+1:], + "imports": imports, + "logic": strings.Title(logic), + "function": strings.Title(strings.TrimSuffix(logic, "Logic")), + "responseType": responseString, + "returnString": returnString, + "request": requestString, + "hasRequest": len(requestType) > 0, + "requestType": requestType, + "hasDoc": len(route.JoinedDoc()) > 0, + "doc": getDoc(route.JoinedDoc()), + }, + }) +} + +func genLogicTestImports(route spec.Route, parentPkg string) string { + var imports []string + //imports = append(imports, `"context"`+"\n") + imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir))) + imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, configDir))) + if shallImportTypesPackage(route) { + imports = append(imports, fmt.Sprintf("\"%s\"\n", pathx.JoinPackages(parentPkg, typesDir))) + } + //imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceURL)) + return strings.Join(imports, "\n\t") +} diff --git a/tools/goctl/api/gogen/handler_test.tpl b/tools/goctl/api/gogen/handler_test.tpl new file mode 100644 index 000000000000..ae0c677cd41d --- /dev/null +++ b/tools/goctl/api/gogen/handler_test.tpl @@ -0,0 +1,48 @@ +package {{.PkgName}} + +import ( + "bytes" + {{if .HasRequest}}"encoding/json"{{end}} + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + {{.ImportPackages}} +) + +{{if .HasDoc}}{{.Doc}}{{end}} +func Test{{.HandlerName}}(t *testing.T) { + // 创建一个ServiceContext实例 + c := config.Config{} + svcCtx := svc.NewServiceContext(c) + + // 创建一个HTTP请求 + reqBody := []byte{} + {{if .HasRequest}} + reqObj:= types.{{.RequestType}}{ + //TODO: add fields here + } + reqBody, _ = json.Marshal(reqObj) + {{end}} + req, err := http.NewRequest("POST", "/unittest", bytes.NewBuffer(reqBody)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + + // 创建一个HTTP响应记录器 + rr := httptest.NewRecorder() + + // 创建一个LoginHandler实例 + handler := {{.HandlerName}}(svcCtx) + + // 调用LoginHandler + handler.ServeHTTP(rr, req) + + // 检查响应状态码 + assert.Equal(t, http.StatusOK, rr.Code) + + // 检查响应体 + t.Log(rr.Body.String()) +} diff --git a/tools/goctl/api/gogen/logic_test.tpl b/tools/goctl/api/gogen/logic_test.tpl new file mode 100644 index 000000000000..00b273c570ff --- /dev/null +++ b/tools/goctl/api/gogen/logic_test.tpl @@ -0,0 +1,66 @@ +package {{.pkgName}} + +import ( + "context" + "testing" + + {{.imports}} + "github.com/stretchr/testify/assert" +) + + +func Test{{.logic}}_{{.function}}(t *testing.T) { + c := config.Config{} + mockSvcCtx := svc.NewServiceContext(c) + + tests := []struct { + name string + ctx context.Context + setupMocks func() + {{if .hasRequest}}req *{{.requestType}}{{end}} + wantErr bool + checkResp func{{.responseType}} + }{ + { + name: "successful", + ctx: context.Background(), + setupMocks: func() { + // No setup needed for this test case + }, + {{if .hasRequest}}req: &{{.requestType}}{ + // init your request here + },{{end}} + wantErr: false, + checkResp: func{{.responseType}} { + // Add your check logic here + }, + }, + { + name: "response error", + ctx: context.Background(), + setupMocks: func() { + // No setup needed for this test case + }, + {{if .hasRequest}}req: &{{.requestType}}{ + // init your request here + },{{end}} + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + l := New{{.logic}}(tt.ctx, mockSvcCtx) + resp, err := l.{{.function}}({{if .hasRequest}}tt.req{{end}}) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, resp) + } else { + assert.NoError(t, err) + assert.NotNil(t, resp) + tt.checkResp(resp, err) + } + }) + } +} diff --git a/tools/goctl/api/gogen/template.go b/tools/goctl/api/gogen/template.go index 580fef4f8b3f..1bb05dc5a4ed 100644 --- a/tools/goctl/api/gogen/template.go +++ b/tools/goctl/api/gogen/template.go @@ -12,7 +12,9 @@ const ( contextTemplateFile = "context.tpl" etcTemplateFile = "etc.tpl" handlerTemplateFile = "handler.tpl" + handlerTestTemplateFile = "handler_test.tpl" logicTemplateFile = "logic.tpl" + logicTestTemplateFile = "logic_test.tpl" mainTemplateFile = "main.tpl" middlewareImplementCodeFile = "middleware.tpl" routesTemplateFile = "routes.tpl" @@ -25,7 +27,9 @@ var templates = map[string]string{ contextTemplateFile: contextTemplate, etcTemplateFile: etcTemplate, handlerTemplateFile: handlerTemplate, + handlerTestTemplateFile: handlerTestTemplate, logicTemplateFile: logicTemplate, + logicTestTemplateFile: logicTestTemplate, mainTemplateFile: mainTemplate, middlewareImplementCodeFile: middlewareImplementCode, routesTemplateFile: routesTemplate, diff --git a/tools/goctl/api/new/newservice.go b/tools/goctl/api/new/newservice.go index 9241d8559afd..7780a240193f 100644 --- a/tools/goctl/api/new/newservice.go +++ b/tools/goctl/api/new/newservice.go @@ -83,6 +83,6 @@ func CreateServiceCommand(_ *cobra.Command, args []string) error { return err } - err = gogen.DoGenProject(apiFilePath, abs, VarStringStyle) + err = gogen.DoGenProject(apiFilePath, abs, VarStringStyle, false) return err } diff --git a/tools/goctl/testdata/tcip-mgr.api b/tools/goctl/testdata/tcip-mgr.api new file mode 100644 index 000000000000..f80f7ee22247 --- /dev/null +++ b/tools/goctl/testdata/tcip-mgr.api @@ -0,0 +1,477 @@ +syntax = "v1" + +info ( + title: "tcip-mgr-service" // + desc: "tcip manage service" // + author: "" + email: "" +) + +type ( + LoginReq { + UserName string `json:"username"` + Password string `json:"password"` + } + LoginResp { + UserId int64 `json:"userId"` + UserName string `json:"userName"` + Role string `json:"role"` // 1 admin, 2 中继网关运营方, 3 子链接入方 + } + PageReq { + PageNum int `json:"pageNum,default=1"` + PageSize int `json:"pageSize,default=10"` + } + UserListReq { + UserName string `json:"userName,optional"` + PageReq + } + UserListResp { + Total int `json:"total"` + List []UserListData `json:"list"` + } + UserListData { + EnterpriseName string `json:"enterpriseName"` //公司名 + PhoneNumber string `json:"phoneNumber"` //手机号 + UserName string `json:"userName"` //用户名 + Role string `json:"role"` //角色1、admin,2、中继网关,3、跨链网关 + CreateAt int `json:"createAt"` //创建时间 + Status int `json:"status"` //1、启用2、禁用 + UserId int `json:"userId"` + } +) + +type ( + RegisterReq { + Password string `json:"password"` + EnterpriseName string `json:"enterpriseName,optional"` + PhoneNumber string `json:"phoneNumber,optional"` + UserName string `json:"userName"` + Role string `json:"role,options=[1,2,3]"` // 1 admin, 2 中继网关运营方, 3 子链接入方 + } + DisableUserReq { + UserId int `json:"userId"` + Status int `json:"status,options=[1,2]"` + } + EditUserReq { + EnterpriseName string `json:"enterpriseName"` + PhoneNumber string `json:"phoneNumber"` + UserName string `json:"userName"` + Role string `json:"role,options=[2,3]"` // 1 admin, 2 中继网关运营方, 3 子链接入方 + UserId int `json:"userId"` + } +) + +type ( + ResetPwReq { + UserId int64 `json:"userId"` + NewPwd string `json:"newPwd"` + } +) + +@server ( + prefix: /api/v1/login + group: login + timeout: 60s // 对当前 Foo 语法块下的所有路由进行超时配置,不需要则请删除此行 + maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 +) +service api { + @doc "登录" + @handler LoginHandler + post /login (LoginReq) returns (LoginResp) + + @doc "注册用户" + @handler RegisterHandler + post /register (RegisterReq) returns (bool) + + @doc "用户列表" + @handler UserListHandler + post /userList (UserListReq) returns (UserListResp) + + @doc "用户信息" + @handler UserInfoHandler + post /userInfo returns (UserListData) + + @doc "启用禁用用户" + @handler DisableUserHandler + post /disableUser (DisableUserReq) returns (bool) + + @doc "重置密码" + @handler ResetPwdHandler + post /resetPwd (ResetPwReq) returns (bool) + + @doc "注册用户" + @handler EditUserHandler + post /editUser (EditUserReq) returns (bool) + + @doc "获取当前服务的token" + @handler GetServerTokenHandler + post /serverToken returns (string) +} + +type ( + HealthResp { + Version string `json:"version"` + Name string `json:"name"` + BuildTime string `json:"buildTime"` + GitBranch string `json:"gitBranch"` + GitCommit string `json:"gitCommit"` + Now string `json:"now"` + StartTime string `json:"startTime"` + } +) + +service api { + @doc "获取服务的健康状态" + @handler HealthHandler + get /api/v1/health returns (HealthResp) +} + +type ( + RelayGatewayInfo { + RelayGatewayId string `json:"relayGatewayId"` + RelayGatewayName string `json:"relayGatewayName"` + CrossBatchContract string `json:"crossBatchContract"` + CrossContract string `json:"crossContract"` + Address string `json:"address"` + AuthCodeKey string `json:"authCodeKey"` + Timeout int `json:"timeout"` + Port int `json:"port"` + CreateAt int `json:"createAt,optional"` //创建时间 + } + NewRelayGatewayInfo { + RelayGatewayInfo + Option int `json:"option,options=[1,2]"` + } + ListRelayGatewaysReq { + RelayGatewayName string `json:"relayGatewayName,optional"` + PageReq + } + ListRelayGatewaysResp { + Total int `json:"total"` + List []RelayGatewayInfo `json:"list"` + } + ConnectRelayGatewayReq { + GatewayId string `json:"gatewayId"` //中继网关ID + } + Node { + NodeAddr string `json:"nodeAddr"` + ConnCnt string `json:"connCnt,default=10,optional"` + EnableTls bool `json:"enableTls,optional"` + TrustRoot string `json:"trustRoot,optional"` + TlsHostName string `json:"tlsHostName,optional"` + } + ChainMakerConfig { + ChainId string `json:"chainId"` + OrgId string `json:"orgId,optional"` + SignCert string `json:"signCert,optional"` + SignKey string `json:"signKey"` + TlsCert string `json:"tlsCert,optional"` + TlsKey string `json:"tlsKey,optional"` + AuthType string `json:"authType"` + HashType string `json:"hashType"` + Node []Node `json:"node"` + } + BcosConfig { + Ca string `json:"ca"` + TlsCert string `json:"tlsCert"` + TlsKey string `json:"tlsKey"` + PrivateKey string `json:"privateKey"` + GroupId string `json:"groupId"` + Address string `json:"address"` + JsonRpcAddress string `json:"jsonRpcAddress,optional"` + ChainId string `json:"chainId"` + IsSmCrypto bool `json:"isSmCrypto"` + CrossContractAbi bool `json:"crossContractAbi"` + } + Org { + OrgId string `json:"orgId"` + MspId string `json:"mspId"` + SignCert string `json:"signCert"` + SignKey string `json:"signKey"` + Peers []Node `json:"peers"` + } + FabricConfig { + ChainId string `json:"chainId"` + Orderers []Node `json:"orderers"` + Orgs []Org `json:"orgs"` + } + AntChainConfig { + RestUrl string `json:"restUrl"` + Account string `json:"account,optional"` + AccessId string `json:"accessId,optional"` + AccessSecret string `json:"accessSecret,optional"` + TtenantId string `json:"TtenantId,optional"` + BizId string `json:"bizId"` + } + AntConfig { + Ext string `json:"ext,optional"` + ChainConfig AntChainConfig `json:"chainConfig"` + } + MainChainReq { + CrossContract string `json:"crossContract,optional"` + Option int64 `json:"option,options=[1,2]"` + ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` + } + MainChainInfoReq {} + MainChainInfoResp { + CrossContract string `json:"crossContract,optional"` + ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` + } + ApplytReq { + ID int `json:"id"` + ApplyStatus int `json:"applyStatus,options=[2,3]"` + Reason string `json:"reason,optional"` + Source int `json:"source,default=1"` + } +) + +@server ( + prefix: /api/v1/relayer + group: relaygateway + timeout: 60s + maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 +) +service api { + @doc "新建中继网关" + @handler NewRelayGatewayHandler + post /newGateway (NewRelayGatewayInfo) returns (bool) + + @doc "获取中继网关列表" + @handler ListRelayGatewaysHandler + post /gatewayList (ListRelayGatewaysReq) returns (ListRelayGatewaysResp) + + @doc "连接中继网关" + @handler connGatewayHandler + post /connGateway (ConnectRelayGatewayReq) returns (bool) + + @doc "授权" + @handler ApplyHandler + post /apply (ApplytReq) returns (bool) + + @doc "主链添加" + @handler MainChainHandler + post /mainChain (MainChainReq) returns (bool) + + @doc "主链信息" + @handler MainChainInfoHandler + post /mainChainInfo (MainChainInfoReq) returns (MainChainInfoResp) +} + +type ( + CrossGatewayInfo { + CrossGatewayId string `json:"crossGatewayId,optional"` + CrossGatewayName string `json:"crossGatewayName"` + VerifyType int `json:"verifyType,options=[1,2]"` + SyncHeaderInterval int `json:"syncHeaderInterval,default=300"` + SyncHeaderCount int `json:"syncHeaderCount,default=1000"` + Address string `json:"address"` + Timeout int `json:"timeout,default=30"` + Port int `json:"port,default=8888"` + GatewayType int `json:"gatewayType,options=[1,2,3,4]"` + Status int `json:"status,optional"` + SubChainCount int `json:"subChainCount,optional"` + ApplyStatus int `json:"applyStatus,optional"` + CreateAt int `json:"createAt,optional"` + } + NewCrossGatewayReq { + CrossGatewayInfo + Option int `json:"option,options=[1,2]"` + } + ListCrossGatewaysReq { + CrossGatewayName string `json:"crossGatewayName,optional"` + PageReq + } + ListCrossGatewaysResp { + Total int `json:"total"` + List []CrossGatewayInfo `json:"list"` + } + ApplyRelayGatewayAuthReq { + RelayGatewayId string `json:"relayGatewayId"` + RelayGatewayName string `json:"relayGatewayName"` + RelayGatewayAddress string `json:"relayGatewayAddress"` + RelayGatewayToken string `json:"relayGatewayToken"` + CrossGatewayAddress string `json:"crossGatewayAddress"` + CrossGatewayOnChainAddress string `json:"crossGatewayOnChainAddress,optional"` + TxVerifyType int `json:"TxVerifyType,optional"` + ApplyMsg string `json:"applyMsg"` + CrossGatewayName string `json:"crossGatewayName"` + Source int `json:"source,default=1"` + } + ApplyRelayGatewayAuthResp { + FormId string `json:"formId"` //申请单ID + } + SubchainListReq { + ChainRid string `json:"chianRid,optional"` + GatewayId string `json:"gatewayId"` + PageReq + } + SubchainListResp { + Total int `json:"total"` + List []SubChainInfo `json:"list"` + } + ConnectCrossGatewayReq { + GatewayId string `json:"gatewayId"` //跨链网关ID + } + UpdateApplyStatusReq { + ApplyStatus int `json:"applyStatus,options=[2,3]"` + Reason string `json:"reason"` + CrossGatewayName string `json:"crossGatewayAddress"` + RelayGatewayId string `json:"relayGatewayId,optional"` + CrossGatewayId string `json:"crossGatewayId,optional"` + AuthCode string `json:"auth_code,optional"` + TLSServerName string `json:"tls_server_name,optional"` // tls server name + TLSCert string `json:"tls_cert,optional"` // tls server cert + TLSKey string `json:"tls_key,optional"` // tls server key + TLSCa string `json:"tls_ca,optional"` // tls server ca + TLSCaKey string `json:"tls_ca_key,optional"` // tls server ca key + ClientTLSCert string `json:"client_tls_cert,optional"` // 客户端tls证书 + ClientTLSKey string `json:"client_tls_key,optional"` // 客户端tls私钥 + RelayClientTLSCert string `json:"relay_client_tls_cert,optional"` // 中继网关客户端tls证书 + RelayClientTLSKey string `json:"relay_client_tls_key,optional"` // 中继网关客户端tls私钥 + RelayClientTLSCa string `json:"relay_client_tls_ca,optional"` // 中继网关ca证书 + RelayCallClientTLSCert string `json:"relay_call_client_tls_cert,optional"` // 中继网关的私钥在跨链网关ca签出的客户端tls证书 + RelayAddress string `json:"relay_address,optional"` + } + SubChainReq { + GatewayId string `json:"gatewayId"` + ChainType int `json:"chainType"` + ChainRid string `json:"chainRid"` + CrossContract string `json:"crossContract"` + Option int `json:"option,options=[1,2]"` + SpvContract string `json:"spvContract,optional"` + ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig,optional"` + BcosConfig BcosConfig `json:"bcosConfig,optional"` + FabricConfig FabricConfig `json:"fabricConfig,optional"` + AntConfig AntConfig `json:"antConfig,optional"` + } + SubChainInfo { + GatewayId string `json:"gatewayId"` + ChainType int `json:"chainType"` + ChainRid string `json:"chainRid"` + CrossContract string `json:"crossContract"` + SpvContract string `json:"spvContract"` + ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` + BcosConfig BcosConfig `json:"bcosConfig"` + FabricConfig FabricConfig `json:"fabricConfig"` + AntConfig AntConfig `json:"antConfig"` + } +) + +@server ( + prefix: /api/v1/cross + group: crossgateway + timeout: 60s + maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 +) +service api { + @doc "新建跨链网关" + @handler NewCrossGatewayHandler + post /newGateway (NewCrossGatewayReq) returns (bool) + + @doc "更新跨链网关授权信息" + @handler UpdateApplyStatusHandler + post /updateApplyStatus (UpdateApplyStatusReq) returns (bool) + + @doc "子链添加" + @handler subChainHandler + post /subChain (SubChainReq) returns (bool) + + @doc "获取跨链网关列表" + @handler ListCrossGatewaysHandler + post /gatewayList (ListCrossGatewaysReq) returns (ListCrossGatewaysResp) + + @doc "申请中继网关的授权" + @handler ApplyRelayGatewayAuthHandler + post /apply (ApplyRelayGatewayAuthReq) returns (ApplyRelayGatewayAuthResp) + + @doc "子链列表" + @handler SubchainListHandler + post /subChainList (SubchainListReq) returns (SubchainListResp) + + @doc "连接跨链网关" + @handler ConnectCrossGatewayHandler + post /connGateway (ConnectCrossGatewayReq) returns (bool) +} + +type ( + CrossConfigInfo { + SrcChainRid string `json:"srcChainRid"` + SrcGatewayId string `json:"srcGatewayId"` + SrcContractName string `json:"srcContractName"` + SrcConfirmMethod string `json:"srcConfirmMethod,optional"` + SrcCancelMethod string `json:"srcCancelMethod,optional"` + SrcAbi string `json:"srcAbi,optional"` + DestGatewayId string `json:"destGatewayId"` + DestChainRid string `json:"destChainRid"` + DestContractName string `json:"destContractName"` + DestTryMethod string `json:"destTryMethod"` + DestConfirmMethod string `json:"destConfirmMethod,optional"` + DestCancelMethod string `json:"destCancelMethod,optional"` + DestAbi string `json:"destAbi,optional"` + CrossId string `json:"crossId,optional"` + ConfigContractName string `json:"configContractName"` + Desc string `json:"desc,optional"` + TriggerCrossType int `json:"triggerCrossType"` + ConfigContractAbi string `json:"configContractAbi,optional"` + } + NewEventReq { + CrossConfigInfo + } + EventListConfigReq { + CrossId string `json:"crossId,optional"` + GatewayId string `json:"gatewayId"` + PageReq + } + ListCrossConfigResp { + List []CrossConfigInfo `json:"list"` + Total int `json:"total"` + } + ApplyListReq { + RelayGatewayId string `json:"relayGatewayId,optional"` + CrossGatewayName string `json:"crossGatewayName,optional"` + PageReq + } + ApplyListResp { + Total int `json:"total"` + List []ApplyListItem `json:"list"` + } + ApplyListItem { + ID int `json:"id"` + ApplyTime int64 `json:"applyTime"` + ApplyMsg string `json:"applyMsg"` + CrossGatewayName string `json:"crossGatewayName"` + ApplyStatus int `json:"applyStatus"` + CrossGatewayId string `json:"crossGatewayId"` + AgreeTime int64 `json:"agreeTime"` + Reason string `json:"reason"` + RelayGatewayName string `json:"relayGatewayName"` + } + DownloadReq { + GatewayId string `json:"gatewayId"` + } +) + +@server ( + prefix: /api/v1/common + group: common + timeout: 60s + maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 +) +service api { + @doc "新建一个跨链配置" + @handler NewEventHandler + post /newEvent (NewEventReq) returns (bool) + + @doc "获取跨链配置列表" + @handler EventListConfigHandler + post /eventList (EventListConfigReq) returns (ListCrossConfigResp) + + @doc "申请列表" + @handler ApplyListHandler + post /applyList (ApplyListReq) returns (ApplyListResp) + + @doc "下载" + @handler DownloadHandler + post /download (DownloadReq) returns ([]byte) +} + From 5a62a6849278b3101545c57b1a49b37453ad1911 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Thu, 31 Oct 2024 18:11:47 +0800 Subject: [PATCH 2/7] fix: Default unit test tpl --bug=1 --- tools/goctl/api/gogen/handler_test.tpl | 89 +++++++++++++++++--------- tools/goctl/api/gogen/logic_test.tpl | 11 ++-- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/tools/goctl/api/gogen/handler_test.tpl b/tools/goctl/api/gogen/handler_test.tpl index ae0c677cd41d..920500250ad5 100644 --- a/tools/goctl/api/gogen/handler_test.tpl +++ b/tools/goctl/api/gogen/handler_test.tpl @@ -13,36 +13,67 @@ import ( {{if .HasDoc}}{{.Doc}}{{end}} func Test{{.HandlerName}}(t *testing.T) { - // 创建一个ServiceContext实例 - c := config.Config{} - svcCtx := svc.NewServiceContext(c) + // new service context + c := config.Config{} + svcCtx := svc.NewServiceContext(c) - // 创建一个HTTP请求 - reqBody := []byte{} - {{if .HasRequest}} - reqObj:= types.{{.RequestType}}{ - //TODO: add fields here - } - reqBody, _ = json.Marshal(reqObj) - {{end}} - req, err := http.NewRequest("POST", "/unittest", bytes.NewBuffer(reqBody)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") + tests := []struct { + name string + reqBody interface{} + setupMocks func() + wantStatus int + wantResp string + }{ + { + name: "invalid request body", + reqBody: "invalid", + setupMocks: func() { + // No setup needed for this test case + }, + wantStatus: http.StatusBadRequest, + wantResp: `{"code":400,"msg":"invalid request"}`, // Adjust based on actual error response + }, + { + name: "handler error", + {{if .HasRequest}}reqBody: types.{{.RequestType}}{ + //TODO: add fields here + }, + {{end}}setupMocks: func() { + // Mock login logic to return an error + }, + wantStatus: http.StatusUnauthorized, + wantResp: `{"code":401,"msg":"unauthorized"}`, // Adjust based on actual error response + }, + { + name: "handler successful", + {{if .HasRequest}}reqBody: types.{{.RequestType}}{ + //TODO: add fields here + }, + {{end}}setupMocks: func() { + // Mock login logic to return success + }, + wantStatus: http.StatusOK, + wantResp: `{"code":0,"msg":"success","data":{}}`, // Adjust based on actual success response + }, + } - // 创建一个HTTP响应记录器 - rr := httptest.NewRecorder() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + reqBody := []byte{} + {{if .HasRequest}}reqBody, _ = json.Marshal(tt.reqBody){{end}} + req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") - // 创建一个LoginHandler实例 - handler := {{.HandlerName}}(svcCtx) - - // 调用LoginHandler - handler.ServeHTTP(rr, req) - - // 检查响应状态码 - assert.Equal(t, http.StatusOK, rr.Code) - - // 检查响应体 - t.Log(rr.Body.String()) + rr := httptest.NewRecorder() + handler := {{.HandlerName}}(svcCtx) + handler.ServeHTTP(rr, req) + t.Log(rr.Body.String()) + assert.Equal(t, tt.wantStatus, rr.Code) + assert.JSONEq(t, tt.wantResp, rr.Body.String()) + }) + } } diff --git a/tools/goctl/api/gogen/logic_test.tpl b/tools/goctl/api/gogen/logic_test.tpl index 00b273c570ff..d3fd13a4143b 100644 --- a/tools/goctl/api/gogen/logic_test.tpl +++ b/tools/goctl/api/gogen/logic_test.tpl @@ -12,6 +12,7 @@ import ( func Test{{.logic}}_{{.function}}(t *testing.T) { c := config.Config{} mockSvcCtx := svc.NewServiceContext(c) + // init mock service context here tests := []struct { name string @@ -25,24 +26,24 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { name: "successful", ctx: context.Background(), setupMocks: func() { - // No setup needed for this test case + // Mock data for this test case }, {{if .hasRequest}}req: &{{.requestType}}{ - // init your request here + // TODO: init your request here },{{end}} wantErr: false, checkResp: func{{.responseType}} { - // Add your check logic here + // TODO: Add your check logic here }, }, { name: "response error", ctx: context.Background(), setupMocks: func() { - // No setup needed for this test case + // mock data for this test case }, {{if .hasRequest}}req: &{{.requestType}}{ - // init your request here + // TODO: init your request here },{{end}} wantErr: true, }, From 6ef58983aa742e418d555154ec4adb90fd0900ce Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Thu, 31 Oct 2024 18:27:52 +0800 Subject: [PATCH 3/7] refactor: Unit test template --story=1 --- tools/goctl/api/gogen/handler_test.tpl | 130 ++++++++++++------------- tools/goctl/api/gogen/logic_test.tpl | 101 +++++++++---------- 2 files changed, 117 insertions(+), 114 deletions(-) diff --git a/tools/goctl/api/gogen/handler_test.tpl b/tools/goctl/api/gogen/handler_test.tpl index 920500250ad5..90c1e1ae6dde 100644 --- a/tools/goctl/api/gogen/handler_test.tpl +++ b/tools/goctl/api/gogen/handler_test.tpl @@ -2,78 +2,78 @@ package {{.PkgName}} import ( "bytes" - {{if .HasRequest}}"encoding/json"{{end}} - "net/http" - "net/http/httptest" - "testing" + {{if .HasRequest}}"encoding/json"{{end}} + "net/http" + "net/http/httptest" + "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" {{.ImportPackages}} ) {{if .HasDoc}}{{.Doc}}{{end}} -func Test{{.HandlerName}}(t *testing.T) { +func Test{{.HandlerName}}(t *testing.T) { // new service context - c := config.Config{} - svcCtx := svc.NewServiceContext(c) + c := config.Config{} + svcCtx := svc.NewServiceContext(c) - tests := []struct { - name string - reqBody interface{} - setupMocks func() - wantStatus int - wantResp string - }{ - { - name: "invalid request body", - reqBody: "invalid", - setupMocks: func() { - // No setup needed for this test case - }, - wantStatus: http.StatusBadRequest, - wantResp: `{"code":400,"msg":"invalid request"}`, // Adjust based on actual error response - }, - { - name: "handler error", - {{if .HasRequest}}reqBody: types.{{.RequestType}}{ - //TODO: add fields here - }, - {{end}}setupMocks: func() { - // Mock login logic to return an error - }, - wantStatus: http.StatusUnauthorized, - wantResp: `{"code":401,"msg":"unauthorized"}`, // Adjust based on actual error response - }, - { - name: "handler successful", - {{if .HasRequest}}reqBody: types.{{.RequestType}}{ - //TODO: add fields here - }, - {{end}}setupMocks: func() { - // Mock login logic to return success - }, - wantStatus: http.StatusOK, - wantResp: `{"code":0,"msg":"success","data":{}}`, // Adjust based on actual success response - }, - } + tests := []struct { + name string + reqBody interface{} + wantStatus int + wantResp string + setupMocks func() + }{ + { + name: "invalid request body", + reqBody: "invalid", + wantStatus: http.StatusBadRequest, + wantResp: `{"code":400,"msg":"invalid request"}`, // Adjust based on actual error response + setupMocks: func() { + // No setup needed for this test case + }, + }, + { + name: "handler error", + {{if .HasRequest}}reqBody: types.{{.RequestType}}{ + //TODO: add fields here + }, + {{end}}wantStatus: http.StatusUnauthorized, + wantResp: `{"code":401,"msg":"unauthorized"}`, // Adjust based on actual error response + setupMocks: func() { + // Mock login logic to return an error + }, + }, + { + name: "handler successful", + {{if .HasRequest}}reqBody: types.{{.RequestType}}{ + //TODO: add fields here + }, + {{end}}wantStatus: http.StatusOK, + wantResp: `{"code":0,"msg":"success","data":{}}`, // Adjust based on actual success response + setupMocks: func() { + // Mock login logic to return success + }, + }, + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMocks() - reqBody := []byte{} - {{if .HasRequest}}reqBody, _ = json.Marshal(tt.reqBody){{end}} - req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + reqBody := []byte{} + {{if .HasRequest}}reqBody, err := json.Marshal(tt.reqBody) + require.NoError(t, err){{end}} + req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") - rr := httptest.NewRecorder() - handler := {{.HandlerName}}(svcCtx) - handler.ServeHTTP(rr, req) - t.Log(rr.Body.String()) - assert.Equal(t, tt.wantStatus, rr.Code) - assert.JSONEq(t, tt.wantResp, rr.Body.String()) - }) - } -} + rr := httptest.NewRecorder() + handler := {{.HandlerName}}(svcCtx) + handler.ServeHTTP(rr, req) + t.Log(rr.Body.String()) + assert.Equal(t, tt.wantStatus, rr.Code) + assert.JSONEq(t, tt.wantResp, rr.Body.String()) + }) + } +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/logic_test.tpl b/tools/goctl/api/gogen/logic_test.tpl index d3fd13a4143b..e6f217a28db8 100644 --- a/tools/goctl/api/gogen/logic_test.tpl +++ b/tools/goctl/api/gogen/logic_test.tpl @@ -4,64 +4,67 @@ import ( "context" "testing" - {{.imports}} - "github.com/stretchr/testify/assert" + {{.imports}} + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) - func Test{{.logic}}_{{.function}}(t *testing.T) { - c := config.Config{} - mockSvcCtx := svc.NewServiceContext(c) + c := config.Config{} + mockSvcCtx := svc.NewServiceContext(c) // init mock service context here - tests := []struct { - name string - ctx context.Context - setupMocks func() - {{if .hasRequest}}req *{{.requestType}}{{end}} - wantErr bool - checkResp func{{.responseType}} - }{ - { - name: "successful", - ctx: context.Background(), - setupMocks: func() { - // Mock data for this test case - }, - {{if .hasRequest}}req: &{{.requestType}}{ + tests := []struct { + name string + ctx context.Context + setupMocks func() + {{if .hasRequest}}req *{{.requestType}}{{end}} + wantErr bool + checkResp func(*{{.responseType}}, error) + }{ + { + name: "successful", + ctx: context.Background(), + setupMocks: func() { + // Mock data for this test case + }, + {{if .hasRequest}}req: &{{.requestType}}{ // TODO: init your request here },{{end}} - wantErr: false, - checkResp: func{{.responseType}} { + wantErr: false, + checkResp: func(resp *{{.responseType}}, err error) { // TODO: Add your check logic here }, - }, - { - name: "response error", - ctx: context.Background(), - setupMocks: func() { - // mock data for this test case - }, - {{if .hasRequest}}req: &{{.requestType}}{ + }, + { + name: "response error", + ctx: context.Background(), + setupMocks: func() { + // mock data for this test case + }, + {{if .hasRequest}}req: &{{.requestType}}{ // TODO: init your request here },{{end}} - wantErr: true, - }, - } + wantErr: true, + checkResp: func(resp *{{.responseType}}, err error) { + // TODO: Add your check logic here + }, + }, + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMocks() - l := New{{.logic}}(tt.ctx, mockSvcCtx) - resp, err := l.{{.function}}({{if .hasRequest}}tt.req{{end}}) - if tt.wantErr { - assert.Error(t, err) - assert.Nil(t, resp) - } else { - assert.NoError(t, err) - assert.NotNil(t, resp) - tt.checkResp(resp, err) - } - }) - } -} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + l := New{{.logic}}(tt.ctx, mockSvcCtx) + resp, err := l.{{.function}}({{if .hasRequest}}tt.req{{end}}) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, resp) + } else { + require.NoError(t, err) + assert.NotNil(t, resp) + tt.checkResp(resp, err) + } + }) + } +} \ No newline at end of file From 7b631c727c43fb7ab60ce5d66e00b4b6512877f0 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Thu, 31 Oct 2024 19:23:29 +0800 Subject: [PATCH 4/7] fix: Some bug --bug=1 --- tools/goctl/api/gogen/genhandlerstest.go | 1 + tools/goctl/api/gogen/logic_test.tpl | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/goctl/api/gogen/genhandlerstest.go b/tools/goctl/api/gogen/genhandlerstest.go index e98a5587f710..ea913144cbb7 100644 --- a/tools/goctl/api/gogen/genhandlerstest.go +++ b/tools/goctl/api/gogen/genhandlerstest.go @@ -42,6 +42,7 @@ func genHandlerTest(dir, rootPkg string, cfg *config.Config, group spec.Group, r "ImportPackages": genHandlerTestImports(group, route, rootPkg), "HandlerName": handler, "RequestType": util.Title(route.RequestTypeName()), + "ResponseType": util.Title(route.ResponseTypeName()), "LogicName": logicName, "LogicType": strings.Title(getLogicName(route)), "Call": strings.Title(strings.TrimSuffix(handler, "Handler")), diff --git a/tools/goctl/api/gogen/logic_test.tpl b/tools/goctl/api/gogen/logic_test.tpl index e6f217a28db8..7ed50e69099b 100644 --- a/tools/goctl/api/gogen/logic_test.tpl +++ b/tools/goctl/api/gogen/logic_test.tpl @@ -20,7 +20,7 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { setupMocks func() {{if .hasRequest}}req *{{.requestType}}{{end}} wantErr bool - checkResp func(*{{.responseType}}, error) + checkResp func{{.responseType}} }{ { name: "successful", @@ -32,7 +32,7 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { // TODO: init your request here },{{end}} wantErr: false, - checkResp: func(resp *{{.responseType}}, err error) { + checkResp: func{{.responseType}} { // TODO: Add your check logic here }, }, @@ -46,7 +46,7 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { // TODO: init your request here },{{end}} wantErr: true, - checkResp: func(resp *{{.responseType}}, err error) { + checkResp: func{{.responseType}} { // TODO: Add your check logic here }, }, From 057c48f17d80d2e5bba6d80e2c9342ad3bb95316 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Fri, 1 Nov 2024 13:38:56 +0800 Subject: [PATCH 5/7] fix: Unit test template bug --bug=1 --- tools/goctl/api/gogen/genlogictest.go | 1 + tools/goctl/api/gogen/handler_test.tpl | 9 +++++---- tools/goctl/api/gogen/logic_test.tpl | 19 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tools/goctl/api/gogen/genlogictest.go b/tools/goctl/api/gogen/genlogictest.go index ca1be1bc02dd..14f4ac21bd12 100644 --- a/tools/goctl/api/gogen/genlogictest.go +++ b/tools/goctl/api/gogen/genlogictest.go @@ -69,6 +69,7 @@ func genLogicTestByRoute(dir, rootPkg string, cfg *config.Config, group spec.Gro "returnString": returnString, "request": requestString, "hasRequest": len(requestType) > 0, + "hasResponse": len(route.ResponseTypeName()) > 0, "requestType": requestType, "hasDoc": len(route.JoinedDoc()) > 0, "doc": getDoc(route.JoinedDoc()), diff --git a/tools/goctl/api/gogen/handler_test.tpl b/tools/goctl/api/gogen/handler_test.tpl index 90c1e1ae6dde..e99277324ed7 100644 --- a/tools/goctl/api/gogen/handler_test.tpl +++ b/tools/goctl/api/gogen/handler_test.tpl @@ -17,6 +17,7 @@ func Test{{.HandlerName}}(t *testing.T) { // new service context c := config.Config{} svcCtx := svc.NewServiceContext(c) + // init mock service context here tests := []struct { name string @@ -29,7 +30,7 @@ func Test{{.HandlerName}}(t *testing.T) { name: "invalid request body", reqBody: "invalid", wantStatus: http.StatusBadRequest, - wantResp: `{"code":400,"msg":"invalid request"}`, // Adjust based on actual error response + wantResp: "unsupported type", // Adjust based on actual error response setupMocks: func() { // No setup needed for this test case }, @@ -39,8 +40,8 @@ func Test{{.HandlerName}}(t *testing.T) { {{if .HasRequest}}reqBody: types.{{.RequestType}}{ //TODO: add fields here }, - {{end}}wantStatus: http.StatusUnauthorized, - wantResp: `{"code":401,"msg":"unauthorized"}`, // Adjust based on actual error response + {{end}}wantStatus: http.StatusBadRequest, + wantResp: "error", // Adjust based on actual error response setupMocks: func() { // Mock login logic to return an error }, @@ -73,7 +74,7 @@ func Test{{.HandlerName}}(t *testing.T) { handler.ServeHTTP(rr, req) t.Log(rr.Body.String()) assert.Equal(t, tt.wantStatus, rr.Code) - assert.JSONEq(t, tt.wantResp, rr.Body.String()) + assert.Contains(t, rr.Body.String(), tt.wantResp) }) } } \ No newline at end of file diff --git a/tools/goctl/api/gogen/logic_test.tpl b/tools/goctl/api/gogen/logic_test.tpl index 7ed50e69099b..58288fdeed4b 100644 --- a/tools/goctl/api/gogen/logic_test.tpl +++ b/tools/goctl/api/gogen/logic_test.tpl @@ -23,29 +23,29 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { checkResp func{{.responseType}} }{ { - name: "successful", + name: "response error", ctx: context.Background(), setupMocks: func() { - // Mock data for this test case + // mock data for this test case }, {{if .hasRequest}}req: &{{.requestType}}{ // TODO: init your request here },{{end}} - wantErr: false, + wantErr: true, checkResp: func{{.responseType}} { // TODO: Add your check logic here }, }, { - name: "response error", + name: "successful", ctx: context.Background(), setupMocks: func() { - // mock data for this test case + // Mock data for this test case }, {{if .hasRequest}}req: &{{.requestType}}{ // TODO: init your request here },{{end}} - wantErr: true, + wantErr: false, checkResp: func{{.responseType}} { // TODO: Add your check logic here }, @@ -56,15 +56,14 @@ func Test{{.logic}}_{{.function}}(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tt.setupMocks() l := New{{.logic}}(tt.ctx, mockSvcCtx) - resp, err := l.{{.function}}({{if .hasRequest}}tt.req{{end}}) + {{if .hasResponse}}resp, {{end}}err := l.{{.function}}({{if .hasRequest}}tt.req{{end}}) if tt.wantErr { assert.Error(t, err) - assert.Nil(t, resp) } else { require.NoError(t, err) - assert.NotNil(t, resp) - tt.checkResp(resp, err) + {{if .hasResponse}}assert.NotNil(t, resp){{end}} } + tt.checkResp({{if .hasResponse}}resp, {{end}}err) }) } } \ No newline at end of file From 53670929c2e3ed4c4111ea9672695bad5f6f1b18 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Fri, 1 Nov 2024 15:46:08 +0800 Subject: [PATCH 6/7] fix: Handler_test.Tpl content error --bug=1 --- tools/goctl/api/gogen/handler_test.tpl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/goctl/api/gogen/handler_test.tpl b/tools/goctl/api/gogen/handler_test.tpl index e99277324ed7..f461f4ddda65 100644 --- a/tools/goctl/api/gogen/handler_test.tpl +++ b/tools/goctl/api/gogen/handler_test.tpl @@ -62,8 +62,9 @@ func Test{{.HandlerName}}(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setupMocks() - reqBody := []byte{} - {{if .HasRequest}}reqBody, err := json.Marshal(tt.reqBody) + var reqBody []byte + {{if .HasRequest}}var err error + reqBody, err = json.Marshal(tt.reqBody) require.NoError(t, err){{end}} req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) require.NoError(t, err) From f73ade3a0de1cdf8a80241b6e3f10a4cda0eaa5c Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Sun, 3 Nov 2024 18:56:44 +0800 Subject: [PATCH 7/7] refactor: --Withtest to --test and remove unused testdata --story=1 --- tools/goctl/api/cmd.go | 2 +- tools/goctl/internal/flags/default_en.json | 3 +- tools/goctl/testdata/tcip-mgr.api | 477 --------------------- 3 files changed, 3 insertions(+), 479 deletions(-) delete mode 100644 tools/goctl/testdata/tcip-mgr.api diff --git a/tools/goctl/api/cmd.go b/tools/goctl/api/cmd.go index 3652f0f5fc07..c16fd1729097 100644 --- a/tools/goctl/api/cmd.go +++ b/tools/goctl/api/cmd.go @@ -72,7 +72,7 @@ func init() { goCmdFlags.StringVar(&gogen.VarStringHome, "home") goCmdFlags.StringVar(&gogen.VarStringRemote, "remote") goCmdFlags.StringVar(&gogen.VarStringBranch, "branch") - goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "withtest") + goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test") goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat) javaCmdFlags.StringVar(&javagen.VarStringDir, "dir") diff --git a/tools/goctl/internal/flags/default_en.json b/tools/goctl/internal/flags/default_en.json index d26ec292c5d8..ae5b486c387b 100644 --- a/tools/goctl/internal/flags/default_en.json +++ b/tools/goctl/internal/flags/default_en.json @@ -37,7 +37,8 @@ "home": "{{.global.home}}", "remote": "{{.global.remote}}", "branch": "{{.global.branch}}", - "style": "{{.global.style}}" + "style": "{{.global.style}}", + "test": "Generate test files" }, "new": { "short": "Fast create api service", diff --git a/tools/goctl/testdata/tcip-mgr.api b/tools/goctl/testdata/tcip-mgr.api deleted file mode 100644 index f80f7ee22247..000000000000 --- a/tools/goctl/testdata/tcip-mgr.api +++ /dev/null @@ -1,477 +0,0 @@ -syntax = "v1" - -info ( - title: "tcip-mgr-service" // - desc: "tcip manage service" // - author: "" - email: "" -) - -type ( - LoginReq { - UserName string `json:"username"` - Password string `json:"password"` - } - LoginResp { - UserId int64 `json:"userId"` - UserName string `json:"userName"` - Role string `json:"role"` // 1 admin, 2 中继网关运营方, 3 子链接入方 - } - PageReq { - PageNum int `json:"pageNum,default=1"` - PageSize int `json:"pageSize,default=10"` - } - UserListReq { - UserName string `json:"userName,optional"` - PageReq - } - UserListResp { - Total int `json:"total"` - List []UserListData `json:"list"` - } - UserListData { - EnterpriseName string `json:"enterpriseName"` //公司名 - PhoneNumber string `json:"phoneNumber"` //手机号 - UserName string `json:"userName"` //用户名 - Role string `json:"role"` //角色1、admin,2、中继网关,3、跨链网关 - CreateAt int `json:"createAt"` //创建时间 - Status int `json:"status"` //1、启用2、禁用 - UserId int `json:"userId"` - } -) - -type ( - RegisterReq { - Password string `json:"password"` - EnterpriseName string `json:"enterpriseName,optional"` - PhoneNumber string `json:"phoneNumber,optional"` - UserName string `json:"userName"` - Role string `json:"role,options=[1,2,3]"` // 1 admin, 2 中继网关运营方, 3 子链接入方 - } - DisableUserReq { - UserId int `json:"userId"` - Status int `json:"status,options=[1,2]"` - } - EditUserReq { - EnterpriseName string `json:"enterpriseName"` - PhoneNumber string `json:"phoneNumber"` - UserName string `json:"userName"` - Role string `json:"role,options=[2,3]"` // 1 admin, 2 中继网关运营方, 3 子链接入方 - UserId int `json:"userId"` - } -) - -type ( - ResetPwReq { - UserId int64 `json:"userId"` - NewPwd string `json:"newPwd"` - } -) - -@server ( - prefix: /api/v1/login - group: login - timeout: 60s // 对当前 Foo 语法块下的所有路由进行超时配置,不需要则请删除此行 - maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 -) -service api { - @doc "登录" - @handler LoginHandler - post /login (LoginReq) returns (LoginResp) - - @doc "注册用户" - @handler RegisterHandler - post /register (RegisterReq) returns (bool) - - @doc "用户列表" - @handler UserListHandler - post /userList (UserListReq) returns (UserListResp) - - @doc "用户信息" - @handler UserInfoHandler - post /userInfo returns (UserListData) - - @doc "启用禁用用户" - @handler DisableUserHandler - post /disableUser (DisableUserReq) returns (bool) - - @doc "重置密码" - @handler ResetPwdHandler - post /resetPwd (ResetPwReq) returns (bool) - - @doc "注册用户" - @handler EditUserHandler - post /editUser (EditUserReq) returns (bool) - - @doc "获取当前服务的token" - @handler GetServerTokenHandler - post /serverToken returns (string) -} - -type ( - HealthResp { - Version string `json:"version"` - Name string `json:"name"` - BuildTime string `json:"buildTime"` - GitBranch string `json:"gitBranch"` - GitCommit string `json:"gitCommit"` - Now string `json:"now"` - StartTime string `json:"startTime"` - } -) - -service api { - @doc "获取服务的健康状态" - @handler HealthHandler - get /api/v1/health returns (HealthResp) -} - -type ( - RelayGatewayInfo { - RelayGatewayId string `json:"relayGatewayId"` - RelayGatewayName string `json:"relayGatewayName"` - CrossBatchContract string `json:"crossBatchContract"` - CrossContract string `json:"crossContract"` - Address string `json:"address"` - AuthCodeKey string `json:"authCodeKey"` - Timeout int `json:"timeout"` - Port int `json:"port"` - CreateAt int `json:"createAt,optional"` //创建时间 - } - NewRelayGatewayInfo { - RelayGatewayInfo - Option int `json:"option,options=[1,2]"` - } - ListRelayGatewaysReq { - RelayGatewayName string `json:"relayGatewayName,optional"` - PageReq - } - ListRelayGatewaysResp { - Total int `json:"total"` - List []RelayGatewayInfo `json:"list"` - } - ConnectRelayGatewayReq { - GatewayId string `json:"gatewayId"` //中继网关ID - } - Node { - NodeAddr string `json:"nodeAddr"` - ConnCnt string `json:"connCnt,default=10,optional"` - EnableTls bool `json:"enableTls,optional"` - TrustRoot string `json:"trustRoot,optional"` - TlsHostName string `json:"tlsHostName,optional"` - } - ChainMakerConfig { - ChainId string `json:"chainId"` - OrgId string `json:"orgId,optional"` - SignCert string `json:"signCert,optional"` - SignKey string `json:"signKey"` - TlsCert string `json:"tlsCert,optional"` - TlsKey string `json:"tlsKey,optional"` - AuthType string `json:"authType"` - HashType string `json:"hashType"` - Node []Node `json:"node"` - } - BcosConfig { - Ca string `json:"ca"` - TlsCert string `json:"tlsCert"` - TlsKey string `json:"tlsKey"` - PrivateKey string `json:"privateKey"` - GroupId string `json:"groupId"` - Address string `json:"address"` - JsonRpcAddress string `json:"jsonRpcAddress,optional"` - ChainId string `json:"chainId"` - IsSmCrypto bool `json:"isSmCrypto"` - CrossContractAbi bool `json:"crossContractAbi"` - } - Org { - OrgId string `json:"orgId"` - MspId string `json:"mspId"` - SignCert string `json:"signCert"` - SignKey string `json:"signKey"` - Peers []Node `json:"peers"` - } - FabricConfig { - ChainId string `json:"chainId"` - Orderers []Node `json:"orderers"` - Orgs []Org `json:"orgs"` - } - AntChainConfig { - RestUrl string `json:"restUrl"` - Account string `json:"account,optional"` - AccessId string `json:"accessId,optional"` - AccessSecret string `json:"accessSecret,optional"` - TtenantId string `json:"TtenantId,optional"` - BizId string `json:"bizId"` - } - AntConfig { - Ext string `json:"ext,optional"` - ChainConfig AntChainConfig `json:"chainConfig"` - } - MainChainReq { - CrossContract string `json:"crossContract,optional"` - Option int64 `json:"option,options=[1,2]"` - ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` - } - MainChainInfoReq {} - MainChainInfoResp { - CrossContract string `json:"crossContract,optional"` - ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` - } - ApplytReq { - ID int `json:"id"` - ApplyStatus int `json:"applyStatus,options=[2,3]"` - Reason string `json:"reason,optional"` - Source int `json:"source,default=1"` - } -) - -@server ( - prefix: /api/v1/relayer - group: relaygateway - timeout: 60s - maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 -) -service api { - @doc "新建中继网关" - @handler NewRelayGatewayHandler - post /newGateway (NewRelayGatewayInfo) returns (bool) - - @doc "获取中继网关列表" - @handler ListRelayGatewaysHandler - post /gatewayList (ListRelayGatewaysReq) returns (ListRelayGatewaysResp) - - @doc "连接中继网关" - @handler connGatewayHandler - post /connGateway (ConnectRelayGatewayReq) returns (bool) - - @doc "授权" - @handler ApplyHandler - post /apply (ApplytReq) returns (bool) - - @doc "主链添加" - @handler MainChainHandler - post /mainChain (MainChainReq) returns (bool) - - @doc "主链信息" - @handler MainChainInfoHandler - post /mainChainInfo (MainChainInfoReq) returns (MainChainInfoResp) -} - -type ( - CrossGatewayInfo { - CrossGatewayId string `json:"crossGatewayId,optional"` - CrossGatewayName string `json:"crossGatewayName"` - VerifyType int `json:"verifyType,options=[1,2]"` - SyncHeaderInterval int `json:"syncHeaderInterval,default=300"` - SyncHeaderCount int `json:"syncHeaderCount,default=1000"` - Address string `json:"address"` - Timeout int `json:"timeout,default=30"` - Port int `json:"port,default=8888"` - GatewayType int `json:"gatewayType,options=[1,2,3,4]"` - Status int `json:"status,optional"` - SubChainCount int `json:"subChainCount,optional"` - ApplyStatus int `json:"applyStatus,optional"` - CreateAt int `json:"createAt,optional"` - } - NewCrossGatewayReq { - CrossGatewayInfo - Option int `json:"option,options=[1,2]"` - } - ListCrossGatewaysReq { - CrossGatewayName string `json:"crossGatewayName,optional"` - PageReq - } - ListCrossGatewaysResp { - Total int `json:"total"` - List []CrossGatewayInfo `json:"list"` - } - ApplyRelayGatewayAuthReq { - RelayGatewayId string `json:"relayGatewayId"` - RelayGatewayName string `json:"relayGatewayName"` - RelayGatewayAddress string `json:"relayGatewayAddress"` - RelayGatewayToken string `json:"relayGatewayToken"` - CrossGatewayAddress string `json:"crossGatewayAddress"` - CrossGatewayOnChainAddress string `json:"crossGatewayOnChainAddress,optional"` - TxVerifyType int `json:"TxVerifyType,optional"` - ApplyMsg string `json:"applyMsg"` - CrossGatewayName string `json:"crossGatewayName"` - Source int `json:"source,default=1"` - } - ApplyRelayGatewayAuthResp { - FormId string `json:"formId"` //申请单ID - } - SubchainListReq { - ChainRid string `json:"chianRid,optional"` - GatewayId string `json:"gatewayId"` - PageReq - } - SubchainListResp { - Total int `json:"total"` - List []SubChainInfo `json:"list"` - } - ConnectCrossGatewayReq { - GatewayId string `json:"gatewayId"` //跨链网关ID - } - UpdateApplyStatusReq { - ApplyStatus int `json:"applyStatus,options=[2,3]"` - Reason string `json:"reason"` - CrossGatewayName string `json:"crossGatewayAddress"` - RelayGatewayId string `json:"relayGatewayId,optional"` - CrossGatewayId string `json:"crossGatewayId,optional"` - AuthCode string `json:"auth_code,optional"` - TLSServerName string `json:"tls_server_name,optional"` // tls server name - TLSCert string `json:"tls_cert,optional"` // tls server cert - TLSKey string `json:"tls_key,optional"` // tls server key - TLSCa string `json:"tls_ca,optional"` // tls server ca - TLSCaKey string `json:"tls_ca_key,optional"` // tls server ca key - ClientTLSCert string `json:"client_tls_cert,optional"` // 客户端tls证书 - ClientTLSKey string `json:"client_tls_key,optional"` // 客户端tls私钥 - RelayClientTLSCert string `json:"relay_client_tls_cert,optional"` // 中继网关客户端tls证书 - RelayClientTLSKey string `json:"relay_client_tls_key,optional"` // 中继网关客户端tls私钥 - RelayClientTLSCa string `json:"relay_client_tls_ca,optional"` // 中继网关ca证书 - RelayCallClientTLSCert string `json:"relay_call_client_tls_cert,optional"` // 中继网关的私钥在跨链网关ca签出的客户端tls证书 - RelayAddress string `json:"relay_address,optional"` - } - SubChainReq { - GatewayId string `json:"gatewayId"` - ChainType int `json:"chainType"` - ChainRid string `json:"chainRid"` - CrossContract string `json:"crossContract"` - Option int `json:"option,options=[1,2]"` - SpvContract string `json:"spvContract,optional"` - ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig,optional"` - BcosConfig BcosConfig `json:"bcosConfig,optional"` - FabricConfig FabricConfig `json:"fabricConfig,optional"` - AntConfig AntConfig `json:"antConfig,optional"` - } - SubChainInfo { - GatewayId string `json:"gatewayId"` - ChainType int `json:"chainType"` - ChainRid string `json:"chainRid"` - CrossContract string `json:"crossContract"` - SpvContract string `json:"spvContract"` - ChainMakerConfig ChainMakerConfig `json:"chainMakerConfig"` - BcosConfig BcosConfig `json:"bcosConfig"` - FabricConfig FabricConfig `json:"fabricConfig"` - AntConfig AntConfig `json:"antConfig"` - } -) - -@server ( - prefix: /api/v1/cross - group: crossgateway - timeout: 60s - maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 -) -service api { - @doc "新建跨链网关" - @handler NewCrossGatewayHandler - post /newGateway (NewCrossGatewayReq) returns (bool) - - @doc "更新跨链网关授权信息" - @handler UpdateApplyStatusHandler - post /updateApplyStatus (UpdateApplyStatusReq) returns (bool) - - @doc "子链添加" - @handler subChainHandler - post /subChain (SubChainReq) returns (bool) - - @doc "获取跨链网关列表" - @handler ListCrossGatewaysHandler - post /gatewayList (ListCrossGatewaysReq) returns (ListCrossGatewaysResp) - - @doc "申请中继网关的授权" - @handler ApplyRelayGatewayAuthHandler - post /apply (ApplyRelayGatewayAuthReq) returns (ApplyRelayGatewayAuthResp) - - @doc "子链列表" - @handler SubchainListHandler - post /subChainList (SubchainListReq) returns (SubchainListResp) - - @doc "连接跨链网关" - @handler ConnectCrossGatewayHandler - post /connGateway (ConnectCrossGatewayReq) returns (bool) -} - -type ( - CrossConfigInfo { - SrcChainRid string `json:"srcChainRid"` - SrcGatewayId string `json:"srcGatewayId"` - SrcContractName string `json:"srcContractName"` - SrcConfirmMethod string `json:"srcConfirmMethod,optional"` - SrcCancelMethod string `json:"srcCancelMethod,optional"` - SrcAbi string `json:"srcAbi,optional"` - DestGatewayId string `json:"destGatewayId"` - DestChainRid string `json:"destChainRid"` - DestContractName string `json:"destContractName"` - DestTryMethod string `json:"destTryMethod"` - DestConfirmMethod string `json:"destConfirmMethod,optional"` - DestCancelMethod string `json:"destCancelMethod,optional"` - DestAbi string `json:"destAbi,optional"` - CrossId string `json:"crossId,optional"` - ConfigContractName string `json:"configContractName"` - Desc string `json:"desc,optional"` - TriggerCrossType int `json:"triggerCrossType"` - ConfigContractAbi string `json:"configContractAbi,optional"` - } - NewEventReq { - CrossConfigInfo - } - EventListConfigReq { - CrossId string `json:"crossId,optional"` - GatewayId string `json:"gatewayId"` - PageReq - } - ListCrossConfigResp { - List []CrossConfigInfo `json:"list"` - Total int `json:"total"` - } - ApplyListReq { - RelayGatewayId string `json:"relayGatewayId,optional"` - CrossGatewayName string `json:"crossGatewayName,optional"` - PageReq - } - ApplyListResp { - Total int `json:"total"` - List []ApplyListItem `json:"list"` - } - ApplyListItem { - ID int `json:"id"` - ApplyTime int64 `json:"applyTime"` - ApplyMsg string `json:"applyMsg"` - CrossGatewayName string `json:"crossGatewayName"` - ApplyStatus int `json:"applyStatus"` - CrossGatewayId string `json:"crossGatewayId"` - AgreeTime int64 `json:"agreeTime"` - Reason string `json:"reason"` - RelayGatewayName string `json:"relayGatewayName"` - } - DownloadReq { - GatewayId string `json:"gatewayId"` - } -) - -@server ( - prefix: /api/v1/common - group: common - timeout: 60s - maxBytes: 5368709120 // 对当前 Foo 语法块下的所有路由添加请求体大小控制,单位为 byte,goctl 版本 >= 1.5.0 才支持 -) -service api { - @doc "新建一个跨链配置" - @handler NewEventHandler - post /newEvent (NewEventReq) returns (bool) - - @doc "获取跨链配置列表" - @handler EventListConfigHandler - post /eventList (EventListConfigReq) returns (ListCrossConfigResp) - - @doc "申请列表" - @handler ApplyListHandler - post /applyList (ApplyListReq) returns (ApplyListResp) - - @doc "下载" - @handler DownloadHandler - post /download (DownloadReq) returns ([]byte) -} -