diff --git a/Dockerfile b/Dockerfile index 584c3616..29608b50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY console/atest-ui . RUN npm install --ignore-scripts --registry=https://registry.npmmirror.com RUN npm run build-only -FROM docker.io/golang:1.22.4 AS builder +FROM docker.io/golang:1.23 AS builder ARG VERSION ARG GOPROXY diff --git a/cmd/convert_test.go b/cmd/convert_test.go index 0f047bfd..b5ed96eb 100644 --- a/cmd/convert_test.go +++ b/cmd/convert_test.go @@ -32,7 +32,7 @@ import ( ) func TestConvert(t *testing.T) { - c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, + c := cmd.NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) c.SetOut(io.Discard) diff --git a/cmd/function_test.go b/cmd/function_test.go index 8365ba7e..05a72b41 100644 --- a/cmd/function_test.go +++ b/cmd/function_test.go @@ -64,7 +64,7 @@ func TestCreateFunctionCommand(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, + c := cmd.NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) buf := new(bytes.Buffer) diff --git a/cmd/jsonschema_test.go b/cmd/jsonschema_test.go index 587c0e22..a8581f5e 100644 --- a/cmd/jsonschema_test.go +++ b/cmd/jsonschema_test.go @@ -28,7 +28,7 @@ import ( ) func TestJSONSchemaCmd(t *testing.T) { - c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, + c := cmd.NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) buf := new(bytes.Buffer) diff --git a/cmd/mock-compose.go b/cmd/mock-compose.go index 91eb85b1..8dbfbd8c 100644 --- a/cmd/mock-compose.go +++ b/cmd/mock-compose.go @@ -17,52 +17,49 @@ limitations under the License. package cmd import ( - "os" - "os/signal" - "syscall" - - "github.com/linuxsuren/api-testing/pkg/mock" - "github.com/spf13/cobra" + "github.com/linuxsuren/api-testing/pkg/mock" + "github.com/spf13/cobra" + "os" + "os/signal" + "syscall" ) func createMockComposeCmd() (c *cobra.Command) { - c = &cobra.Command{ - Use: "mock-compose", - Short: "Mock multiple servers", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) (err error) { - reader := mock.NewLocalFileReader(args[0]) + c = &cobra.Command{ + Use: "mock-compose", + Short: "Mock multiple servers", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + reader := mock.NewLocalFileReader(args[0]) - var server *mock.Server - if server, err = reader.Parse(); err != nil { - return - } + var server *mock.Server + if server, err = reader.Parse(); err != nil { + return + } - var subServers []mock.DynamicServer - for _, proxy := range server.Proxies { - subProxy := &mock.Server{ - Proxies: []mock.Proxy{proxy}, - } + var subServers []mock.DynamicServer + for _, proxy := range server.Proxies { + subProxy := &mock.Server{ + Proxies: []mock.Proxy{proxy}, + } - subReader := mock.NewObjectReader(subProxy) - subServer := mock.NewInMemoryServer(c.Context(), proxy.Port) - if err = subServer.Start(subReader, proxy.Prefix); err != nil { - return - } - subServers = append(subServers, subServer) - } + subReader := mock.NewObjectReader(subProxy) + subServer := mock.NewInMemoryServer(c.Context(), proxy.Port) + go subServer.Start(subReader, proxy.Prefix) + subServers = append(subServers, subServer) + } - clean := make(chan os.Signal, 1) - signal.Notify(clean, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) - select { - case <-c.Context().Done(): - case <-clean: - } - for _, server := range subServers { - server.Stop() - } - return - }, - } - return + clean := make(chan os.Signal, 1) + signal.Notify(clean, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) + select { + case <-c.Context().Done(): + case <-clean: + } + for _, server := range subServers { + server.Stop() + } + return + }, + } + return } diff --git a/cmd/mock_test.go b/cmd/mock_test.go index 24a8f574..dc72021f 100644 --- a/cmd/mock_test.go +++ b/cmd/mock_test.go @@ -49,7 +49,7 @@ func TestMockCommand(t *testing.T) { } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - root := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) + root := NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) root.SetOut(io.Discard) root.SetArgs(tc.args) ctx, cancel := context.WithCancel(context.TODO()) diff --git a/cmd/root_test.go b/cmd/root_test.go index d808a553..c5543909 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -26,7 +26,7 @@ import ( ) func TestCreateRunCommand(t *testing.T) { - execer := fakeruntime.FakeExecer{} + execer := &fakeruntime.FakeExecer{} cmd := createRunCommand() assert.Equal(t, "run", cmd.Use) @@ -45,7 +45,7 @@ func TestCreateRunCommand(t *testing.T) { } func TestRootCmd(t *testing.T) { - c := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) + c := NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) assert.NotNil(t, c) assert.Equal(t, "atest", c.Use) } diff --git a/cmd/sample_test.go b/cmd/sample_test.go index 437069e9..a70ba3e1 100644 --- a/cmd/sample_test.go +++ b/cmd/sample_test.go @@ -28,7 +28,7 @@ import ( ) func TestSampleCmd(t *testing.T) { - c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, + c := cmd.NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) buf := new(bytes.Buffer) diff --git a/cmd/server_test.go b/cmd/server_test.go index b64ba736..82acca34 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -80,7 +80,7 @@ func TestPrintProto(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf := new(bytes.Buffer) - root := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) + root := NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) root.SetOut(buf) root.SetArgs(append(tt.args, "--dry-run")) err := root.Execute() @@ -182,7 +182,7 @@ func TestFrontEndHandlerWithLocation(t *testing.T) { t.Run("download atest", func(t *testing.T) { opt := &serverOption{ - execer: fakeruntime.FakeExecer{ + execer: &fakeruntime.FakeExecer{ ExpectOS: "linux", ExpectLookPathError: errors.New("fake"), }, @@ -199,7 +199,7 @@ func TestFrontEndHandlerWithLocation(t *testing.T) { t.Run("download atest, failed to read", func(t *testing.T) { opt := &serverOption{ - execer: fakeruntime.FakeExecer{ + execer: &fakeruntime.FakeExecer{ ExpectOS: "linux", }, } @@ -268,7 +268,7 @@ func TestOAuth(t *testing.T) { }} for i, tt := range tests { buf := new(bytes.Buffer) - root := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) + root := NewRootCmd(&fakeruntime.FakeExecer{ExpectOS: "linux"}, server.NewFakeHTTPServer()) root.SetOut(buf) root.SetArgs(append(tt.args, "--dry-run")) err := root.Execute() @@ -294,7 +294,7 @@ func TestStartPlugins(t *testing.T) { } rootCmd.SetOut(io.Discard) rootCmd.AddCommand(createServerCmd( - fakeruntime.FakeExecer{ExpectOS: "linux", ExpectLookPathError: errors.New("not-found")}, + &fakeruntime.FakeExecer{ExpectOS: "linux", ExpectLookPathError: errors.New("not-found")}, server.NewFakeHTTPServer(), )) @@ -310,7 +310,7 @@ func TestStartPlugins(t *testing.T) { } rootCmd.SetOut(io.Discard) rootCmd.AddCommand(createServerCmd( - fakeruntime.FakeExecer{ExpectOS: "linux", ExpectLookPathError: errors.New("not-found")}, + &fakeruntime.FakeExecer{ExpectOS: "linux", ExpectLookPathError: errors.New("not-found")}, httpServer, )) diff --git a/docs/site/content/zh/latest/tasks/mock.md b/docs/site/content/zh/latest/tasks/mock.md index f43d57a5..007b00d7 100644 --- a/docs/site/content/zh/latest/tasks/mock.md +++ b/docs/site/content/zh/latest/tasks/mock.md @@ -38,7 +38,7 @@ docker pull localhost:6060/repo/name:tag * 面向对象的 CRUD * 自定义 HTTP 服务 -### 面对对象 +### 面向对象 ```yaml #!api-testing-mock @@ -160,6 +160,16 @@ proxies: target: http://192.168.123.58:9200 ``` +## TCP 协议代理 + +```yaml +proxies: + - protocol: tcp + port: 3306 + path: / + target: 192.168.123.58:33060 +``` + ## 代理多个服务 ```shell @@ -178,6 +188,12 @@ proxies: port: 17001 path: /{path:.*} target: http://192.168.123.58:17001 + - protocol: tcp + port: 33060 + path: / + target: 192.168.123.58:33060 ``` +当前代理支持 HTTP 和 TCP 协议,上面的例子中代理了 MySQL 的 `33060` 端口。 + > 更多 URL 中通配符的用法,请参考 https://github.com/gorilla/mux diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index 8b68163e..235b10e5 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -16,582 +16,632 @@ limitations under the License. package mock import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net" - "net/http" - "strings" - "sync" - "time" - - jsonpatch "github.com/evanphx/json-patch" - "github.com/swaggest/openapi-go/openapi3" - "github.com/swaggest/rest/gorillamux" - - "github.com/linuxsuren/api-testing/pkg/version" - - "github.com/linuxsuren/api-testing/pkg/logging" - "github.com/linuxsuren/api-testing/pkg/render" - "github.com/linuxsuren/api-testing/pkg/util" - - "github.com/gorilla/mux" + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strings" + "sync" + "time" + + jsonpatch "github.com/evanphx/json-patch" + "github.com/swaggest/openapi-go/openapi3" + "github.com/swaggest/rest/gorillamux" + + "github.com/linuxsuren/api-testing/pkg/version" + + "github.com/linuxsuren/api-testing/pkg/logging" + "github.com/linuxsuren/api-testing/pkg/render" + "github.com/linuxsuren/api-testing/pkg/util" + + "github.com/gorilla/mux" ) var ( - memLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("memory") + memLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("memory") ) type inMemoryServer struct { - data map[string][]map[string]interface{} - mux *mux.Router - listener net.Listener - port int - prefix string - wg sync.WaitGroup - ctx context.Context - cancelFunc context.CancelFunc - reader Reader - metrics RequestMetrics + data map[string][]map[string]interface{} + mux *mux.Router + listener net.Listener + port int + prefix string + wg sync.WaitGroup + ctx context.Context + cancelFunc context.CancelFunc + reader Reader + metrics RequestMetrics } func NewInMemoryServer(ctx context.Context, port int) DynamicServer { - ctx, cancel := context.WithCancel(ctx) - return &inMemoryServer{ - port: port, - wg: sync.WaitGroup{}, - ctx: ctx, - cancelFunc: cancel, - metrics: NewNoopMetrics(), - } + ctx, cancel := context.WithCancel(ctx) + return &inMemoryServer{ + port: port, + wg: sync.WaitGroup{}, + ctx: ctx, + cancelFunc: cancel, + metrics: NewNoopMetrics(), + } } func (s *inMemoryServer) SetupHandler(reader Reader, prefix string) (handler http.Handler, err error) { - s.reader = reader - // init the data - s.data = make(map[string][]map[string]interface{}) - s.mux = mux.NewRouter().PathPrefix(prefix).Subrouter() - s.prefix = prefix - handler = s.mux - s.metrics.AddMetricsHandler(s.mux) - err = s.Load() - return + s.reader = reader + // init the data + s.data = make(map[string][]map[string]interface{}) + s.mux = mux.NewRouter().PathPrefix(prefix).Subrouter() + s.prefix = prefix + handler = s.mux + s.metrics.AddMetricsHandler(s.mux) + err = s.Load() + return } func (s *inMemoryServer) Load() (err error) { - var server *Server - if server, err = s.reader.Parse(); err != nil { - return - } - - memLogger.Info("start to run all the APIs from objects", "count", len(server.Objects)) - for _, obj := range server.Objects { - memLogger.Info("start mock server from object", "name", obj.Name) - s.startObject(obj) - s.initObjectData(obj) - } - - memLogger.Info("start to run all the APIs from items", "count", len(server.Items)) - for _, item := range server.Items { - s.startItem(item) - } - - memLogger.Info("start webhook servers", "count", len(server.Webhooks)) - for _, item := range server.Webhooks { - if err = s.startWebhook(&item); err != nil { - continue - } - } - - s.handleOpenAPI() - - for _, proxy := range server.Proxies { - memLogger.Info("start to proxy", "target", proxy.Target) - s.mux.HandleFunc(proxy.Path, func(w http.ResponseWriter, req *http.Request) { - if !strings.HasSuffix(proxy.Target, "/") { - proxy.Target += "/" - } - targetPath := strings.TrimPrefix(req.URL.Path, s.prefix) - if strings.HasPrefix(targetPath, "/") { - targetPath = strings.TrimPrefix(targetPath, "/") - } - - apiRaw := fmt.Sprintf("%s%s", proxy.Target, targetPath) - var api string - api, err = render.Render("proxy api", apiRaw, s) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - memLogger.Error(err, "failed to render proxy api", "api", apiRaw) - return - } - memLogger.Info("redirect to", "target", api) - - var requestBody []byte - if requestBody, err = io.ReadAll(req.Body); err != nil { - w.WriteHeader(http.StatusInternalServerError) - } - - if proxy.RequestAmend.BodyPatch != "" && len(requestBody) > 0 { - var patch jsonpatch.Patch - if patch, err = jsonpatch.DecodePatch([]byte(proxy.RequestAmend.BodyPatch)); err != nil { - return - } - - fmt.Println("before patch:", string(requestBody)) - if requestBody, err = patch.Apply(requestBody); err != nil { - fmt.Println(err) - return - } - fmt.Println("after patch:", string(requestBody)) - } - - targetReq, err := http.NewRequestWithContext(req.Context(), req.Method, api, bytes.NewBuffer(requestBody)) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - memLogger.Error(err, "failed to create proxy request") - return - } - - for k, v := range req.Header { - targetReq.Header.Add(k, v[0]) - } - - resp, err := http.DefaultClient.Do(targetReq) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - memLogger.Error(err, "failed to do proxy request") - return - } - - data, err := io.ReadAll(resp.Body) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - memLogger.Error(err, "failed to read response body") - return - } - - for k, v := range resp.Header { - w.Header().Add(k, v[0]) - } - w.Write(data) - }) - } - return + var server *Server + if server, err = s.reader.Parse(); err != nil { + return + } + + memLogger.Info("start to run all the APIs from objects", "count", len(server.Objects)) + for _, obj := range server.Objects { + memLogger.Info("start mock server from object", "name", obj.Name) + s.startObject(obj) + s.initObjectData(obj) + } + + memLogger.Info("start to run all the APIs from items", "count", len(server.Items)) + for _, item := range server.Items { + s.startItem(item) + } + + memLogger.Info("start webhook servers", "count", len(server.Webhooks)) + for _, item := range server.Webhooks { + if err = s.startWebhook(&item); err != nil { + continue + } + } + + s.handleOpenAPI() + + for i, proxy := range server.Proxies { + memLogger.Info("start to proxy", "target", proxy.Target) + switch proxy.Protocol { + case "http", "": + s.httpProxy(&proxy) + case "tcp": + s.tcpProxy(&server.Proxies[i]) + default: + memLogger.Error(fmt.Errorf("unsupported protocol: %s", proxy.Protocol), "failed to start proxy") + } + } + return +} + +func (s *inMemoryServer) httpProxy(proxy *Proxy) { + s.mux.HandleFunc(proxy.Path, func(w http.ResponseWriter, req *http.Request) { + if !strings.HasSuffix(proxy.Target, "/") { + proxy.Target += "/" + } + targetPath := strings.TrimPrefix(req.URL.Path, s.prefix) + if strings.HasPrefix(targetPath, "/") { + targetPath = strings.TrimPrefix(targetPath, "/") + } + + apiRaw := fmt.Sprintf("%s%s", proxy.Target, targetPath) + api, err := render.Render("proxy api", apiRaw, s) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + memLogger.Error(err, "failed to render proxy api", "api", apiRaw) + return + } + memLogger.Info("redirect to", "target", api) + + var requestBody []byte + if requestBody, err = io.ReadAll(req.Body); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + + if proxy.RequestAmend.BodyPatch != "" && len(requestBody) > 0 { + var patch jsonpatch.Patch + if patch, err = jsonpatch.DecodePatch([]byte(proxy.RequestAmend.BodyPatch)); err != nil { + return + } + + fmt.Println("before patch:", string(requestBody)) + if requestBody, err = patch.Apply(requestBody); err != nil { + fmt.Println(err) + return + } + fmt.Println("after patch:", string(requestBody)) + } + + targetReq, err := http.NewRequestWithContext(req.Context(), req.Method, api, bytes.NewBuffer(requestBody)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + memLogger.Error(err, "failed to create proxy request") + return + } + + for k, v := range req.Header { + targetReq.Header.Add(k, v[0]) + } + + resp, err := http.DefaultClient.Do(targetReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + memLogger.Error(err, "failed to do proxy request") + return + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + memLogger.Error(err, "failed to read response body") + return + } + + for k, v := range resp.Header { + w.Header().Add(k, v[0]) + } + w.Write(data) + }) +} + +func (s *inMemoryServer) tcpProxy(proxy *Proxy) { + fmt.Println("start to proxy", proxy.Port) + lisener, err := net.Listen("tcp", fmt.Sprintf(":%d", proxy.Port)) + if err != nil { + memLogger.Error(err, "failed to listen") + return + } + fmt.Printf("proxy local: %d, target: %s\n", proxy.Port, proxy.Target) + defer lisener.Close() + + for { + conn, err := lisener.Accept() + if err != nil { + memLogger.Error(err, "failed to accept") + continue + } + + fmt.Println("accept connection") + go handleConnection(conn, proxy.Target) + } +} + +func handleConnection(clientConn net.Conn, targetAddr string) { + defer clientConn.Close() + + targetConn, err := net.DialTimeout("tcp", targetAddr, 10*time.Second) + if err != nil { + fmt.Printf("Failed to connect to target server: %v\n", err) + return + } + defer targetConn.Close() + + fmt.Printf("Connection established between %s and %s\n", clientConn.RemoteAddr(), targetConn.RemoteAddr()) + + go io.Copy(clientConn, targetConn) + go io.Copy(targetConn, clientConn) + + select {} } func (s *inMemoryServer) Start(reader Reader, prefix string) (err error) { - var handler http.Handler - if handler, err = s.SetupHandler(reader, prefix); err == nil { - if s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.port)); err == nil { - go func() { - err = http.Serve(s.listener, handler) - }() - } - } - return + var handler http.Handler + if handler, err = s.SetupHandler(reader, prefix); err == nil { + if s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.port)); err == nil { + go func() { + err = http.Serve(s.listener, handler) + }() + } + } + return } func (s *inMemoryServer) EnableMetrics() { - s.metrics = NewInMemoryMetrics() + s.metrics = NewInMemoryMetrics() } func (s *inMemoryServer) startObject(obj Object) { - // create a simple CRUD server - s.mux.HandleFunc("/"+obj.Name, func(w http.ResponseWriter, req *http.Request) { - fmt.Println("mock server received request", req.URL.Path) - s.metrics.RecordRequest(req.URL.Path) - method := req.Method - w.Header().Set(util.ContentType, util.JSON) - - switch method { - case http.MethodGet: - // list all items - allItems := s.data[obj.Name] - filteredItems := make([]map[string]interface{}, 0) - - for i, item := range allItems { - exclude := false - - for k, v := range req.URL.Query() { - if len(v) == 0 { - continue - } - - if val, ok := item[k]; ok && val != v[0] { - exclude = true - break - } - } - - if !exclude { - filteredItems = append(filteredItems, allItems[i]) - } - } - - if len(filteredItems) != len(allItems) { - allItems = filteredItems - } - - data, err := json.Marshal(allItems) - writeResponse(w, data, err) - case http.MethodPost: - // create an item - if data, err := io.ReadAll(req.Body); err == nil { - objData := map[string]interface{}{} - - jsonErr := json.Unmarshal(data, &objData) - if jsonErr != nil { - memLogger.Info(jsonErr.Error()) - return - } - - s.data[obj.Name] = append(s.data[obj.Name], objData) - - _, _ = w.Write(data) - } else { - memLogger.Info("failed to read from body", "error", err) - } - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - }) - - // handle a single object - s.mux.HandleFunc(fmt.Sprintf("/%s/{name}", obj.Name), func(w http.ResponseWriter, req *http.Request) { - s.metrics.RecordRequest(req.URL.Path) - w.Header().Set(util.ContentType, util.JSON) - objects := s.data[obj.Name] - if objects != nil { - name := mux.Vars(req)["name"] - var data []byte - for _, obj := range objects { - if obj["name"] == name { - - data, _ = json.Marshal(obj) - break - } - } - - if len(data) == 0 { - w.WriteHeader(http.StatusNotFound) - return - } - - method := req.Method - switch method { - case http.MethodGet: - writeResponse(w, data, nil) - case http.MethodPut: - objData := map[string]interface{}{} - if data, err := io.ReadAll(req.Body); err == nil { - - jsonErr := json.Unmarshal(data, &objData) - if jsonErr != nil { - memLogger.Info(jsonErr.Error()) - return - } - for i, item := range s.data[obj.Name] { - if item["name"] == name { - s.data[obj.Name][i] = objData - break - } - } - _, _ = w.Write(data) - } - case http.MethodDelete: - for i, item := range s.data[obj.Name] { - if item["name"] == name { - if len(s.data[obj.Name]) == i+1 { - s.data[obj.Name] = s.data[obj.Name][:i] - } else { - s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) - } - - writeResponse(w, []byte(`{"msg": "deleted"}`), nil) - } - } - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - - } - }) + // create a simple CRUD server + s.mux.HandleFunc("/"+obj.Name, func(w http.ResponseWriter, req *http.Request) { + fmt.Println("mock server received request", req.URL.Path) + s.metrics.RecordRequest(req.URL.Path) + method := req.Method + w.Header().Set(util.ContentType, util.JSON) + + switch method { + case http.MethodGet: + // list all items + allItems := s.data[obj.Name] + filteredItems := make([]map[string]interface{}, 0) + + for i, item := range allItems { + exclude := false + + for k, v := range req.URL.Query() { + if len(v) == 0 { + continue + } + + if val, ok := item[k]; ok && val != v[0] { + exclude = true + break + } + } + + if !exclude { + filteredItems = append(filteredItems, allItems[i]) + } + } + + if len(filteredItems) != len(allItems) { + allItems = filteredItems + } + + data, err := json.Marshal(allItems) + writeResponse(w, data, err) + case http.MethodPost: + // create an item + if data, err := io.ReadAll(req.Body); err == nil { + objData := map[string]interface{}{} + + jsonErr := json.Unmarshal(data, &objData) + if jsonErr != nil { + memLogger.Info(jsonErr.Error()) + return + } + + s.data[obj.Name] = append(s.data[obj.Name], objData) + + _, _ = w.Write(data) + } else { + memLogger.Info("failed to read from body", "error", err) + } + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } + }) + + // handle a single object + s.mux.HandleFunc(fmt.Sprintf("/%s/{name}", obj.Name), func(w http.ResponseWriter, req *http.Request) { + s.metrics.RecordRequest(req.URL.Path) + w.Header().Set(util.ContentType, util.JSON) + objects := s.data[obj.Name] + if objects != nil { + name := mux.Vars(req)["name"] + var data []byte + for _, obj := range objects { + if obj["name"] == name { + + data, _ = json.Marshal(obj) + break + } + } + + if len(data) == 0 { + w.WriteHeader(http.StatusNotFound) + return + } + + method := req.Method + switch method { + case http.MethodGet: + writeResponse(w, data, nil) + case http.MethodPut: + objData := map[string]interface{}{} + if data, err := io.ReadAll(req.Body); err == nil { + + jsonErr := json.Unmarshal(data, &objData) + if jsonErr != nil { + memLogger.Info(jsonErr.Error()) + return + } + for i, item := range s.data[obj.Name] { + if item["name"] == name { + s.data[obj.Name][i] = objData + break + } + } + _, _ = w.Write(data) + } + case http.MethodDelete: + for i, item := range s.data[obj.Name] { + if item["name"] == name { + if len(s.data[obj.Name]) == i+1 { + s.data[obj.Name] = s.data[obj.Name][:i] + } else { + s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) + } + + writeResponse(w, []byte(`{"msg": "deleted"}`), nil) + } + } + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } + + } + }) } func (s *inMemoryServer) startItem(item Item) { - method := util.EmptyThenDefault(item.Request.Method, http.MethodGet) - memLogger.Info("register mock service", "method", method, "path", item.Request.Path, "encoder", item.Response.Encoder) - - var headerSlices []string - for k, v := range item.Request.Header { - headerSlices = append(headerSlices, k, v) - } - - adHandler := &advanceHandler{ - item: &item, - metrics: s.metrics, - mu: sync.Mutex{}, - } - s.mux.HandleFunc(item.Request.Path, adHandler.handle).Methods(strings.Split(method, ",")...).Headers(headerSlices...) + method := util.EmptyThenDefault(item.Request.Method, http.MethodGet) + memLogger.Info("register mock service", "method", method, "path", item.Request.Path, "encoder", item.Response.Encoder) + + var headerSlices []string + for k, v := range item.Request.Header { + headerSlices = append(headerSlices, k, v) + } + + adHandler := &advanceHandler{ + item: &item, + metrics: s.metrics, + mu: sync.Mutex{}, + } + s.mux.HandleFunc(item.Request.Path, adHandler.handle).Methods(strings.Split(method, ",")...).Headers(headerSlices...) } type advanceHandler struct { - item *Item - metrics RequestMetrics - mu sync.Mutex + item *Item + metrics RequestMetrics + mu sync.Mutex } func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) { - h.mu.Lock() - defer h.mu.Unlock() - - h.metrics.RecordRequest(req.URL.Path) - memLogger.Info("receiving mock request", "name", h.item.Name, "method", req.Method, "path", req.URL.Path, - "encoder", h.item.Response.Encoder) - - h.item.Param = mux.Vars(req) - if h.item.Param == nil { - h.item.Param = make(map[string]string) - } - h.item.Param["Host"] = req.Host - if h.item.Response.Header == nil { - h.item.Response.Header = make(map[string]string) - } - h.item.Response.Header[headerMockServer] = fmt.Sprintf("api-testing: %s", version.GetVersion()) - for k, v := range h.item.Response.Header { - hv, hErr := render.Render("mock-server-header", v, &h.item) - if hErr != nil { - hv = v - memLogger.Error(hErr, "failed render mock-server-header", "value", v) - } - - w.Header().Set(k, hv) - } - - var err error - if h.item.Response.Encoder == "base64" { - h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(h.item.Response.Body) - } else if h.item.Response.Encoder == "url" { - var resp *http.Response - if resp, err = http.Get(h.item.Response.Body); err == nil { - h.item.Response.BodyData, err = io.ReadAll(resp.Body) - } - } else { - if h.item.Response.BodyData, err = render.RenderAsBytes("start-item", h.item.Response.Body, h.item); err != nil { - fmt.Printf("failed to render body: %v", err) - } - } - - if err == nil { - h.item.Response.Header[util.ContentLength] = fmt.Sprintf("%d", len(h.item.Response.BodyData)) - w.Header().Set(util.ContentLength, h.item.Response.Header[util.ContentLength]) - } - - writeResponse(w, h.item.Response.BodyData, err) + h.mu.Lock() + defer h.mu.Unlock() + + h.metrics.RecordRequest(req.URL.Path) + memLogger.Info("receiving mock request", "name", h.item.Name, "method", req.Method, "path", req.URL.Path, + "encoder", h.item.Response.Encoder) + + h.item.Param = mux.Vars(req) + if h.item.Param == nil { + h.item.Param = make(map[string]string) + } + h.item.Param["Host"] = req.Host + if h.item.Response.Header == nil { + h.item.Response.Header = make(map[string]string) + } + h.item.Response.Header[headerMockServer] = fmt.Sprintf("api-testing: %s", version.GetVersion()) + for k, v := range h.item.Response.Header { + hv, hErr := render.Render("mock-server-header", v, &h.item) + if hErr != nil { + hv = v + memLogger.Error(hErr, "failed render mock-server-header", "value", v) + } + + w.Header().Set(k, hv) + } + + var err error + if h.item.Response.Encoder == "base64" { + h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(h.item.Response.Body) + } else if h.item.Response.Encoder == "url" { + var resp *http.Response + if resp, err = http.Get(h.item.Response.Body); err == nil { + h.item.Response.BodyData, err = io.ReadAll(resp.Body) + } + } else { + if h.item.Response.BodyData, err = render.RenderAsBytes("start-item", h.item.Response.Body, h.item); err != nil { + fmt.Printf("failed to render body: %v", err) + } + } + + if err == nil { + h.item.Response.Header[util.ContentLength] = fmt.Sprintf("%d", len(h.item.Response.BodyData)) + w.Header().Set(util.ContentLength, h.item.Response.Header[util.ContentLength]) + } + + writeResponse(w, h.item.Response.BodyData, err) } func writeResponse(w http.ResponseWriter, data []byte, err error) { - if err == nil { - w.Write(data) - } else { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - } + if err == nil { + w.Write(data) + } else { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + } } func (s *inMemoryServer) initObjectData(obj Object) { - if obj.Sample == "" { - return - } - - defaultCount := 1 - if obj.InitCount == nil { - obj.InitCount = &defaultCount - } - - for i := 0; i < *obj.InitCount; i++ { - objData, jsonErr := jsonStrToInterface(obj.Sample) - if jsonErr == nil { - s.data[obj.Name] = append(s.data[obj.Name], objData) - } else { - memLogger.Info(jsonErr.Error()) - } - } + if obj.Sample == "" { + return + } + + defaultCount := 1 + if obj.InitCount == nil { + obj.InitCount = &defaultCount + } + + for i := 0; i < *obj.InitCount; i++ { + objData, jsonErr := jsonStrToInterface(obj.Sample) + if jsonErr == nil { + s.data[obj.Name] = append(s.data[obj.Name], objData) + } else { + memLogger.Info(jsonErr.Error()) + } + } } func (s *inMemoryServer) startWebhook(webhook *Webhook) (err error) { - if webhook.Timer == "" || webhook.Name == "" { - return - } - - var duration time.Duration - duration, err = time.ParseDuration(webhook.Timer) - if err != nil { - memLogger.Error(err, "Error parsing webhook timer") - return - } - - s.wg.Add(1) - go func(wh *Webhook) { - defer s.wg.Done() - - memLogger.Info("start webhook server", "name", wh.Name) - timer := time.NewTimer(duration) - for { - timer.Reset(duration) - select { - case <-s.ctx.Done(): - memLogger.Info("stop webhook server", "name", wh.Name) - return - case <-timer.C: - if err = runWebhook(s.ctx, s, wh); err != nil { - memLogger.Error(err, "Error when run webhook") - } - } - } - }(webhook) - return + if webhook.Timer == "" || webhook.Name == "" { + return + } + + var duration time.Duration + duration, err = time.ParseDuration(webhook.Timer) + if err != nil { + memLogger.Error(err, "Error parsing webhook timer") + return + } + + s.wg.Add(1) + go func(wh *Webhook) { + defer s.wg.Done() + + memLogger.Info("start webhook server", "name", wh.Name) + timer := time.NewTimer(duration) + for { + timer.Reset(duration) + select { + case <-s.ctx.Done(): + memLogger.Info("stop webhook server", "name", wh.Name) + return + case <-timer.C: + if err = runWebhook(s.ctx, s, wh); err != nil { + memLogger.Error(err, "Error when run webhook") + } + } + } + }(webhook) + return } func runWebhook(ctx context.Context, objCtx interface{}, wh *Webhook) (err error) { - client := http.DefaultClient - - var payload io.Reader - payload, err = render.RenderAsReader("mock webhook server payload", wh.Request.Body, wh) - if err != nil { - err = fmt.Errorf("error when render payload: %w", err) - return - } - - method := util.EmptyThenDefault(wh.Request.Method, http.MethodPost) - var api string - api, err = render.Render("webhook request api", wh.Request.Path, objCtx) - if err != nil { - err = fmt.Errorf("error when render api: %w, template: %s", err, wh.Request.Path) - return - } - - var bearerToken string - bearerToken, err = getBearerToken(ctx, wh.Request) - if err != nil { - memLogger.Error(err, "Error when render bearer token") - return - } - - var req *http.Request - req, err = http.NewRequestWithContext(ctx, method, api, payload) - if err != nil { - memLogger.Error(err, "Error when create request") - return - } - - if bearerToken != "" { - memLogger.V(7).Info("set bearer token", "token", bearerToken) - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", bearerToken)) - } - - for k, v := range wh.Request.Header { - req.Header.Set(k, v) - } - - memLogger.Info("send webhook request", "api", api) - resp, err := client.Do(req) - if err != nil { - err = fmt.Errorf("error when sending webhook") - } else { - if resp.StatusCode != http.StatusOK { - memLogger.Info("unexpected status", "code", resp.StatusCode) - } - - data, _ := io.ReadAll(resp.Body) - memLogger.V(7).Info("received from webhook", "code", resp.StatusCode, "response", string(data)) - } - return + client := http.DefaultClient + + var payload io.Reader + payload, err = render.RenderAsReader("mock webhook server payload", wh.Request.Body, wh) + if err != nil { + err = fmt.Errorf("error when render payload: %w", err) + return + } + + method := util.EmptyThenDefault(wh.Request.Method, http.MethodPost) + var api string + api, err = render.Render("webhook request api", wh.Request.Path, objCtx) + if err != nil { + err = fmt.Errorf("error when render api: %w, template: %s", err, wh.Request.Path) + return + } + + var bearerToken string + bearerToken, err = getBearerToken(ctx, wh.Request) + if err != nil { + memLogger.Error(err, "Error when render bearer token") + return + } + + var req *http.Request + req, err = http.NewRequestWithContext(ctx, method, api, payload) + if err != nil { + memLogger.Error(err, "Error when create request") + return + } + + if bearerToken != "" { + memLogger.V(7).Info("set bearer token", "token", bearerToken) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", bearerToken)) + } + + for k, v := range wh.Request.Header { + req.Header.Set(k, v) + } + + memLogger.Info("send webhook request", "api", api) + resp, err := client.Do(req) + if err != nil { + err = fmt.Errorf("error when sending webhook") + } else { + if resp.StatusCode != http.StatusOK { + memLogger.Info("unexpected status", "code", resp.StatusCode) + } + + data, _ := io.ReadAll(resp.Body) + memLogger.V(7).Info("received from webhook", "code", resp.StatusCode, "response", string(data)) + } + return } type bearerToken struct { - Token string `json:"token"` + Token string `json:"token"` } func getBearerToken(ctx context.Context, request RequestWithAuth) (token string, err error) { - if request.BearerAPI == "" { - return - } - - if request.BearerAPI, err = render.Render("bearer token request", request.BearerAPI, &request); err != nil { - return - } - - var data []byte - if data, err = json.Marshal(&request); err == nil { - client := http.DefaultClient - var req *http.Request - if req, err = http.NewRequestWithContext(ctx, http.MethodPost, request.BearerAPI, bytes.NewBuffer(data)); err == nil { - req.Header.Set(util.ContentType, util.JSON) - - var resp *http.Response - if resp, err = client.Do(req); err == nil && resp.StatusCode == http.StatusOK { - if data, err = io.ReadAll(resp.Body); err == nil { - var tokenObj bearerToken - if err = json.Unmarshal(data, &tokenObj); err == nil { - token = tokenObj.Token - } - } - } - } - } - - return + if request.BearerAPI == "" { + return + } + + if request.BearerAPI, err = render.Render("bearer token request", request.BearerAPI, &request); err != nil { + return + } + + var data []byte + if data, err = json.Marshal(&request); err == nil { + client := http.DefaultClient + var req *http.Request + if req, err = http.NewRequestWithContext(ctx, http.MethodPost, request.BearerAPI, bytes.NewBuffer(data)); err == nil { + req.Header.Set(util.ContentType, util.JSON) + + var resp *http.Response + if resp, err = client.Do(req); err == nil && resp.StatusCode == http.StatusOK { + if data, err = io.ReadAll(resp.Body); err == nil { + var tokenObj bearerToken + if err = json.Unmarshal(data, &tokenObj); err == nil { + token = tokenObj.Token + } + } + } + } + } + + return } func (s *inMemoryServer) handleOpenAPI() { - s.mux.HandleFunc("/api.json", func(w http.ResponseWriter, req *http.Request) { - // Setup OpenAPI schema - reflector := openapi3.NewReflector() - reflector.SpecSchema().SetTitle("Mock Server API") - reflector.SpecSchema().SetVersion(version.GetVersion()) - reflector.SpecSchema().SetDescription("Powered by https://github.com/linuxsuren/api-testing") - - // Walk the router with OpenAPI collector - c := gorillamux.NewOpenAPICollector(reflector) - - _ = s.mux.Walk(c.Walker) - - // Get the resulting schema - if jsonData, err := reflector.Spec.MarshalJSON(); err == nil { - w.Header().Set(util.ContentType, util.JSON) - _, _ = w.Write(jsonData) - } else { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(err.Error())) - } - }) + s.mux.HandleFunc("/api.json", func(w http.ResponseWriter, req *http.Request) { + // Setup OpenAPI schema + reflector := openapi3.NewReflector() + reflector.SpecSchema().SetTitle("Mock Server API") + reflector.SpecSchema().SetVersion(version.GetVersion()) + reflector.SpecSchema().SetDescription("Powered by https://github.com/linuxsuren/api-testing") + + // Walk the router with OpenAPI collector + c := gorillamux.NewOpenAPICollector(reflector) + + _ = s.mux.Walk(c.Walker) + + // Get the resulting schema + if jsonData, err := reflector.Spec.MarshalJSON(); err == nil { + w.Header().Set(util.ContentType, util.JSON) + _, _ = w.Write(jsonData) + } else { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(err.Error())) + } + }) } func jsonStrToInterface(jsonStr string) (objData map[string]interface{}, err error) { - if jsonStr, err = render.Render("init object", jsonStr, nil); err == nil { - objData = map[string]interface{}{} - err = json.Unmarshal([]byte(jsonStr), &objData) - } - return + if jsonStr, err = render.Render("init object", jsonStr, nil); err == nil { + objData = map[string]interface{}{} + err = json.Unmarshal([]byte(jsonStr), &objData) + } + return } func (s *inMemoryServer) GetPort() string { - return util.GetPort(s.listener) + return util.GetPort(s.listener) } func (s *inMemoryServer) Stop() (err error) { - if s.listener != nil { - err = s.listener.Close() - } else { - memLogger.Info("listener is nil") - } - if s.cancelFunc != nil { - s.cancelFunc() - } - s.wg.Wait() - return + if s.listener != nil { + err = s.listener.Close() + } else { + memLogger.Info("listener is nil") + } + if s.cancelFunc != nil { + s.cancelFunc() + } + s.wg.Wait() + return } diff --git a/pkg/mock/types.go b/pkg/mock/types.go index fdbcf67c..b1bb0d8e 100644 --- a/pkg/mock/types.go +++ b/pkg/mock/types.go @@ -62,6 +62,7 @@ type Proxy struct { Path string `yaml:"path" json:"path"` Target string `yaml:"target" json:"target"` RequestAmend RequestAmend `yaml:"requestAmend" json:"requestAmend"` + Protocol string `yaml:"protocol" json:"protocol"` } type RequestAmend struct { diff --git a/pkg/runner/http_test.go b/pkg/runner/http_test.go index 63918ce6..5bf6be7f 100644 --- a/pkg/runner/http_test.go +++ b/pkg/runner/http_test.go @@ -16,622 +16,622 @@ limitations under the License. package runner import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "strings" - "testing" + "bytes" + "context" + "errors" + "io" + "net/http" + "strings" + "testing" - _ "embed" + _ "embed" - "github.com/go-openapi/spec" - "github.com/h2non/gock" - atest "github.com/linuxsuren/api-testing/pkg/testing" - "github.com/linuxsuren/api-testing/pkg/util" - fakeruntime "github.com/linuxsuren/go-fake-runtime" - "github.com/stretchr/testify/assert" + "github.com/go-openapi/spec" + "github.com/h2non/gock" + atest "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/linuxsuren/api-testing/pkg/util" + fakeruntime "github.com/linuxsuren/go-fake-runtime" + "github.com/stretchr/testify/assert" ) func TestTestCase(t *testing.T) { - fooRequest := atest.Request{ - API: urlFoo, - } - defaultForm := map[string]string{ - "key": "value", - } - const defaultBody = `{"name":"hello"}` - defaultPrepare := func() { - gock.New(urlLocalhost). - Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`). - SetHeader(util.ContentType, util.JSON) - } - defaultPostPrepare := func() { - gock.New(urlLocalhost). - Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`) - } + fooRequest := atest.Request{ + API: urlFoo, + } + defaultForm := map[string]string{ + "key": "value", + } + const defaultBody = `{"name":"hello"}` + defaultPrepare := func() { + gock.New(urlLocalhost). + Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`). + SetHeader(util.ContentType, util.JSON) + } + defaultPostPrepare := func() { + gock.New(urlLocalhost). + Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`) + } - tests := []struct { - name string - execer fakeruntime.Execer - testCase *atest.TestCase - ctx interface{} - prepare func() - verify func(t *testing.T, output interface{}, err error) - }{{ - name: "failed during the prepare stage", - testCase: &atest.TestCase{ - Before: &atest.Job{ - Items: []string{"demo.yaml"}, - }, - }, - execer: fakeruntime.FakeExecer{ExpectError: errors.New("fake")}, - }, { - name: "normal, response is map", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Header: defaultForm, - Query: map[string]interface{}{ - "name": "linuxsuren", - }, - Cookie: map[string]string{ - "key": "value", - }, - Body: atest.NewRequestBody(`{"foo":"bar"}`), - }, - Expect: atest.Response{ - StatusCode: http.StatusOK, - BodyFieldsExpect: map[string]interface{}{ - "name": "linuxsuren", - "number": 1, - }, - Header: map[string]string{ - "type": "generic", - }, - Verify: []string{ - `data.name == "linuxsuren"`, - }, - }, - Before: &atest.Job{ - Items: []string{"sleep(1)"}, - }, - }, - execer: fakeruntime.FakeExecer{}, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo"). - MatchHeader("key", "value"). - Reply(http.StatusOK). - SetHeader("type", "generic"). - SetHeader(util.ContentType, util.JSON). - File("testdata/generic_response.json") - }, - verify: func(t *testing.T, output interface{}, err error) { - assert.Nil(t, err) - assert.Equal(t, map[string]interface{}{"name": "linuxsuren", "number": float64(1)}, output) - }, - }, { - name: "normal, response is slice", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - StatusCode: http.StatusOK, - Body: `["foo", "bar"]`, - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo"). - Reply(http.StatusOK). - SetHeader(util.ContentType, util.JSON). - BodyString(`["foo", "bar"]`) - }, - verify: func(t *testing.T, output interface{}, err error) { - assert.Nil(t, err) - assert.Equal(t, []interface{}{"foo", "bar"}, output) - }, - }, { - name: "normal, response from file", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - BodyFromFile: "testdata/generic_response.json", - }, - Expect: atest.Response{ - StatusCode: http.StatusOK, - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Post("/foo").BodyString(genericBody). - Reply(http.StatusOK).BodyString("123"). - SetHeader(util.ContentType, util.JSON) - }, - }, { - name: "response from a not found file", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - BodyFromFile: "testdata/fake.json", - }, - }, - }, { - name: "bad request", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - StatusCode: http.StatusOK, - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo").Reply(http.StatusBadRequest) - }, - }, { - name: "error with request", - testCase: &atest.TestCase{ - Request: fooRequest, - }, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo").ReplyError(errors.New("error")) - }, - }, { - name: "not match with body", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - Body: "bar", - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo").Reply(http.StatusOK). - SetHeader(util.ContentType, util.Plain).BodyString("foo") - }, - }, { - name: "not match with header", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - Header: map[string]string{ - "foo": "bar", - }, - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Get("/foo").Reply(http.StatusOK).SetHeader("foo", "value") - }, - }, { - name: "not found from fields", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - BodyFieldsExpect: map[string]interface{}{ - "foo": "bar", - }, - }, - }, - prepare: prepareForFoo, - }, { - name: "body filed not match", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - BodyFieldsExpect: map[string]interface{}{ - "name": "bar", - }, - }, - }, - prepare: prepareForFoo, - }, { - name: "invalid filed finding", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - BodyFieldsExpect: map[string]interface{}{ - "0.items": "bar", - }, - }, - }, - prepare: defaultPrepare, - verify: func(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "not found field") - }, - }, { - name: "verify failed", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - }, - Expect: atest.Response{ - Verify: []string{ - "len(data.items) > 0", - }, - }, - }, - prepare: defaultPrepare, - verify: func(t *testing.T, output interface{}, err error) { - if assert.NotNil(t, err) { - assert.Contains(t, err.Error(), "failed to verify") - } - }, - }, { - name: "failed to compile", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - Verify: []string{ - `println("12")`, - }, - }, - }, - prepare: defaultPrepare, - verify: func(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "unknown name println") - }, - }, { - name: "failed to compile", - testCase: &atest.TestCase{ - Request: fooRequest, - Expect: atest.Response{ - Verify: []string{ - `1 + 1`, - }, - }, - }, - prepare: defaultPrepare, - verify: func(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "expected bool, but got int") - }, - }, { - name: "wrong API format", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: "ssh://localhost/foo", - Method: "fake,fake", - }, - }, - verify: func(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "invalid method") - }, - }, { - name: "failed to render API", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: "http://localhost/foo/{{.abc}", - }, - }, - verify: func(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "template: api:1:") - }, - }, { - name: "multipart form request", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - Header: map[string]string{ - util.ContentType: "multipart/form-data", - }, - Form: defaultForm, - }, - }, - prepare: defaultPostPrepare, - verify: noError, - }, { - name: "normal form request", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - Header: map[string]string{ - util.ContentType: "application/x-www-form-urlencoded", - }, - Form: defaultForm, - }, - }, - prepare: defaultPostPrepare, - verify: noError, - }, { - name: "body is a template", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - Body: atest.NewRequestBody(`{"name":"{{lower "HELLO"}}"}`), - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Post("/foo").BodyString(`{"name":"hello"}`). - Reply(http.StatusOK).BodyString(`{}`) - }, - verify: noError, - }, { - name: "status code not match", - testCase: &atest.TestCase{ - Request: atest.Request{ - API: urlFoo, - Method: http.MethodPost, - }, - Expect: atest.Response{ - StatusCode: http.StatusBadRequest, - }, - }, - prepare: func() { - gock.New(urlLocalhost). - Post("/foo"). - Reply(http.StatusOK). - SetHeader(util.ContentType, util.JSON). - BodyString(defaultBody) - }, - verify: func(t *testing.T, _ interface{}, err error) { - assert.Error(t, err) - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer gock.Clean() - if tt.prepare != nil { - tt.prepare() - } - if tt.verify == nil { - tt.verify = hasError - } - runner := NewSimpleTestCaseRunner() - runner.WithOutputWriter(io.Discard) - if tt.execer != nil { - runner.WithExecer(tt.execer) - } - output, err := runner.RunTestCase(tt.testCase, tt.ctx, context.TODO()) - tt.verify(t, output, err) + tests := []struct { + name string + execer fakeruntime.Execer + testCase *atest.TestCase + ctx interface{} + prepare func() + verify func(t *testing.T, output interface{}, err error) + }{{ + name: "failed during the prepare stage", + testCase: &atest.TestCase{ + Before: &atest.Job{ + Items: []string{"demo.yaml"}, + }, + }, + execer: &fakeruntime.FakeExecer{ExpectError: errors.New("fake")}, + }, { + name: "normal, response is map", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Header: defaultForm, + Query: map[string]interface{}{ + "name": "linuxsuren", + }, + Cookie: map[string]string{ + "key": "value", + }, + Body: atest.NewRequestBody(`{"foo":"bar"}`), + }, + Expect: atest.Response{ + StatusCode: http.StatusOK, + BodyFieldsExpect: map[string]interface{}{ + "name": "linuxsuren", + "number": 1, + }, + Header: map[string]string{ + "type": "generic", + }, + Verify: []string{ + `data.name == "linuxsuren"`, + }, + }, + Before: &atest.Job{ + Items: []string{"sleep(1)"}, + }, + }, + execer: &fakeruntime.FakeExecer{}, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo"). + MatchHeader("key", "value"). + Reply(http.StatusOK). + SetHeader("type", "generic"). + SetHeader(util.ContentType, util.JSON). + File("testdata/generic_response.json") + }, + verify: func(t *testing.T, output interface{}, err error) { + assert.Nil(t, err) + assert.Equal(t, map[string]interface{}{"name": "linuxsuren", "number": float64(1)}, output) + }, + }, { + name: "normal, response is slice", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + StatusCode: http.StatusOK, + Body: `["foo", "bar"]`, + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo"). + Reply(http.StatusOK). + SetHeader(util.ContentType, util.JSON). + BodyString(`["foo", "bar"]`) + }, + verify: func(t *testing.T, output interface{}, err error) { + assert.Nil(t, err) + assert.Equal(t, []interface{}{"foo", "bar"}, output) + }, + }, { + name: "normal, response from file", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + BodyFromFile: "testdata/generic_response.json", + }, + Expect: atest.Response{ + StatusCode: http.StatusOK, + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Post("/foo").BodyString(genericBody). + Reply(http.StatusOK).BodyString("123"). + SetHeader(util.ContentType, util.JSON) + }, + }, { + name: "response from a not found file", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + BodyFromFile: "testdata/fake.json", + }, + }, + }, { + name: "bad request", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + StatusCode: http.StatusOK, + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo").Reply(http.StatusBadRequest) + }, + }, { + name: "error with request", + testCase: &atest.TestCase{ + Request: fooRequest, + }, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo").ReplyError(errors.New("error")) + }, + }, { + name: "not match with body", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + Body: "bar", + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo").Reply(http.StatusOK). + SetHeader(util.ContentType, util.Plain).BodyString("foo") + }, + }, { + name: "not match with header", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + Header: map[string]string{ + "foo": "bar", + }, + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Get("/foo").Reply(http.StatusOK).SetHeader("foo", "value") + }, + }, { + name: "not found from fields", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + BodyFieldsExpect: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + prepare: prepareForFoo, + }, { + name: "body filed not match", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + BodyFieldsExpect: map[string]interface{}{ + "name": "bar", + }, + }, + }, + prepare: prepareForFoo, + }, { + name: "invalid filed finding", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + BodyFieldsExpect: map[string]interface{}{ + "0.items": "bar", + }, + }, + }, + prepare: defaultPrepare, + verify: func(t *testing.T, output interface{}, err error) { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "not found field") + }, + }, { + name: "verify failed", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + }, + Expect: atest.Response{ + Verify: []string{ + "len(data.items) > 0", + }, + }, + }, + prepare: defaultPrepare, + verify: func(t *testing.T, output interface{}, err error) { + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "failed to verify") + } + }, + }, { + name: "failed to compile", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + Verify: []string{ + `println("12")`, + }, + }, + }, + prepare: defaultPrepare, + verify: func(t *testing.T, output interface{}, err error) { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "unknown name println") + }, + }, { + name: "failed to compile", + testCase: &atest.TestCase{ + Request: fooRequest, + Expect: atest.Response{ + Verify: []string{ + `1 + 1`, + }, + }, + }, + prepare: defaultPrepare, + verify: func(t *testing.T, output interface{}, err error) { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "expected bool, but got int") + }, + }, { + name: "wrong API format", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: "ssh://localhost/foo", + Method: "fake,fake", + }, + }, + verify: func(t *testing.T, output interface{}, err error) { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "invalid method") + }, + }, { + name: "failed to render API", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: "http://localhost/foo/{{.abc}", + }, + }, + verify: func(t *testing.T, output interface{}, err error) { + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "template: api:1:") + }, + }, { + name: "multipart form request", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + Header: map[string]string{ + util.ContentType: "multipart/form-data", + }, + Form: defaultForm, + }, + }, + prepare: defaultPostPrepare, + verify: noError, + }, { + name: "normal form request", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + Header: map[string]string{ + util.ContentType: "application/x-www-form-urlencoded", + }, + Form: defaultForm, + }, + }, + prepare: defaultPostPrepare, + verify: noError, + }, { + name: "body is a template", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + Body: atest.NewRequestBody(`{"name":"{{lower "HELLO"}}"}`), + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Post("/foo").BodyString(`{"name":"hello"}`). + Reply(http.StatusOK).BodyString(`{}`) + }, + verify: noError, + }, { + name: "status code not match", + testCase: &atest.TestCase{ + Request: atest.Request{ + API: urlFoo, + Method: http.MethodPost, + }, + Expect: atest.Response{ + StatusCode: http.StatusBadRequest, + }, + }, + prepare: func() { + gock.New(urlLocalhost). + Post("/foo"). + Reply(http.StatusOK). + SetHeader(util.ContentType, util.JSON). + BodyString(defaultBody) + }, + verify: func(t *testing.T, _ interface{}, err error) { + assert.Error(t, err) + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer gock.Clean() + if tt.prepare != nil { + tt.prepare() + } + if tt.verify == nil { + tt.verify = hasError + } + runner := NewSimpleTestCaseRunner() + runner.WithOutputWriter(io.Discard) + if tt.execer != nil { + runner.WithExecer(tt.execer) + } + output, err := runner.RunTestCase(tt.testCase, tt.ctx, context.TODO()) + tt.verify(t, output, err) - getter, ok := runner.(ResponseRecord) - assert.True(t, ok) - assert.NotNil(t, getter.GetResponseRecord()) - }) - } + getter, ok := runner.(ResponseRecord) + assert.True(t, ok) + assert.NotNil(t, getter.GetResponseRecord()) + }) + } } func TestLevelWriter(t *testing.T) { - tests := []struct { - name string - buf *bytes.Buffer - level string - expect string - }{{ - name: "debug", - buf: new(bytes.Buffer), - level: "debug", - expect: "debuginfo", - }, { - name: "info", - buf: new(bytes.Buffer), - level: "info", - expect: "info", - }} - for _, tt := range tests { - writer := NewDefaultLevelWriter(tt.level, tt.buf) - if assert.NotNil(t, writer) { - writer.Debug("debug") - writer.Info("info") + tests := []struct { + name string + buf *bytes.Buffer + level string + expect string + }{{ + name: "debug", + buf: new(bytes.Buffer), + level: "debug", + expect: "debuginfo", + }, { + name: "info", + buf: new(bytes.Buffer), + level: "info", + expect: "info", + }} + for _, tt := range tests { + writer := NewDefaultLevelWriter(tt.level, tt.buf) + if assert.NotNil(t, writer) { + writer.Debug("debug") + writer.Info("info") - assert.Equal(t, tt.expect, tt.buf.String()) - } - } + assert.Equal(t, tt.expect, tt.buf.String()) + } + } } func TestJSONSchemaValidation(t *testing.T) { - tests := []struct { - name string - schema string - body string - hasErr bool - }{{ - name: "normal", - schema: defaultSchemaForTest, - body: `{"name": "linuxsuren", "age": 100}`, - hasErr: false, - }, { - name: "schema is empty", - schema: "", - hasErr: false, - }, { - name: "failed to validate", - schema: defaultSchemaForTest, - body: `{"name": "linuxsuren", "age": "100"}`, - hasErr: true, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := jsonSchemaValidation(tt.schema, []byte(tt.body)) - assert.Equal(t, tt.hasErr, err != nil, err) - }) - } + tests := []struct { + name string + schema string + body string + hasErr bool + }{{ + name: "normal", + schema: defaultSchemaForTest, + body: `{"name": "linuxsuren", "age": 100}`, + hasErr: false, + }, { + name: "schema is empty", + schema: "", + hasErr: false, + }, { + name: "failed to validate", + schema: defaultSchemaForTest, + body: `{"name": "linuxsuren", "age": "100"}`, + hasErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := jsonSchemaValidation(tt.schema, []byte(tt.body)) + assert.Equal(t, tt.hasErr, err != nil, err) + }) + } } func TestRunJob(t *testing.T) { - tests := []struct { - name string - job atest.Job - hasErr bool - }{{ - name: "sleep 1s", - job: atest.Job{ - Items: []string{"sleep(1)"}, - }, - hasErr: false, - }, { - name: "no params", - job: atest.Job{ - Items: []string{"sleep()"}, - }, - hasErr: true, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := runJob(&tt.job, nil, nil) - assert.Equal(t, tt.hasErr, err != nil, err) - }) - } + tests := []struct { + name string + job atest.Job + hasErr bool + }{{ + name: "sleep 1s", + job: atest.Job{ + Items: []string{"sleep(1)"}, + }, + hasErr: false, + }, { + name: "no params", + job: atest.Job{ + Items: []string{"sleep()"}, + }, + hasErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := runJob(&tt.job, nil, nil) + assert.Equal(t, tt.hasErr, err != nil, err) + }) + } } func TestContextKey(t *testing.T) { - assert.Equal(t, ContextKey("parentDir"), NewContextKeyBuilder().ParentDir()) + assert.Equal(t, ContextKey("parentDir"), NewContextKeyBuilder().ParentDir()) - ctx := context.WithValue(context.Background(), NewContextKeyBuilder().ParentDir(), "/tmp") - assert.Equal(t, "/tmp", NewContextKeyBuilder().ParentDir().GetContextValueOrEmpty(ctx)) - assert.Empty(t, ContextKey("fake").GetContextValueOrEmpty(ctx)) + ctx := context.WithValue(context.Background(), NewContextKeyBuilder().ParentDir(), "/tmp") + assert.Equal(t, "/tmp", NewContextKeyBuilder().ParentDir().GetContextValueOrEmpty(ctx)) + assert.Empty(t, ContextKey("fake").GetContextValueOrEmpty(ctx)) } func TestBodyFiledsVerify(t *testing.T) { - tests := []struct { - name string - bodyFields map[string]interface{} - body string - hasErr bool - }{{ - name: "normal", - bodyFields: map[string]interface{}{ - "name": "linuxsuren", - "number": 1, - }, - body: genericBody, - hasErr: false, - }, { - name: "field not found", - bodyFields: map[string]interface{}{ - "project": "", - }, - body: genericBody, - hasErr: true, - }, { - name: "number is not equal", - bodyFields: map[string]interface{}{ - "number": 2, - }, - body: genericBody, - hasErr: true, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - verifier := &jsonBodyVerifier{ - body: &atest.Response{ - BodyFieldsExpect: tt.bodyFields, - }, - } + tests := []struct { + name string + bodyFields map[string]interface{} + body string + hasErr bool + }{{ + name: "normal", + bodyFields: map[string]interface{}{ + "name": "linuxsuren", + "number": 1, + }, + body: genericBody, + hasErr: false, + }, { + name: "field not found", + bodyFields: map[string]interface{}{ + "project": "", + }, + body: genericBody, + hasErr: true, + }, { + name: "number is not equal", + bodyFields: map[string]interface{}{ + "number": 2, + }, + body: genericBody, + hasErr: true, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifier := &jsonBodyVerifier{ + body: &atest.Response{ + BodyFieldsExpect: tt.bodyFields, + }, + } - err := verifier.Verify([]byte(tt.body)) - assert.Equal(t, tt.hasErr, err != nil, err) - }) - } + err := verifier.Verify([]byte(tt.body)) + assert.Equal(t, tt.hasErr, err != nil, err) + }) + } } func TestGetSuggestedAPIs(t *testing.T) { - runner := NewSimpleTestCaseRunner() - runner.WithSuite(nil) - runner.WithAPISuggestLimit(6) - // not a swagger - result, err := runner.GetSuggestedAPIs(&atest.TestSuite{}, "") - assert.NoError(t, err, err) - assert.Empty(t, result) + runner := NewSimpleTestCaseRunner() + runner.WithSuite(nil) + runner.WithAPISuggestLimit(6) + // not a swagger + result, err := runner.GetSuggestedAPIs(&atest.TestSuite{}, "") + assert.NoError(t, err, err) + assert.Empty(t, result) - // swagger - defer gock.Off() - gock.New(urlFoo).Get("swagger.json").Reply(http.StatusOK).File("testdata/swagger.json") - result, err = runner.GetSuggestedAPIs(&atest.TestSuite{ - Spec: atest.APISpec{ - Kind: "swagger", - URL: urlFoo + "/swagger.json", - }, - }, "") - assert.NoError(t, err, err) - assert.NotEmpty(t, result) - method := result[0].Request.Method - assert.Equal(t, strings.ToUpper(method), method) + // swagger + defer gock.Off() + gock.New(urlFoo).Get("swagger.json").Reply(http.StatusOK).File("testdata/swagger.json") + result, err = runner.GetSuggestedAPIs(&atest.TestSuite{ + Spec: atest.APISpec{ + Kind: "swagger", + URL: urlFoo + "/swagger.json", + }, + }, "") + assert.NoError(t, err, err) + assert.NotEmpty(t, result) + method := result[0].Request.Method + assert.Equal(t, strings.ToUpper(method), method) } func TestIsStructContent(t *testing.T) { - tests := []struct { - contentType string - expectOk bool - }{{ - contentType: util.JSON, - expectOk: true, - }, { - contentType: util.YAML, - expectOk: true, - }, { - contentType: util.OctetStream, - expectOk: false, - }, { - contentType: util.OCIImageIndex, - expectOk: true, - }, { - contentType: "application/problem+json", - expectOk: true, - }} - for _, tt := range tests { - t.Run(tt.contentType, func(t *testing.T) { - ok := isNonBinaryContent(tt.contentType) - assert.Equal(t, tt.expectOk, ok) - }) - } + tests := []struct { + contentType string + expectOk bool + }{{ + contentType: util.JSON, + expectOk: true, + }, { + contentType: util.YAML, + expectOk: true, + }, { + contentType: util.OctetStream, + expectOk: false, + }, { + contentType: util.OCIImageIndex, + expectOk: true, + }, { + contentType: "application/problem+json", + expectOk: true, + }} + for _, tt := range tests { + t.Run(tt.contentType, func(t *testing.T) { + ok := isNonBinaryContent(tt.contentType) + assert.Equal(t, tt.expectOk, ok) + }) + } } func TestGenerateRandomValue(t *testing.T) { - tests := []struct { - param spec.Parameter - expected interface{} - }{ - { - param: spec.Parameter{ - SimpleSchema: spec.SimpleSchema{ - Format: "int32", - }, - }, - expected: 101, - }, { - param: spec.Parameter{ - SimpleSchema: spec.SimpleSchema{ - Format: "boolean", - }, - }, - expected: true, - }, { - param: spec.Parameter{ - SimpleSchema: spec.SimpleSchema{ - Format: "string", - }, - }, - expected: "random", - }, - } + tests := []struct { + param spec.Parameter + expected interface{} + }{ + { + param: spec.Parameter{ + SimpleSchema: spec.SimpleSchema{ + Format: "int32", + }, + }, + expected: 101, + }, { + param: spec.Parameter{ + SimpleSchema: spec.SimpleSchema{ + Format: "boolean", + }, + }, + expected: true, + }, { + param: spec.Parameter{ + SimpleSchema: spec.SimpleSchema{ + Format: "string", + }, + }, + expected: "random", + }, + } - for _, tt := range tests { - result := generateRandomValue(tt.param) + for _, tt := range tests { + result := generateRandomValue(tt.param) - if result != tt.expected { - t.Errorf("generateRandomValue(%v) = %v, expected %v", tt.param, result, tt.expected) - } - } + if result != tt.expected { + t.Errorf("generateRandomValue(%v) = %v, expected %v", tt.param, result, tt.expected) + } + } } func TestAmmendHeaders(t *testing.T) { - headers := http.Header{"Content-Type": []string{"application/json"}} - ammendHeaders(headers, []byte("good")) - assert.Equal(t, "4", headers.Get(util.ContentLength)) + headers := http.Header{"Content-Type": []string{"application/json"}} + ammendHeaders(headers, []byte("good")) + assert.Equal(t, "4", headers.Get(util.ContentLength)) } const defaultSchemaForTest = `{"properties": { @@ -642,17 +642,17 @@ const defaultSchemaForTest = `{"properties": { }` func hasError(t *testing.T, output interface{}, err error) { - assert.NotNil(t, err) + assert.NotNil(t, err) } func noError(t *testing.T, output interface{}, err error) { - assert.Nil(t, err) + assert.Nil(t, err) } func prepareForFoo() { - gock.New(urlLocalhost). - Get("/foo").Reply(http.StatusOK).BodyString(genericBody). - SetHeader(util.ContentType, util.JSON) + gock.New(urlLocalhost). + Get("/foo").Reply(http.StatusOK).BodyString(genericBody). + SetHeader(util.ContentType, util.JSON) } //go:embed testdata/generic_response.json diff --git a/pkg/server/store_ext_manager_test.go b/pkg/server/store_ext_manager_test.go index b976d7c8..4d189821 100644 --- a/pkg/server/store_ext_manager_test.go +++ b/pkg/server/store_ext_manager_test.go @@ -1,5 +1,5 @@ /* -Copyright 2023-2024 API Testing Authors. +Copyright 2023-2025 API Testing Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,35 +16,35 @@ limitations under the License. package server import ( - "errors" - "testing" - "time" + "errors" + "testing" + "time" - fakeruntime "github.com/linuxsuren/go-fake-runtime" - "github.com/stretchr/testify/assert" + fakeruntime "github.com/linuxsuren/go-fake-runtime" + "github.com/stretchr/testify/assert" ) func TestStoreExtManager(t *testing.T) { - t.Run("not found", func(t *testing.T) { - mgr := NewStoreExtManager(fakeruntime.FakeExecer{ - ExpectLookPathError: errors.New("not found"), - }) - err := mgr.Start("fake", "") - assert.Error(t, err) - }) - - t.Run("exist executable file", func(t *testing.T) { - mgr := NewStoreExtManagerInstance(fakeruntime.FakeExecer{ - ExpectLookPath: "/usr/local/bin/go", - }) - err := mgr.Start("go", "") - assert.NoError(t, err, err) - - time.Sleep(time.Microsecond * 100) - err = mgr.Start("go", "") - assert.NoError(t, err) - - err = mgr.StopAll() - assert.NoError(t, err) - }) + t.Run("not found", func(t *testing.T) { + mgr := NewStoreExtManager(&fakeruntime.FakeExecer{ + ExpectLookPathError: errors.New("not found"), + }) + err := mgr.Start("fake", "") + assert.Error(t, err) + }) + + t.Run("exist executable file", func(t *testing.T) { + mgr := NewStoreExtManagerInstance(&fakeruntime.FakeExecer{ + ExpectLookPath: "/usr/local/bin/go", + }) + err := mgr.Start("go", "") + assert.NoError(t, err, err) + + time.Sleep(time.Microsecond * 100) + err = mgr.Start("go", "") + assert.NoError(t, err) + + err = mgr.StopAll() + assert.NoError(t, err) + }) } diff --git a/tools/github-actions/setup-deps/action.yml b/tools/github-actions/setup-deps/action.yml index 730b5c73..5dc13ae2 100644 --- a/tools/github-actions/setup-deps/action.yml +++ b/tools/github-actions/setup-deps/action.yml @@ -6,5 +6,5 @@ runs: steps: - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: - go-version: 1.22.x + go-version: 1.23.x cache: true