From c3880bf46ceb4d576ddc145ebc8fec8f123d383a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:42:05 +0000 Subject: [PATCH 1/4] Initial exploration and understanding of test scaffolding requirements Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com> --- tools/goctl/api/gogen/jwt.api | 17 ++++ tools/goctl/api/gogen/workspace/a.go | 34 ++++++++ .../goctl/api/gogen/workspace/etc/a-api.yaml | 3 + tools/goctl/api/gogen/workspace/go.mod | 3 + .../gogen/workspace/internal/config/config.go | 19 +++++ .../internal/handler/greethandler.go | 31 +++++++ .../internal/handler/greethandler_test.go | 85 +++++++++++++++++++ .../workspace/internal/handler/routes.go | 29 +++++++ .../workspace/internal/logic/greetlogic.go | 33 +++++++ .../internal/logic/greetlogic_test.go | 75 ++++++++++++++++ .../middleware/tokenvalidatemiddleware.go | 22 +++++ .../workspace/internal/svc/servicecontext.go | 22 +++++ .../gogen/workspace/internal/types/types.go | 12 +++ 13 files changed, 385 insertions(+) create mode 100755 tools/goctl/api/gogen/jwt.api create mode 100644 tools/goctl/api/gogen/workspace/a.go create mode 100644 tools/goctl/api/gogen/workspace/etc/a-api.yaml create mode 100644 tools/goctl/api/gogen/workspace/go.mod create mode 100644 tools/goctl/api/gogen/workspace/internal/config/config.go create mode 100644 tools/goctl/api/gogen/workspace/internal/handler/greethandler.go create mode 100644 tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go create mode 100644 tools/goctl/api/gogen/workspace/internal/handler/routes.go create mode 100644 tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go create mode 100644 tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go create mode 100644 tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go create mode 100644 tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go create mode 100644 tools/goctl/api/gogen/workspace/internal/types/types.go diff --git a/tools/goctl/api/gogen/jwt.api b/tools/goctl/api/gogen/jwt.api new file mode 100755 index 000000000000..c95980f8d57c --- /dev/null +++ b/tools/goctl/api/gogen/jwt.api @@ -0,0 +1,17 @@ +type Request { + Name string `path:"name,options=you|me"` +} + +type Response { + Message string `json:"message"` +} + +@server( + jwt: Auth + jwtTransition: Trans + middleware: TokenValidate +) +service A-api { + @handler GreetHandler + get /greet/from/:name(Request) returns (Response) +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/workspace/a.go b/tools/goctl/api/gogen/workspace/a.go new file mode 100644 index 000000000000..2f0b26fb088d --- /dev/null +++ b/tools/goctl/api/gogen/workspace/a.go @@ -0,0 +1,34 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package main + +import ( + "flag" + "fmt" + + "workspace/internal/config" + "workspace/internal/handler" + "workspace/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +var configFile = flag.String("f", "etc/a-api.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + + server := rest.MustNewServer(c.RestConf) + defer server.Stop() + + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) + server.Start() +} diff --git a/tools/goctl/api/gogen/workspace/etc/a-api.yaml b/tools/goctl/api/gogen/workspace/etc/a-api.yaml new file mode 100644 index 000000000000..f87b9931bec0 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/etc/a-api.yaml @@ -0,0 +1,3 @@ +Name: A-api +Host: 0.0.0.0 +Port: 8888 diff --git a/tools/goctl/api/gogen/workspace/go.mod b/tools/goctl/api/gogen/workspace/go.mod new file mode 100644 index 000000000000..801fbdd569b8 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/go.mod @@ -0,0 +1,3 @@ +module workspace + +go 1.24.7 diff --git a/tools/goctl/api/gogen/workspace/internal/config/config.go b/tools/goctl/api/gogen/workspace/internal/config/config.go new file mode 100644 index 000000000000..4962bd9d5a97 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/config/config.go @@ -0,0 +1,19 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package config + +import "github.com/zeromicro/go-zero/rest" + +type Config struct { + rest.RestConf + Auth struct { + AccessSecret string + AccessExpire int64 + } + + Trans struct { + Secret string + PrevSecret string + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go b/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go new file mode 100644 index 000000000000..8587a3d91939 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go @@ -0,0 +1,31 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "workspace/internal/logic" + "workspace/internal/svc" + "workspace/internal/types" +) + +func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.Request + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewGreetLogic(r.Context(), svcCtx) + resp, err := l.Greet(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go b/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go new file mode 100644 index 000000000000..034a96a5dfff --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go @@ -0,0 +1,85 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package handler + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "workspace/internal/config" + "workspace/internal/svc" + "workspace/internal/types" +) + +func TestGreetHandler(t *testing.T) { + // new service context + c := config.Config{} + svcCtx := svc.NewServiceContext(c) + // init mock service context here + + tests := []struct { + name string + reqBody interface{} + wantStatus int + wantResp string + setupMocks func() + }{ + { + name: "invalid request body", + reqBody: "invalid", + wantStatus: http.StatusBadRequest, + wantResp: "unsupported type", // Adjust based on actual error response + setupMocks: func() { + // No setup needed for this test case + }, + }, + { + name: "handler error", + reqBody: types.Request{ + //TODO: add fields here + }, + wantStatus: http.StatusBadRequest, + wantResp: "error", // Adjust based on actual error response + setupMocks: func() { + // Mock login logic to return an error + }, + }, + { + name: "handler successful", + reqBody: types.Request{ + //TODO: add fields here + }, + 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() + var reqBody []byte + var err error + reqBody, err = json.Marshal(tt.reqBody) + require.NoError(t, err) + req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler := GreetHandler(svcCtx) + handler.ServeHTTP(rr, req) + t.Log(rr.Body.String()) + assert.Equal(t, tt.wantStatus, rr.Code) + assert.Contains(t, rr.Body.String(), tt.wantResp) + }) + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/routes.go b/tools/goctl/api/gogen/workspace/internal/handler/routes.go new file mode 100644 index 000000000000..e0237575f9c0 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/handler/routes.go @@ -0,0 +1,29 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.1-alpha + +package handler + +import ( + "net/http" + + "workspace/internal/svc" + + "github.com/zeromicro/go-zero/rest" +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.TokenValidate}, + []rest.Route{ + { + Method: http.MethodGet, + Path: "/greet/from/:name", + Handler: GreetHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.Auth.AccessSecret), + rest.WithJwtTransition(serverCtx.Config.Trans.PrevSecret, serverCtx.Config.Trans.Secret), + ) +} diff --git a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go new file mode 100644 index 000000000000..63c6df181c05 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go @@ -0,0 +1,33 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package logic + +import ( + "context" + + "workspace/internal/svc" + "workspace/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GreetLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGreetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GreetLogic { + return &GreetLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GreetLogic) Greet(req *types.Request) (resp *types.Response, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go new file mode 100644 index 000000000000..d7e603291429 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go @@ -0,0 +1,75 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package logic + +import ( + "context" + "testing" + + "workspace/internal/config" + "workspace/internal/svc" + "workspace/internal/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGreetLogic_Greet(t *testing.T) { + c := config.Config{} + mockSvcCtx := svc.NewServiceContext(c) + // init mock service context here + + tests := []struct { + name string + ctx context.Context + setupMocks func() + req *types.Request + wantErr bool + checkResp func(resp *types.Response, err error) + }{ + { + name: "response error", + ctx: context.Background(), + setupMocks: func() { + // mock data for this test case + }, + req: &types.Request{ + // TODO: init your request here + }, + wantErr: true, + checkResp: func(resp *types.Response, err error) { + // TODO: Add your check logic here + }, + }, + { + name: "successful", + ctx: context.Background(), + setupMocks: func() { + // Mock data for this test case + }, + req: &types.Request{ + // TODO: init your request here + }, + wantErr: false, + checkResp: func(resp *types.Response, err error) { + // TODO: Add your check logic here + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMocks() + l := NewGreetLogic(tt.ctx, mockSvcCtx) + resp, err := l.Greet(tt.req) + if tt.wantErr { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.NotNil(t, resp) + } + tt.checkResp(resp, err) + }) + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go b/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go new file mode 100644 index 000000000000..7e512c332d94 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go @@ -0,0 +1,22 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package middleware + +import "net/http" + +type TokenValidateMiddleware struct { +} + +func NewTokenValidateMiddleware() *TokenValidateMiddleware { + return &TokenValidateMiddleware{} +} + +func (m *TokenValidateMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO generate middleware implement function, delete after code implementation + + // Passthrough to next handler if need + next(w, r) + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go b/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go new file mode 100644 index 000000000000..8e70dae92fa4 --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go @@ -0,0 +1,22 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.1-alpha + +package svc + +import ( + "github.com/zeromicro/go-zero/rest" + "workspace/internal/config" + "workspace/internal/middleware" +) + +type ServiceContext struct { + Config config.Config + TokenValidate rest.Middleware +} + +func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + Config: c, + TokenValidate: middleware.NewTokenValidateMiddleware().Handle, + } +} diff --git a/tools/goctl/api/gogen/workspace/internal/types/types.go b/tools/goctl/api/gogen/workspace/internal/types/types.go new file mode 100644 index 000000000000..1ddd477be58f --- /dev/null +++ b/tools/goctl/api/gogen/workspace/internal/types/types.go @@ -0,0 +1,12 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.1-alpha + +package types + +type Request struct { + Name string `path:"name,options=you|me"` +} + +type Response struct { + Message string `json:"message"` +} From 056c5df8b35bab0285711190e652a8168a83cce3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:47:04 +0000 Subject: [PATCH 2/4] Implement complete test scaffolding with --with-tests flag Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com> --- tools/goctl/api/cmd.go | 2 +- tools/goctl/api/gogen/gen.go | 2 + tools/goctl/api/gogen/genintegrationtest.go | 42 ++++++ tools/goctl/api/gogen/gensvctest.go | 34 +++++ tools/goctl/api/gogen/integration_test.tpl | 120 ++++++++++++++++++ tools/goctl/api/gogen/svc_test.tpl | 60 +++++++++ tools/goctl/api/gogen/template.go | 4 + tools/goctl/api/gogen/workspace/a.go | 34 ----- .../goctl/api/gogen/workspace/etc/a-api.yaml | 3 - tools/goctl/api/gogen/workspace/go.mod | 3 - .../gogen/workspace/internal/config/config.go | 19 --- .../internal/handler/greethandler.go | 31 ----- .../internal/handler/greethandler_test.go | 85 ------------- .../workspace/internal/handler/routes.go | 29 ----- .../workspace/internal/logic/greetlogic.go | 33 ----- .../internal/logic/greetlogic_test.go | 75 ----------- .../middleware/tokenvalidatemiddleware.go | 22 ---- .../workspace/internal/svc/servicecontext.go | 22 ---- .../gogen/workspace/internal/types/types.go | 12 -- 19 files changed, 263 insertions(+), 369 deletions(-) create mode 100644 tools/goctl/api/gogen/genintegrationtest.go create mode 100644 tools/goctl/api/gogen/gensvctest.go create mode 100644 tools/goctl/api/gogen/integration_test.tpl create mode 100644 tools/goctl/api/gogen/svc_test.tpl delete mode 100644 tools/goctl/api/gogen/workspace/a.go delete mode 100644 tools/goctl/api/gogen/workspace/etc/a-api.yaml delete mode 100644 tools/goctl/api/gogen/workspace/go.mod delete mode 100644 tools/goctl/api/gogen/workspace/internal/config/config.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/handler/greethandler.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/handler/routes.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go delete mode 100644 tools/goctl/api/gogen/workspace/internal/types/types.go diff --git a/tools/goctl/api/cmd.go b/tools/goctl/api/cmd.go index d300260cda8f..033f0c0225f6 100644 --- a/tools/goctl/api/cmd.go +++ b/tools/goctl/api/cmd.go @@ -76,7 +76,7 @@ func init() { goCmdFlags.StringVar(&gogen.VarStringHome, "home") goCmdFlags.StringVar(&gogen.VarStringRemote, "remote") goCmdFlags.StringVar(&gogen.VarStringBranch, "branch") - goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test") + goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "with-tests") goCmdFlags.BoolVar(&gogen.VarBoolTypeGroup, "type-group") goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat) diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go index f8fec6a17c56..b3d12642a3d3 100644 --- a/tools/goctl/api/gogen/gen.go +++ b/tools/goctl/api/gogen/gen.go @@ -118,6 +118,8 @@ func DoGenProjectWithModule(apiFile, dir, moduleName, style string, withTest boo if withTest { logx.Must(genHandlersTest(dir, rootPkg, projectPkg, cfg, api)) logx.Must(genLogicTest(dir, rootPkg, projectPkg, cfg, api)) + logx.Must(genServiceContextTest(dir, rootPkg, projectPkg, cfg, api)) + logx.Must(genIntegrationTest(dir, rootPkg, projectPkg, cfg, api)) } if err := backupAndSweep(apiFile); err != nil { diff --git a/tools/goctl/api/gogen/genintegrationtest.go b/tools/goctl/api/gogen/genintegrationtest.go new file mode 100644 index 000000000000..942e71c550e2 --- /dev/null +++ b/tools/goctl/api/gogen/genintegrationtest.go @@ -0,0 +1,42 @@ +package gogen + +import ( + _ "embed" + + "github.com/zeromicro/go-zero/tools/goctl/api/spec" + "github.com/zeromicro/go-zero/tools/goctl/config" + "github.com/zeromicro/go-zero/tools/goctl/internal/version" + "github.com/zeromicro/go-zero/tools/goctl/util/format" +) + +//go:embed integration_test.tpl +var integrationTestTemplate string + +func genIntegrationTest(dir, rootPkg, projectPkg string, cfg *config.Config, api *spec.ApiSpec) error { + serviceName := api.Service.Name + if len(serviceName) == 0 { + serviceName = "server" + } + + filename, err := format.FileNamingFormat(cfg.NamingFormat, serviceName) + if err != nil { + return err + } + + return genFile(fileGenConfig{ + dir: dir, + subdir: "", + filename: filename + "_test.go", + templateName: "integrationTestTemplate", + category: category, + templateFile: integrationTestTemplateFile, + builtinTemplate: integrationTestTemplate, + data: map[string]any{ + "projectPkg": projectPkg, + "serviceName": serviceName, + "version": version.BuildVersion, + "hasRoutes": len(api.Service.Routes()) > 0, + "routes": api.Service.Routes(), + }, + }) +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/gensvctest.go b/tools/goctl/api/gogen/gensvctest.go new file mode 100644 index 000000000000..e61be4a27086 --- /dev/null +++ b/tools/goctl/api/gogen/gensvctest.go @@ -0,0 +1,34 @@ +package gogen + +import ( + _ "embed" + + "github.com/zeromicro/go-zero/tools/goctl/api/spec" + "github.com/zeromicro/go-zero/tools/goctl/config" + "github.com/zeromicro/go-zero/tools/goctl/internal/version" + "github.com/zeromicro/go-zero/tools/goctl/util/format" +) + +//go:embed svc_test.tpl +var svcTestTemplate string + +func genServiceContextTest(dir, rootPkg, projectPkg string, cfg *config.Config, api *spec.ApiSpec) error { + filename, err := format.FileNamingFormat(cfg.NamingFormat, contextFilename) + if err != nil { + return err + } + + return genFile(fileGenConfig{ + dir: dir, + subdir: contextDir, + filename: filename + "_test.go", + templateName: "svcTestTemplate", + category: category, + templateFile: svcTestTemplateFile, + builtinTemplate: svcTestTemplate, + data: map[string]any{ + "projectPkg": projectPkg, + "version": version.BuildVersion, + }, + }) +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/integration_test.tpl b/tools/goctl/api/gogen/integration_test.tpl new file mode 100644 index 000000000000..6fb9bc09dd99 --- /dev/null +++ b/tools/goctl/api/gogen/integration_test.tpl @@ -0,0 +1,120 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl {{.version}} + +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "{{.projectPkg}}/internal/config" + "{{.projectPkg}}/internal/handler" + "{{.projectPkg}}/internal/svc" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zeromicro/go-zero/rest" +) + +func TestMain(m *testing.M) { + // TODO: Add setup/teardown logic here if needed + m.Run() +} + +func TestServerIntegration(t *testing.T) { + // Create test server + c := config.Config{ + RestConf: rest.RestConf{ + Host: "127.0.0.1", + Port: 0, // Use random available port + }, + } + + server := rest.MustNewServer(c.RestConf) + defer server.Stop() + + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + // Start server in background + go func() { + server.Start() + }() + + // Wait for server to start + time.Sleep(100 * time.Millisecond) + + tests := []struct { + name string + method string + path string + body string + expectedStatus int + setup func() + }{ + { + name: "health check", + method: "GET", + path: "/health", + expectedStatus: http.StatusNotFound, // Adjust based on actual routes + setup: func() {}, + }, + {{if .hasRoutes}}{{range .routes}}{ + name: "{{.Method}} {{.Path}}", + method: "{{.Method}}", + path: "{{.Path}}", + expectedStatus: http.StatusOK, // TODO: Adjust expected status + setup: func() { + // TODO: Add setup logic for this endpoint + }, + }, + {{end}}{{end}}{ + name: "not found route", + method: "GET", + path: "/nonexistent", + expectedStatus: http.StatusNotFound, + setup: func() {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + + req, err := http.NewRequest(tt.method, tt.path, nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + + assert.Equal(t, tt.expectedStatus, rr.Code) + + // TODO: Add response body assertions + t.Logf("Response: %s", rr.Body.String()) + }) + } +} + +func TestServerLifecycle(t *testing.T) { + c := config.Config{ + RestConf: rest.RestConf{ + Host: "127.0.0.1", + Port: 0, + }, + } + + server := rest.MustNewServer(c.RestConf) + + // Test server can start and stop without errors + ctx := svc.NewServiceContext(c) + handler.RegisterHandlers(server, ctx) + + // In a real integration test, you might start the server in a goroutine + // and test actual HTTP requests, but for scaffolding we keep it simple + server.Stop() + + // TODO: Add more lifecycle tests as needed + assert.True(t, true, "Server lifecycle test passed") +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/svc_test.tpl b/tools/goctl/api/gogen/svc_test.tpl new file mode 100644 index 000000000000..4d4ee3aa4aa0 --- /dev/null +++ b/tools/goctl/api/gogen/svc_test.tpl @@ -0,0 +1,60 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl {{.version}} + +package svc + +import ( + "testing" + + "{{.projectPkg}}/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewServiceContext(t *testing.T) { + tests := []struct { + name string + config config.Config + setup func() config.Config + }{ + { + name: "default config", + setup: func() config.Config { + return config.Config{} + }, + }, + { + name: "valid config", + setup: func() config.Config { + return config.Config{ + // TODO: Add valid config values here + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := tt.setup() + svcCtx := NewServiceContext(c) + + // Basic assertions + require.NotNil(t, svcCtx) + assert.Equal(t, c, svcCtx.Config) + + // TODO: Add additional assertions for middleware and dependencies + }) + } +} + +func TestServiceContext_Initialization(t *testing.T) { + c := config.Config{} + svcCtx := NewServiceContext(c) + + // Verify service context is properly initialized + assert.NotNil(t, svcCtx) + assert.Equal(t, c, svcCtx.Config) + + // TODO: Add tests for middleware initialization if any + // TODO: Add tests for external dependencies if any +} \ No newline at end of file diff --git a/tools/goctl/api/gogen/template.go b/tools/goctl/api/gogen/template.go index a00f3741cadd..27adc711b1b0 100644 --- a/tools/goctl/api/gogen/template.go +++ b/tools/goctl/api/gogen/template.go @@ -22,6 +22,8 @@ const ( routesTemplateFile = "routes.tpl" routesAdditionTemplateFile = "route-addition.tpl" typesTemplateFile = "types.tpl" + svcTestTemplateFile = "svc_test.tpl" + integrationTestTemplateFile = "integration_test.tpl" ) var templates = map[string]string{ @@ -39,6 +41,8 @@ var templates = map[string]string{ routesTemplateFile: routesTemplate, routesAdditionTemplateFile: routesAdditionTemplate, typesTemplateFile: typesTemplate, + svcTestTemplateFile: svcTestTemplate, + integrationTestTemplateFile: integrationTestTemplate, } // Category returns the category of the api files. diff --git a/tools/goctl/api/gogen/workspace/a.go b/tools/goctl/api/gogen/workspace/a.go deleted file mode 100644 index 2f0b26fb088d..000000000000 --- a/tools/goctl/api/gogen/workspace/a.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package main - -import ( - "flag" - "fmt" - - "workspace/internal/config" - "workspace/internal/handler" - "workspace/internal/svc" - - "github.com/zeromicro/go-zero/core/conf" - "github.com/zeromicro/go-zero/rest" -) - -var configFile = flag.String("f", "etc/a-api.yaml", "the config file") - -func main() { - flag.Parse() - - var c config.Config - conf.MustLoad(*configFile, &c) - - server := rest.MustNewServer(c.RestConf) - defer server.Stop() - - ctx := svc.NewServiceContext(c) - handler.RegisterHandlers(server, ctx) - - fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) - server.Start() -} diff --git a/tools/goctl/api/gogen/workspace/etc/a-api.yaml b/tools/goctl/api/gogen/workspace/etc/a-api.yaml deleted file mode 100644 index f87b9931bec0..000000000000 --- a/tools/goctl/api/gogen/workspace/etc/a-api.yaml +++ /dev/null @@ -1,3 +0,0 @@ -Name: A-api -Host: 0.0.0.0 -Port: 8888 diff --git a/tools/goctl/api/gogen/workspace/go.mod b/tools/goctl/api/gogen/workspace/go.mod deleted file mode 100644 index 801fbdd569b8..000000000000 --- a/tools/goctl/api/gogen/workspace/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module workspace - -go 1.24.7 diff --git a/tools/goctl/api/gogen/workspace/internal/config/config.go b/tools/goctl/api/gogen/workspace/internal/config/config.go deleted file mode 100644 index 4962bd9d5a97..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/config/config.go +++ /dev/null @@ -1,19 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package config - -import "github.com/zeromicro/go-zero/rest" - -type Config struct { - rest.RestConf - Auth struct { - AccessSecret string - AccessExpire int64 - } - - Trans struct { - Secret string - PrevSecret string - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go b/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go deleted file mode 100644 index 8587a3d91939..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/handler/greethandler.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package handler - -import ( - "net/http" - - "github.com/zeromicro/go-zero/rest/httpx" - "workspace/internal/logic" - "workspace/internal/svc" - "workspace/internal/types" -) - -func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req types.Request - if err := httpx.Parse(r, &req); err != nil { - httpx.ErrorCtx(r.Context(), w, err) - return - } - - l := logic.NewGreetLogic(r.Context(), svcCtx) - resp, err := l.Greet(&req) - if err != nil { - httpx.ErrorCtx(r.Context(), w, err) - } else { - httpx.OkJsonCtx(r.Context(), w, resp) - } - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go b/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go deleted file mode 100644 index 034a96a5dfff..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/handler/greethandler_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package handler - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "workspace/internal/config" - "workspace/internal/svc" - "workspace/internal/types" -) - -func TestGreetHandler(t *testing.T) { - // new service context - c := config.Config{} - svcCtx := svc.NewServiceContext(c) - // init mock service context here - - tests := []struct { - name string - reqBody interface{} - wantStatus int - wantResp string - setupMocks func() - }{ - { - name: "invalid request body", - reqBody: "invalid", - wantStatus: http.StatusBadRequest, - wantResp: "unsupported type", // Adjust based on actual error response - setupMocks: func() { - // No setup needed for this test case - }, - }, - { - name: "handler error", - reqBody: types.Request{ - //TODO: add fields here - }, - wantStatus: http.StatusBadRequest, - wantResp: "error", // Adjust based on actual error response - setupMocks: func() { - // Mock login logic to return an error - }, - }, - { - name: "handler successful", - reqBody: types.Request{ - //TODO: add fields here - }, - 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() - var reqBody []byte - var err error - reqBody, err = json.Marshal(tt.reqBody) - require.NoError(t, err) - req, err := http.NewRequest("POST", "/ut", bytes.NewBuffer(reqBody)) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := GreetHandler(svcCtx) - handler.ServeHTTP(rr, req) - t.Log(rr.Body.String()) - assert.Equal(t, tt.wantStatus, rr.Code) - assert.Contains(t, rr.Body.String(), tt.wantResp) - }) - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/handler/routes.go b/tools/goctl/api/gogen/workspace/internal/handler/routes.go deleted file mode 100644 index e0237575f9c0..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/handler/routes.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by goctl. DO NOT EDIT. -// goctl 1.9.1-alpha - -package handler - -import ( - "net/http" - - "workspace/internal/svc" - - "github.com/zeromicro/go-zero/rest" -) - -func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { - server.AddRoutes( - rest.WithMiddlewares( - []rest.Middleware{serverCtx.TokenValidate}, - []rest.Route{ - { - Method: http.MethodGet, - Path: "/greet/from/:name", - Handler: GreetHandler(serverCtx), - }, - }..., - ), - rest.WithJwt(serverCtx.Config.Auth.AccessSecret), - rest.WithJwtTransition(serverCtx.Config.Trans.PrevSecret, serverCtx.Config.Trans.Secret), - ) -} diff --git a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go deleted file mode 100644 index 63c6df181c05..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic.go +++ /dev/null @@ -1,33 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package logic - -import ( - "context" - - "workspace/internal/svc" - "workspace/internal/types" - - "github.com/zeromicro/go-zero/core/logx" -) - -type GreetLogic struct { - logx.Logger - ctx context.Context - svcCtx *svc.ServiceContext -} - -func NewGreetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GreetLogic { - return &GreetLogic{ - Logger: logx.WithContext(ctx), - ctx: ctx, - svcCtx: svcCtx, - } -} - -func (l *GreetLogic) Greet(req *types.Request) (resp *types.Response, err error) { - // todo: add your logic here and delete this line - - return -} diff --git a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go b/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go deleted file mode 100644 index d7e603291429..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/logic/greetlogic_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package logic - -import ( - "context" - "testing" - - "workspace/internal/config" - "workspace/internal/svc" - "workspace/internal/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGreetLogic_Greet(t *testing.T) { - c := config.Config{} - mockSvcCtx := svc.NewServiceContext(c) - // init mock service context here - - tests := []struct { - name string - ctx context.Context - setupMocks func() - req *types.Request - wantErr bool - checkResp func(resp *types.Response, err error) - }{ - { - name: "response error", - ctx: context.Background(), - setupMocks: func() { - // mock data for this test case - }, - req: &types.Request{ - // TODO: init your request here - }, - wantErr: true, - checkResp: func(resp *types.Response, err error) { - // TODO: Add your check logic here - }, - }, - { - name: "successful", - ctx: context.Background(), - setupMocks: func() { - // Mock data for this test case - }, - req: &types.Request{ - // TODO: init your request here - }, - wantErr: false, - checkResp: func(resp *types.Response, err error) { - // TODO: Add your check logic here - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMocks() - l := NewGreetLogic(tt.ctx, mockSvcCtx) - resp, err := l.Greet(tt.req) - if tt.wantErr { - assert.Error(t, err) - } else { - require.NoError(t, err) - assert.NotNil(t, resp) - } - tt.checkResp(resp, err) - }) - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go b/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go deleted file mode 100644 index 7e512c332d94..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/middleware/tokenvalidatemiddleware.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package middleware - -import "net/http" - -type TokenValidateMiddleware struct { -} - -func NewTokenValidateMiddleware() *TokenValidateMiddleware { - return &TokenValidateMiddleware{} -} - -func (m *TokenValidateMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // TODO generate middleware implement function, delete after code implementation - - // Passthrough to next handler if need - next(w, r) - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go b/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go deleted file mode 100644 index 8e70dae92fa4..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/svc/servicecontext.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code scaffolded by goctl. Safe to edit. -// goctl 1.9.1-alpha - -package svc - -import ( - "github.com/zeromicro/go-zero/rest" - "workspace/internal/config" - "workspace/internal/middleware" -) - -type ServiceContext struct { - Config config.Config - TokenValidate rest.Middleware -} - -func NewServiceContext(c config.Config) *ServiceContext { - return &ServiceContext{ - Config: c, - TokenValidate: middleware.NewTokenValidateMiddleware().Handle, - } -} diff --git a/tools/goctl/api/gogen/workspace/internal/types/types.go b/tools/goctl/api/gogen/workspace/internal/types/types.go deleted file mode 100644 index 1ddd477be58f..000000000000 --- a/tools/goctl/api/gogen/workspace/internal/types/types.go +++ /dev/null @@ -1,12 +0,0 @@ -// Code generated by goctl. DO NOT EDIT. -// goctl 1.9.1-alpha - -package types - -type Request struct { - Name string `path:"name,options=you|me"` -} - -type Response struct { - Message string `json:"message"` -} From d5c421015f076d2b67ae9a23467f9e295e912067 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:01:25 +0000 Subject: [PATCH 3/4] Fix PR review feedback: revert flag to --test for backward compatibility and add trailing newlines Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com> --- tools/goctl/api/cmd.go | 2 +- tools/goctl/api/gogen/genintegrationtest.go | 2 +- tools/goctl/api/gogen/gensvctest.go | 2 +- tools/goctl/api/gogen/integration_test.tpl | 2 +- tools/goctl/api/gogen/svc_test.tpl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/goctl/api/cmd.go b/tools/goctl/api/cmd.go index 033f0c0225f6..d300260cda8f 100644 --- a/tools/goctl/api/cmd.go +++ b/tools/goctl/api/cmd.go @@ -76,7 +76,7 @@ func init() { goCmdFlags.StringVar(&gogen.VarStringHome, "home") goCmdFlags.StringVar(&gogen.VarStringRemote, "remote") goCmdFlags.StringVar(&gogen.VarStringBranch, "branch") - goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "with-tests") + goCmdFlags.BoolVar(&gogen.VarBoolWithTest, "test") goCmdFlags.BoolVar(&gogen.VarBoolTypeGroup, "type-group") goCmdFlags.StringVarWithDefaultValue(&gogen.VarStringStyle, "style", config.DefaultFormat) diff --git a/tools/goctl/api/gogen/genintegrationtest.go b/tools/goctl/api/gogen/genintegrationtest.go index 942e71c550e2..48c920ca101e 100644 --- a/tools/goctl/api/gogen/genintegrationtest.go +++ b/tools/goctl/api/gogen/genintegrationtest.go @@ -39,4 +39,4 @@ func genIntegrationTest(dir, rootPkg, projectPkg string, cfg *config.Config, api "routes": api.Service.Routes(), }, }) -} \ No newline at end of file +} diff --git a/tools/goctl/api/gogen/gensvctest.go b/tools/goctl/api/gogen/gensvctest.go index e61be4a27086..3699ecf3ee2d 100644 --- a/tools/goctl/api/gogen/gensvctest.go +++ b/tools/goctl/api/gogen/gensvctest.go @@ -31,4 +31,4 @@ func genServiceContextTest(dir, rootPkg, projectPkg string, cfg *config.Config, "version": version.BuildVersion, }, }) -} \ No newline at end of file +} diff --git a/tools/goctl/api/gogen/integration_test.tpl b/tools/goctl/api/gogen/integration_test.tpl index 6fb9bc09dd99..e64c2f3e2c49 100644 --- a/tools/goctl/api/gogen/integration_test.tpl +++ b/tools/goctl/api/gogen/integration_test.tpl @@ -117,4 +117,4 @@ func TestServerLifecycle(t *testing.T) { // TODO: Add more lifecycle tests as needed assert.True(t, true, "Server lifecycle test passed") -} \ No newline at end of file +} diff --git a/tools/goctl/api/gogen/svc_test.tpl b/tools/goctl/api/gogen/svc_test.tpl index 4d4ee3aa4aa0..10009d9a4900 100644 --- a/tools/goctl/api/gogen/svc_test.tpl +++ b/tools/goctl/api/gogen/svc_test.tpl @@ -57,4 +57,4 @@ func TestServiceContext_Initialization(t *testing.T) { // TODO: Add tests for middleware initialization if any // TODO: Add tests for external dependencies if any -} \ No newline at end of file +} From ea7561a34263b474381dcd576eacf40c515e0232 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:06:22 +0000 Subject: [PATCH 4/4] Fix trailing newline for jwt.api file Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com> --- tools/goctl/api/gogen/jwt.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/goctl/api/gogen/jwt.api b/tools/goctl/api/gogen/jwt.api index c95980f8d57c..a8763c0d4293 100755 --- a/tools/goctl/api/gogen/jwt.api +++ b/tools/goctl/api/gogen/jwt.api @@ -14,4 +14,4 @@ type Response { service A-api { @handler GreetHandler get /greet/from/:name(Request) returns (Response) -} \ No newline at end of file +}