From 1478c6f11459cc339f91e77b8ad09abc82ad0d2f Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Sun, 12 Jan 2025 22:34:36 +0800 Subject: [PATCH 01/10] infra: add some infra Signed-off-by: yuluo-yx --- .github/workflows/check.yaml | 38 + .github/workflows/docs.yaml | 27 +- .github/workflows/license-check.yaml | 11 - .licenserc.yaml | 13 - checklink_config.json | 2 +- pkg/server/remote_server.go | 2192 ++++++++--------- pkg/testing/case.go | 356 +-- pkg/testing/case_test.go | 166 +- tools/linter/codespell/.codespell.ignorewords | 5 + tools/linter/codespell/.codespell.skip | 14 + tools/linter/license/.licenserc.yaml | 26 + .../markdownlint}/markdown_lint_config.json | 0 tools/make/docs.mk | 15 +- tools/make/golang.mk | 12 +- tools/make/lint.mk | 36 + tools/make/tools.mk | 11 + tools/src/codespell/requirements.txt | 1 + tools/src/linkinator/package.json | 5 + tools/src/markdownlint/package.json | 5 + tools/src/skywalking-eyes/go.mod | 59 + tools/src/skywalking-eyes/go.sum | 208 ++ tools/src/skywalking-eyes/pin.go | 22 + 22 files changed, 1804 insertions(+), 1420 deletions(-) create mode 100644 .github/workflows/check.yaml delete mode 100644 .github/workflows/license-check.yaml delete mode 100644 .licenserc.yaml create mode 100644 tools/linter/codespell/.codespell.ignorewords create mode 100644 tools/linter/codespell/.codespell.skip create mode 100644 tools/linter/license/.licenserc.yaml rename {.github => tools/linter/markdownlint}/markdown_lint_config.json (100%) create mode 100644 tools/src/codespell/requirements.txt create mode 100644 tools/src/linkinator/package.json create mode 100644 tools/src/markdownlint/package.json create mode 100644 tools/src/skywalking-eyes/go.mod create mode 100644 tools/src/skywalking-eyes/go.sum create mode 100644 tools/src/skywalking-eyes/pin.go diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 00000000..521eec10 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,38 @@ +name: Hugo Docs +on: + push: + branches: + - "master" + pull_request: + branches: + - "master" + +permissions: + contents: read + +jobs: + files-lint: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: ./tools/github-actions/setup-deps + - name: Setup Node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.1.0 + with: + node-version: "21" + + - name: Markdown Lint check + run: | + make lint.markdown + + - name: Yaml Lint check + run: | + make lint.yaml + + - name: Check License + run: | + make lint.checklicense + + - name: Check Code Spell + run: | + make lint.codespell diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index cda00e8e..6411007d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -14,31 +14,8 @@ permissions: contents: read jobs: - docs-lint: - runs-on: ubuntu-22.04 - steps: - - name: Check out code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - uses: ./tools/github-actions/setup-deps - - - name: Run markdown linter - uses: nosborn/github-action-markdown-cli@9b5e871c11cc0649c5ac2526af22e23525fa344d # v3.3.0 - with: - files: docs/site/content/* - config_file: ".github/markdown_lint_config.json" - - - name: Install linkinator - run: npm install -g linkinator@6.0.4 - - - name: Check links - run: make docs # docs-check-links - docs-build: runs-on: ubuntu-latest - needs: docs-lint permissions: contents: write steps: @@ -65,7 +42,9 @@ jobs: run: | cp docs/api-testing-schema.json docs/site/static/api-testing-schema.json cp docs/api-testing-mock-schema.json docs/site/static/api-testing-mock-schema.json - make docs # docs-check-links + make lint.checklinks + make lint.markdown + make docs # Upload docs for GitHub Pages - name: Upload GitHub Pages artifact diff --git a/.github/workflows/license-check.yaml b/.github/workflows/license-check.yaml deleted file mode 100644 index 751f36e6..00000000 --- a/.github/workflows/license-check.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: License check - -on: - - pull_request - -jobs: - Test: - runs-on: ubuntu-20.04 - steps: - - name: Check License Header - uses: apache/skywalking-eyes@v0.4.0 diff --git a/.licenserc.yaml b/.licenserc.yaml deleted file mode 100644 index 0e67c6eb..00000000 --- a/.licenserc.yaml +++ /dev/null @@ -1,13 +0,0 @@ -header: - license: - spdx-id: Apache License - copyright-owner: API Testing Authors - - paths-ignore: - - 'dist' - - 'licenses' - - '**/*.md' - - 'LICENSE' - - 'NOTICE' - - comment: on-failure diff --git a/checklink_config.json b/checklink_config.json index 2d836174..1c9308e0 100644 --- a/checklink_config.json +++ b/checklink_config.json @@ -7,4 +7,4 @@ "aliveStatusCodes": [ 200 ] -} \ No newline at end of file +} diff --git a/pkg/server/remote_server.go b/pkg/server/remote_server.go index 9727a547..09fa25e1 100644 --- a/pkg/server/remote_server.go +++ b/pkg/server/remote_server.go @@ -17,72 +17,72 @@ limitations under the License. package server import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "mime" - "net/http" - "os" - "path/filepath" - reflect "reflect" - "regexp" - "strconv" - "strings" - "time" - - "github.com/expr-lang/expr/builtin" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" - - "github.com/linuxsuren/api-testing/pkg/util/home" - - "github.com/linuxsuren/api-testing/pkg/mock" - - _ "embed" - - "github.com/linuxsuren/api-testing/pkg/generator" - "github.com/linuxsuren/api-testing/pkg/logging" - "github.com/linuxsuren/api-testing/pkg/oauth" - "github.com/linuxsuren/api-testing/pkg/render" - "github.com/linuxsuren/api-testing/pkg/runner" - "github.com/linuxsuren/api-testing/pkg/testing" - "github.com/linuxsuren/api-testing/pkg/util" - "github.com/linuxsuren/api-testing/pkg/version" - "github.com/linuxsuren/api-testing/sample" - - "google.golang.org/grpc/metadata" - "gopkg.in/yaml.v3" + "bytes" + "context" + "errors" + "fmt" + "io" + "mime" + "net/http" + "os" + "path/filepath" + reflect "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/expr-lang/expr/builtin" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/linuxsuren/api-testing/pkg/util/home" + + "github.com/linuxsuren/api-testing/pkg/mock" + + _ "embed" + + "github.com/linuxsuren/api-testing/pkg/generator" + "github.com/linuxsuren/api-testing/pkg/logging" + "github.com/linuxsuren/api-testing/pkg/oauth" + "github.com/linuxsuren/api-testing/pkg/render" + "github.com/linuxsuren/api-testing/pkg/runner" + "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/linuxsuren/api-testing/pkg/util" + "github.com/linuxsuren/api-testing/pkg/version" + "github.com/linuxsuren/api-testing/sample" + + "google.golang.org/grpc/metadata" + "gopkg.in/yaml.v3" ) var ( - remoteServerLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("remote_server") - GrpcMaxRecvMsgSize int + remoteServerLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("remote_server") + GrpcMaxRecvMsgSize int ) type server struct { - UnimplementedRunnerServer - loader testing.Writer - storeWriterFactory testing.StoreWriterFactory - configDir string - storeExtMgr ExtManager + UnimplementedRunnerServer + loader testing.Writer + storeWriterFactory testing.StoreWriterFactory + configDir string + storeExtMgr ExtManager - secretServer SecretServiceServer + secretServer SecretServiceServer - grpcMaxRecvMsgSize int + grpcMaxRecvMsgSize int } type SecretServiceServer interface { - GetSecrets(context.Context, *Empty) (*Secrets, error) - CreateSecret(context.Context, *Secret) (*CommonResult, error) - DeleteSecret(context.Context, *Secret) (*CommonResult, error) - UpdateSecret(context.Context, *Secret) (*CommonResult, error) + GetSecrets(context.Context, *Empty) (*Secrets, error) + CreateSecret(context.Context, *Secret) (*CommonResult, error) + DeleteSecret(context.Context, *Secret) (*CommonResult, error) + UpdateSecret(context.Context, *Secret) (*CommonResult, error) } type SecertServiceGetable interface { - GetSecret(context.Context, *Secret) (*Secret, error) + GetSecret(context.Context, *Secret) (*Secret, error) } type fakeSecretServer struct{} @@ -90,1305 +90,1305 @@ type fakeSecretServer struct{} var errNoSecretService = errors.New("no secret service found") func (f *fakeSecretServer) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } // NewRemoteServer creates a remote server instance func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, storeExtMgr ExtManager, configDir string, grpcMaxRecvMsgSize int) RunnerServer { - if secretServer == nil { - secretServer = &fakeSecretServer{} - } - GrpcMaxRecvMsgSize = grpcMaxRecvMsgSize - return &server{ - loader: loader, - storeWriterFactory: storeWriterFactory, - configDir: configDir, - secretServer: secretServer, - storeExtMgr: storeExtMgr, - grpcMaxRecvMsgSize: grpcMaxRecvMsgSize, - } + if secretServer == nil { + secretServer = &fakeSecretServer{} + } + GrpcMaxRecvMsgSize = grpcMaxRecvMsgSize + return &server{ + loader: loader, + storeWriterFactory: storeWriterFactory, + configDir: configDir, + secretServer: secretServer, + storeExtMgr: storeExtMgr, + grpcMaxRecvMsgSize: grpcMaxRecvMsgSize, + } } func withDefaultValue(old, defVal any) any { - if old == "" || old == nil { - old = defVal - } - return old + if old == "" || old == nil { + old = defVal + } + return old } func parseSuiteWithItems(data []byte) (suite *testing.TestSuite, err error) { - suite, err = testing.ParseFromData(data) - if err == nil && (suite == nil || suite.Items == nil) { - err = errNoTestSuiteFound - } - return + suite, err = testing.ParseFromData(data) + if err == nil && (suite == nil || suite.Items == nil) { + err = errNoTestSuiteFound + } + return } func (s *server) getSuiteFromTestTask(task *TestTask) (suite *testing.TestSuite, err error) { - switch task.Kind { - case "suite": - suite, err = parseSuiteWithItems([]byte(task.Data)) - case "testcase": - var testCase *testing.TestCase - if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil { - return - } - suite = &testing.TestSuite{ - Items: []testing.TestCase{*testCase}, - } - case "testcaseInSuite": - suite, err = parseSuiteWithItems([]byte(task.Data)) - if err != nil { - return - } - - var targetTestcase *testing.TestCase - for _, item := range suite.Items { - if item.Name == task.CaseName { - targetTestcase = &item - break - } - } - - if targetTestcase != nil { - parentCases := findParentTestCases(targetTestcase, suite) - remoteServerLogger.Info("find parent cases", "num", len(parentCases)) - suite.Items = append(parentCases, *targetTestcase) - } else { - err = fmt.Errorf("cannot found testcase %s", task.CaseName) - } - default: - err = fmt.Errorf("not support '%s'", task.Kind) - } - return + switch task.Kind { + case "suite": + suite, err = parseSuiteWithItems([]byte(task.Data)) + case "testcase": + var testCase *testing.TestCase + if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil { + return + } + suite = &testing.TestSuite{ + Items: []testing.TestCase{*testCase}, + } + case "testcaseInSuite": + suite, err = parseSuiteWithItems([]byte(task.Data)) + if err != nil { + return + } + + var targetTestcase *testing.TestCase + for _, item := range suite.Items { + if item.Name == task.CaseName { + targetTestcase = &item + break + } + } + + if targetTestcase != nil { + parentCases := findParentTestCases(targetTestcase, suite) + remoteServerLogger.Info("find parent cases", "num", len(parentCases)) + suite.Items = append(parentCases, *targetTestcase) + } else { + err = fmt.Errorf("cannot found testcase %s", task.CaseName) + } + default: + err = fmt.Errorf("not support '%s'", task.Kind) + } + return } func resetEnv(oldEnv map[string]string) { - for key, val := range oldEnv { - os.Setenv(key, val) - } + for key, val := range oldEnv { + os.Setenv(key, val) + } } func (s *server) getLoader(ctx context.Context) (loader testing.Writer) { - var ok bool - loader = s.loader - - var mdd metadata.MD - if mdd, ok = metadata.FromIncomingContext(ctx); ok { - storeNameMeta := mdd.Get(HeaderKeyStoreName) - if len(storeNameMeta) > 0 { - storeName := strings.TrimSpace(storeNameMeta[0]) - if storeName == "local" || storeName == "" { - return - } - - var err error - if loader, err = s.getLoaderByStoreName(storeName); err != nil { - remoteServerLogger.Info("failed to get loader", "name", storeName, "error", err) - loader = testing.NewNonWriter() - } - } - } - return + var ok bool + loader = s.loader + + var mdd metadata.MD + if mdd, ok = metadata.FromIncomingContext(ctx); ok { + storeNameMeta := mdd.Get(HeaderKeyStoreName) + if len(storeNameMeta) > 0 { + storeName := strings.TrimSpace(storeNameMeta[0]) + if storeName == "local" || storeName == "" { + return + } + + var err error + if loader, err = s.getLoaderByStoreName(storeName); err != nil { + remoteServerLogger.Info("failed to get loader", "name", storeName, "error", err) + loader = testing.NewNonWriter() + } + } + } + return } // Run start to run the test task func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, err error) { - task.Level = withDefaultValue(task.Level, "info").(string) - task.Env = withDefaultValue(task.Env, map[string]string{}).(map[string]string) - - var suite *testing.TestSuite - // TODO may not safe in multiple threads - oldEnv := map[string]string{} - for key, val := range task.Env { - oldEnv[key] = os.Getenv(key) - os.Setenv(key, val) - } - - defer func() { - resetEnv(oldEnv) - }() - - if suite, err = s.getSuiteFromTestTask(task); err != nil { - return - } - - remoteServerLogger.Info("prepare to run", "name", suite.Name, " with level: ", task.Level) - remoteServerLogger.Info("task kind to run", "kind", task.Kind, "lens", len(suite.Items)) - dataContext := map[string]interface{}{} - - if err = suite.Render(dataContext); err != nil { - reply.Error = err.Error() - err = nil - return - } - // inject the parameters from input - if len(task.Parameters) > 0 { - dataContext[testing.ContextKeyGlobalParam] = pairToMap(task.Parameters) - } - - buf := new(bytes.Buffer) - reply = &TestResult{} - - for _, testCase := range suite.Items { - suiteRunner := runner.GetTestSuiteRunner(suite) - suiteRunner.WithOutputWriter(buf) - suiteRunner.WithWriteLevel(task.Level) - suiteRunner.WithSecure(suite.Spec.Secure) - - // reuse the API prefix - testCase.Request.RenderAPI(suite.API) - historyHeader := make(map[string]string) - for k, v := range testCase.Request.Header { - historyHeader[k] = v - } - - output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx) - if getter, ok := suiteRunner.(runner.ResponseRecord); ok { - resp := getter.GetResponseRecord() - //resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name) - reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{ - StatusCode: int32(resp.StatusCode), - Body: resp.Body, - Header: mapToPair(resp.Header), - Id: testCase.ID, - Output: buf.String(), - }) - } - - if testErr == nil { - dataContext[testCase.Name] = output - } else { - reply.Error = testErr.Error() - break - } - // create history record - go func(historyHeader map[string]string) { - loader := s.getLoader(ctx) - defer loader.Close() - for _, testCaseResult := range reply.TestCaseResult { - err = loader.CreateHistoryTestCase(ToNormalTestCaseResult(testCaseResult), suite, historyHeader) - if err != nil { - remoteServerLogger.Info("error create history") - } - } - }(historyHeader) - } - - if reply.Error != "" { - fmt.Fprintln(buf, reply.Error) - } - reply.Message = buf.String() - return + task.Level = withDefaultValue(task.Level, "info").(string) + task.Env = withDefaultValue(task.Env, map[string]string{}).(map[string]string) + + var suite *testing.TestSuite + // TODO may not safe in multiple threads + oldEnv := map[string]string{} + for key, val := range task.Env { + oldEnv[key] = os.Getenv(key) + os.Setenv(key, val) + } + + defer func() { + resetEnv(oldEnv) + }() + + if suite, err = s.getSuiteFromTestTask(task); err != nil { + return + } + + remoteServerLogger.Info("prepare to run", "name", suite.Name, " with level: ", task.Level) + remoteServerLogger.Info("task kind to run", "kind", task.Kind, "lens", len(suite.Items)) + dataContext := map[string]interface{}{} + + if err = suite.Render(dataContext); err != nil { + reply.Error = err.Error() + err = nil + return + } + // inject the parameters from input + if len(task.Parameters) > 0 { + dataContext[testing.ContextKeyGlobalParam] = pairToMap(task.Parameters) + } + + buf := new(bytes.Buffer) + reply = &TestResult{} + + for _, testCase := range suite.Items { + suiteRunner := runner.GetTestSuiteRunner(suite) + suiteRunner.WithOutputWriter(buf) + suiteRunner.WithWriteLevel(task.Level) + suiteRunner.WithSecure(suite.Spec.Secure) + + // reuse the API prefix + testCase.Request.RenderAPI(suite.API) + historyHeader := make(map[string]string) + for k, v := range testCase.Request.Header { + historyHeader[k] = v + } + + output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx) + if getter, ok := suiteRunner.(runner.ResponseRecord); ok { + resp := getter.GetResponseRecord() + //resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name) + reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{ + StatusCode: int32(resp.StatusCode), + Body: resp.Body, + Header: mapToPair(resp.Header), + Id: testCase.ID, + Output: buf.String(), + }) + } + + if testErr == nil { + dataContext[testCase.Name] = output + } else { + reply.Error = testErr.Error() + break + } + // create history record + go func(historyHeader map[string]string) { + loader := s.getLoader(ctx) + defer loader.Close() + for _, testCaseResult := range reply.TestCaseResult { + err = loader.CreateHistoryTestCase(ToNormalTestCaseResult(testCaseResult), suite, historyHeader) + if err != nil { + remoteServerLogger.Info("error create history") + } + } + }(historyHeader) + } + + if reply.Error != "" { + fmt.Fprintln(buf, reply.Error) + } + reply.Message = buf.String() + return } func (s *server) BatchRun(srv Runner_BatchRunServer) (err error) { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - var in *BatchTestTask - in, err = srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - for i := 0; i < int(in.Count); i++ { - var reply *TestCaseResult - if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ - Suite: in.SuiteName, - Testcase: in.CaseName, - }); err != nil { - return - } - - if err = srv.Send(&TestResult{ - TestCaseResult: []*TestCaseResult{reply}, - Error: reply.Error, - }); err != nil { - return err - } - - var interval string - if interval, err = render.Render("batch run interval", in.Interval, nil); err != nil { - return - } - - var duration time.Duration - if duration, err = time.ParseDuration(interval); err != nil { - return - } - time.Sleep(duration) - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + var in *BatchTestTask + in, err = srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + for i := 0; i < int(in.Count); i++ { + var reply *TestCaseResult + if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ + Suite: in.SuiteName, + Testcase: in.CaseName, + }); err != nil { + return + } + + if err = srv.Send(&TestResult{ + TestCaseResult: []*TestCaseResult{reply}, + Error: reply.Error, + }); err != nil { + return err + } + + var interval string + if interval, err = render.Render("batch run interval", in.Interval, nil); err != nil { + return + } + + var duration time.Duration + if duration, err = time.ParseDuration(interval); err != nil { + return + } + time.Sleep(duration) + } + } + } } func (s *server) DownloadResponseFile(ctx context.Context, in *TestCase) (reply *FileData, err error) { - if in.Response != nil { - tempFileName := in.Response.Body - if tempFileName == "" { - return nil, errors.New("file name is empty") - } - - tempDir := os.TempDir() - filePath := filepath.Join(tempDir, tempFileName) - if filepath.Clean(filePath) != filepath.Join(tempDir, filepath.Base(tempFileName)) { - return nil, errors.New("invalid file path") - } - - fmt.Println("get file from", filePath) - fileContent, err := os.ReadFile(filePath) - if err != nil { - return nil, fmt.Errorf("failed to read file: %s", filePath) - } - - mimeType := mime.TypeByExtension(filepath.Ext(filePath)) - if mimeType == "" { - mimeType = "application/octet-stream" - } - - filename := filepath.Base(filePath) - // try to get the original filename - var originalFileName []byte - if originalFileName, err = os.ReadFile(filePath + "name"); err == nil && len(originalFileName) > 0 { - filename = string(originalFileName) - } - - reply = &FileData{ - Data: fileContent, - ContentType: mimeType, - Filename: filename, - } - - return reply, nil - } else { - return reply, errors.New("response is empty") - } + if in.Response != nil { + tempFileName := in.Response.Body + if tempFileName == "" { + return nil, errors.New("file name is empty") + } + + tempDir := os.TempDir() + filePath := filepath.Join(tempDir, tempFileName) + if filepath.Clean(filePath) != filepath.Join(tempDir, filepath.Base(tempFileName)) { + return nil, errors.New("invalid file path") + } + + fmt.Println("get file from", filePath) + fileContent, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read file: %s", filePath) + } + + mimeType := mime.TypeByExtension(filepath.Ext(filePath)) + if mimeType == "" { + mimeType = "application/octet-stream" + } + + filename := filepath.Base(filePath) + // try to get the original filename + var originalFileName []byte + if originalFileName, err = os.ReadFile(filePath + "name"); err == nil && len(originalFileName) > 0 { + filename = string(originalFileName) + } + + reply = &FileData{ + Data: fileContent, + ContentType: mimeType, + Filename: filename, + } + + return reply, nil + } else { + return reply, errors.New("response is empty") + } } func (s *server) RunTestSuite(srv Runner_RunTestSuiteServer) (err error) { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - var in *TestSuiteIdentity - in, err = srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - var suite *Suite - if suite, err = s.ListTestCase(ctx, in); err != nil { - return - } - - for _, item := range suite.Items { - var reply *TestCaseResult - if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ - Suite: in.Name, - Testcase: item.Name, - }); err != nil { - return - } - - if err = srv.Send(&TestResult{ - TestCaseResult: []*TestCaseResult{reply}, - Error: reply.Error, - }); err != nil { - return err - } - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + var in *TestSuiteIdentity + in, err = srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + var suite *Suite + if suite, err = s.ListTestCase(ctx, in); err != nil { + return + } + + for _, item := range suite.Items { + var reply *TestCaseResult + if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ + Suite: in.Name, + Testcase: item.Name, + }); err != nil { + return + } + + if err = srv.Send(&TestResult{ + TestCaseResult: []*TestCaseResult{reply}, + Error: reply.Error, + }); err != nil { + return err + } + } + } + } } // GetVersion returns the version func (s *server) GetVersion(ctx context.Context, in *Empty) (reply *Version, err error) { - reply = &Version{ - Version: version.GetVersion(), - Date: version.GetDate(), - Commit: version.GetCommit(), - } - return + reply = &Version{ + Version: version.GetVersion(), + Date: version.GetDate(), + Commit: version.GetCommit(), + } + return } func (s *server) GetSuites(ctx context.Context, in *Empty) (reply *Suites, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &Suites{ - Data: make(map[string]*Items), - } - - var suites []testing.TestSuite - if suites, err = loader.ListTestSuite(); err == nil && suites != nil { - for _, suite := range suites { - items := &Items{} - for _, item := range suite.Items { - items.Data = append(items.Data, item.Name) - } - items.Kind = suite.Spec.Kind - reply.Data[suite.Name] = items - } - } - - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &Suites{ + Data: make(map[string]*Items), + } + + var suites []testing.TestSuite + if suites, err = loader.ListTestSuite(); err == nil && suites != nil { + for _, suite := range suites { + items := &Items{} + for _, item := range suite.Items { + items.Data = append(items.Data, item.Name) + } + items.Kind = suite.Spec.Kind + reply.Data[suite.Name] = items + } + } + + return } func (s *server) GetHistorySuites(ctx context.Context, in *Empty) (reply *HistorySuites, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HistorySuites{ - Data: make(map[string]*HistoryItems), - } - - var suites []testing.HistoryTestSuite - if suites, err = loader.ListHistoryTestSuite(); err == nil && suites != nil { - for _, suite := range suites { - items := &HistoryItems{} - for _, item := range suite.Items { - data := &HistoryCaseIdentity{ - ID: item.ID, - HistorySuiteName: item.HistorySuiteName, - Kind: item.SuiteSpec.Kind, - Suite: item.SuiteName, - Testcase: item.CaseName, - } - items.Data = append(items.Data, data) - } - reply.Data[suite.HistorySuiteName] = items - } - } - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HistorySuites{ + Data: make(map[string]*HistoryItems), + } + + var suites []testing.HistoryTestSuite + if suites, err = loader.ListHistoryTestSuite(); err == nil && suites != nil { + for _, suite := range suites { + items := &HistoryItems{} + for _, item := range suite.Items { + data := &HistoryCaseIdentity{ + ID: item.ID, + HistorySuiteName: item.HistorySuiteName, + Kind: item.SuiteSpec.Kind, + Suite: item.SuiteName, + Testcase: item.CaseName, + } + items.Data = append(items.Data, data) + } + reply.Data[suite.HistorySuiteName] = items + } + } + return } func (s *server) CreateTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - if loader == nil { - reply.Error = "no loader found" - } else { - if err = loader.CreateSuite(in.Name, in.Api); err == nil { - toUpdate := testing.TestSuite{ - Name: in.Name, - API: in.Api, - Spec: testing.APISpec{ - Kind: in.Kind, - }, - } - - switch strings.ToLower(in.Kind) { - case "grpc", "trpc": - toUpdate.Spec.RPC = &testing.RPCDesc{} - } - - err = loader.UpdateSuite(toUpdate) - } - } - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + if loader == nil { + reply.Error = "no loader found" + } else { + if err = loader.CreateSuite(in.Name, in.Api); err == nil { + toUpdate := testing.TestSuite{ + Name: in.Name, + API: in.Api, + Spec: testing.APISpec{ + Kind: in.Kind, + }, + } + + switch strings.ToLower(in.Kind) { + case "grpc", "trpc": + toUpdate.Spec.RPC = &testing.RPCDesc{} + } + + err = loader.UpdateSuite(toUpdate) + } + } + return } func (s *server) ImportTestSuite(ctx context.Context, in *TestSuiteSource) (result *CommonResult, err error) { - result = &CommonResult{} - var dataImporter generator.Importer - switch in.Kind { - case "postman": - dataImporter = generator.NewPostmanImporter() - case "native", "": - dataImporter = generator.NewNativeImporter() - default: - result.Success = false - result.Message = fmt.Sprintf("not support kind: %s", in.Kind) - return - } - - remoteServerLogger.Logger.Info("import test suite", "kind", in.Kind, "url", in.Url) - var suite *testing.TestSuite - if in.Url != "" { - suite, err = dataImporter.ConvertFromURL(in.Url) - } else if in.Data != "" { - suite, err = dataImporter.Convert([]byte(in.Data)) - } else { - err = errors.New("url or data is required") - } - - if err != nil { - result.Success = false - result.Message = err.Error() - return - } - - loader := s.getLoader(ctx) - defer loader.Close() - - if err = loader.CreateSuite(suite.Name, suite.API); err != nil { - return - } - - for _, item := range suite.Items { - if err = loader.CreateTestCase(suite.Name, item); err != nil { - break - } - } - result.Success = true - return + result = &CommonResult{} + var dataImporter generator.Importer + switch in.Kind { + case "postman": + dataImporter = generator.NewPostmanImporter() + case "native", "": + dataImporter = generator.NewNativeImporter() + default: + result.Success = false + result.Message = fmt.Sprintf("not support kind: %s", in.Kind) + return + } + + remoteServerLogger.Logger.Info("import test suite", "kind", in.Kind, "url", in.Url) + var suite *testing.TestSuite + if in.Url != "" { + suite, err = dataImporter.ConvertFromURL(in.Url) + } else if in.Data != "" { + suite, err = dataImporter.Convert([]byte(in.Data)) + } else { + err = errors.New("url or data is required") + } + + if err != nil { + result.Success = false + result.Message = err.Error() + return + } + + loader := s.getLoader(ctx) + defer loader.Close() + + if err = loader.CreateSuite(suite.Name, suite.API); err != nil { + return + } + + for _, item := range suite.Items { + if err = loader.CreateTestCase(suite.Name, item); err != nil { + break + } + } + result.Success = true + return } func (s *server) GetTestSuite(ctx context.Context, in *TestSuiteIdentity) (result *TestSuite, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - var suite *testing.TestSuite - if suite, _, err = loader.GetSuite(in.Name); err == nil && suite != nil { - result = ToGRPCSuite(suite) - } - return + loader := s.getLoader(ctx) + defer loader.Close() + var suite *testing.TestSuite + if suite, _, err = loader.GetSuite(in.Name); err == nil && suite != nil { + result = ToGRPCSuite(suite) + } + return } func (s *server) UpdateTestSuite(ctx context.Context, in *TestSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.UpdateSuite(*ToNormalSuite(in)) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.UpdateSuite(*ToNormalSuite(in)) + return } func (s *server) DeleteTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.DeleteSuite(in.Name) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.DeleteSuite(in.Name) + return } func (s *server) DuplicateTestSuite(ctx context.Context, in *TestSuiteDuplicate) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - - if in.SourceSuiteName == in.TargetSuiteName { - reply.Error = "source and target suite name should be different" - return - } - - var suite testing.TestSuite - if suite, err = loader.GetTestSuite(in.SourceSuiteName, true); err == nil { - suite.Name = in.TargetSuiteName - if err = loader.CreateSuite(suite.Name, suite.API); err == nil { - for _, testCase := range suite.Items { - if err = loader.CreateTestCase(suite.Name, testCase); err != nil { - break - } - } - } - } - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + + if in.SourceSuiteName == in.TargetSuiteName { + reply.Error = "source and target suite name should be different" + return + } + + var suite testing.TestSuite + if suite, err = loader.GetTestSuite(in.SourceSuiteName, true); err == nil { + suite.Name = in.TargetSuiteName + if err = loader.CreateSuite(suite.Name, suite.API); err == nil { + for _, testCase := range suite.Items { + if err = loader.CreateTestCase(suite.Name, testCase); err != nil { + break + } + } + } + } + return } func (s *server) RenameTestSuite(ctx context.Context, in *TestSuiteDuplicate) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.RenameTestSuite(in.SourceSuiteName, in.TargetSuiteName) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.RenameTestSuite(in.SourceSuiteName, in.TargetSuiteName) + return } func (s *server) ListTestCase(ctx context.Context, in *TestSuiteIdentity) (result *Suite, err error) { - var items []testing.TestCase - loader := s.getLoader(ctx) - defer loader.Close() - if items, err = loader.ListTestCase(in.Name); err == nil { - result = &Suite{} - for _, item := range items { - result.Items = append(result.Items, ToGRPCTestCase(item)) - } - } - return + var items []testing.TestCase + loader := s.getLoader(ctx) + defer loader.Close() + if items, err = loader.ListTestCase(in.Name); err == nil { + result = &Suite{} + for _, item := range items { + result.Items = append(result.Items, ToGRPCTestCase(item)) + } + } + return } func (s *server) GetTestSuiteYaml(ctx context.Context, in *TestSuiteIdentity) (reply *YamlData, err error) { - var data []byte - loader := s.getLoader(ctx) - defer loader.Close() - if data, err = loader.GetTestSuiteYaml(in.Name); err == nil { - reply = &YamlData{ - Data: data, - } - } - return + var data []byte + loader := s.getLoader(ctx) + defer loader.Close() + if data, err = loader.GetTestSuiteYaml(in.Name); err == nil { + reply = &YamlData{ + Data: data, + } + } + return } func (s *server) GetTestCase(ctx context.Context, in *TestCaseIdentity) (reply *TestCase, err error) { - var result testing.TestCase - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetTestCase(in.Suite, in.Testcase); err == nil { - reply = ToGRPCTestCase(result) + var result testing.TestCase + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetTestCase(in.Suite, in.Testcase); err == nil { + reply = ToGRPCTestCase(result) - var suite testing.TestSuite - if suite, err = loader.GetTestSuite(in.Suite, false); err == nil { - reply.Server = suite.API - } - } - return + var suite testing.TestSuite + if suite, err = loader.GetTestSuite(in.Suite, false); err == nil { + reply.Server = suite.API + } + } + return } func (s *server) GetHistoryTestCaseWithResult(ctx context.Context, in *HistoryTestCase) (reply *HistoryTestResult, err error) { - var result testing.HistoryTestResult - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetHistoryTestCaseWithResult(in.ID); err == nil { - reply = ToGRPCHistoryTestCaseResult(result) - } - return + var result testing.HistoryTestResult + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetHistoryTestCaseWithResult(in.ID); err == nil { + reply = ToGRPCHistoryTestCaseResult(result) + } + return } func (s *server) GetHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HistoryTestCase, err error) { - var result testing.HistoryTestCase - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetHistoryTestCase(in.ID); err == nil { - reply = ConvertToGRPCHistoryTestCase(result) - } - return + var result testing.HistoryTestCase + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetHistoryTestCase(in.ID); err == nil { + reply = ConvertToGRPCHistoryTestCase(result) + } + return } var ExecutionCountNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_count", - Help: "The total number of request execution", + Name: "atest_execution_count", + Help: "The total number of request execution", }) var ExecutionSuccessNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_success", - Help: "The total number of request execution success", + Name: "atest_execution_success", + Help: "The total number of request execution success", }) var ExecutionFailNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_fail", - Help: "The total number of request execution fail", + Name: "atest_execution_fail", + Help: "The total number of request execution fail", }) func (s *server) GetTestCaseAllHistory(ctx context.Context, in *TestCase) (result *HistoryTestCases, err error) { - var items []testing.HistoryTestCase - loader := s.getLoader(ctx) - defer loader.Close() - if items, err = loader.GetTestCaseAllHistory(in.SuiteName, in.Name); err == nil { - result = &HistoryTestCases{} - for _, item := range items { - result.Data = append(result.Data, ConvertToGRPCHistoryTestCase(item)) - } - } - return + var items []testing.HistoryTestCase + loader := s.getLoader(ctx) + defer loader.Close() + if items, err = loader.GetTestCaseAllHistory(in.SuiteName, in.Name); err == nil { + result = &HistoryTestCases{} + for _, item := range items { + result.Data = append(result.Data, ConvertToGRPCHistoryTestCase(item)) + } + } + return } func (s *server) RunTestCase(ctx context.Context, in *TestCaseIdentity) (result *TestCaseResult, err error) { - var targetTestSuite testing.TestSuite - ExecutionCountNum.Inc() - defer func() { - if result.Error == "" { - ExecutionSuccessNum.Inc() - } else { - ExecutionFailNum.Inc() - } - }() - - result = &TestCaseResult{} - loader := s.getLoader(ctx) - defer loader.Close() - targetTestSuite, err = loader.GetTestSuite(in.Suite, true) - if err != nil || targetTestSuite.Name == "" { - err = nil - result.Error = fmt.Sprintf("not found suite: %s", in.Suite) - return - } - - var data []byte - if data, err = yaml.Marshal(targetTestSuite); err == nil { - task := &TestTask{ - Kind: "testcaseInSuite", - Data: string(data), - CaseName: in.Testcase, - Level: "debug", - Parameters: in.Parameters, - } - - var reply *TestResult - var lastItem *TestCaseResult - if reply, err = s.Run(ctx, task); err == nil && len(reply.TestCaseResult) > 0 { - lastIndex := len(reply.TestCaseResult) - 1 - lastItem = reply.TestCaseResult[lastIndex] - - if len(lastItem.Body) > GrpcMaxRecvMsgSize { - e := "the HTTP response body exceeded the maximum message size limit received by the gRPC client" - result = &TestCaseResult{ - Output: reply.Message, - Error: e, - Body: "", - Header: lastItem.Header, - StatusCode: http.StatusOK, - } - return - } - - result = &TestCaseResult{ - Output: reply.Message, - Error: reply.Error, - Body: lastItem.Body, - Header: lastItem.Header, - StatusCode: lastItem.StatusCode, - } - } else if err != nil { - result.Error = err.Error() - } else { - result = &TestCaseResult{ - Output: reply.Message, - Error: reply.Error, - } - } - - if reply != nil { - result.Output = reply.Message - result.Error = reply.Error - } - if lastItem != nil { - result.Body = lastItem.Body - result.Header = lastItem.Header - result.StatusCode = lastItem.StatusCode - } - } - return + var targetTestSuite testing.TestSuite + ExecutionCountNum.Inc() + defer func() { + if result.Error == "" { + ExecutionSuccessNum.Inc() + } else { + ExecutionFailNum.Inc() + } + }() + + result = &TestCaseResult{} + loader := s.getLoader(ctx) + defer loader.Close() + targetTestSuite, err = loader.GetTestSuite(in.Suite, true) + if err != nil || targetTestSuite.Name == "" { + err = nil + result.Error = fmt.Sprintf("not found suite: %s", in.Suite) + return + } + + var data []byte + if data, err = yaml.Marshal(targetTestSuite); err == nil { + task := &TestTask{ + Kind: "testcaseInSuite", + Data: string(data), + CaseName: in.Testcase, + Level: "debug", + Parameters: in.Parameters, + } + + var reply *TestResult + var lastItem *TestCaseResult + if reply, err = s.Run(ctx, task); err == nil && len(reply.TestCaseResult) > 0 { + lastIndex := len(reply.TestCaseResult) - 1 + lastItem = reply.TestCaseResult[lastIndex] + + if len(lastItem.Body) > GrpcMaxRecvMsgSize { + e := "the HTTP response body exceeded the maximum message size limit received by the gRPC client" + result = &TestCaseResult{ + Output: reply.Message, + Error: e, + Body: "", + Header: lastItem.Header, + StatusCode: http.StatusOK, + } + return + } + + result = &TestCaseResult{ + Output: reply.Message, + Error: reply.Error, + Body: lastItem.Body, + Header: lastItem.Header, + StatusCode: lastItem.StatusCode, + } + } else if err != nil { + result.Error = err.Error() + } else { + result = &TestCaseResult{ + Output: reply.Message, + Error: reply.Error, + } + } + + if reply != nil { + result.Output = reply.Message + result.Error = reply.Error + } + if lastItem != nil { + result.Body = lastItem.Body + result.Header = lastItem.Header + result.StatusCode = lastItem.StatusCode + } + } + return } func mapInterToPair(data map[string]interface{}) (pairs []*Pair) { - pairs = make([]*Pair, 0) - for k, v := range data { - pairs = append(pairs, &Pair{ - Key: k, - Value: fmt.Sprintf("%v", v), - }) - } - return + pairs = make([]*Pair, 0) + for k, v := range data { + pairs = append(pairs, &Pair{ + Key: k, + Value: fmt.Sprintf("%v", v), + }) + } + return } func mapToPair(data map[string]string) (pairs []*Pair) { - pairs = make([]*Pair, 0) - for k, v := range data { - pairs = append(pairs, &Pair{ - Key: k, - Value: v, - }) - } - return + pairs = make([]*Pair, 0) + for k, v := range data { + pairs = append(pairs, &Pair{ + Key: k, + Value: v, + }) + } + return } func pairToInterMap(pairs []*Pair) (data map[string]interface{}) { - data = make(map[string]interface{}) - for _, pair := range pairs { - if pair.Key == "" { - continue - } - data[pair.Key] = pair.Value - } - return + data = make(map[string]interface{}) + for _, pair := range pairs { + if pair.Key == "" { + continue + } + data[pair.Key] = pair.Value + } + return } func pairToMap(pairs []*Pair) (data map[string]string) { - data = make(map[string]string) - for _, pair := range pairs { - if pair.Key == "" { - continue - } - data[pair.Key] = pair.Value - } - return + data = make(map[string]string) + for _, pair := range pairs { + if pair.Key == "" { + continue + } + data[pair.Key] = pair.Value + } + return } func convertConditionalVerify(verify []*ConditionalVerify) (result []testing.ConditionalVerify) { - if verify != nil { - result = make([]testing.ConditionalVerify, 0) + if verify != nil { + result = make([]testing.ConditionalVerify, 0) - for _, item := range verify { - result = append(result, testing.ConditionalVerify{ - Condition: item.Condition, - Verify: item.Verify, - }) - } - } - return + for _, item := range verify { + result = append(result, testing.ConditionalVerify{ + Condition: item.Condition, + Verify: item.Verify, + }) + } + } + return } func (s *server) CreateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - if in.Data == nil { - err = errors.New("data is required") - } else { - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.CreateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) - } - return + reply = &HelloReply{} + if in.Data == nil { + err = errors.New("data is required") + } else { + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.CreateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) + } + return } func (s *server) UpdateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - if in.Data == nil { - err = errors.New("data is required") - return - } - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.UpdateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) - return + reply = &HelloReply{} + if in.Data == nil { + err = errors.New("data is required") + return + } + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.UpdateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) + return } func (s *server) DeleteTestCase(ctx context.Context, in *TestCaseIdentity) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteTestCase(in.Suite, in.Testcase) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteTestCase(in.Suite, in.Testcase) + return } func (s *server) DeleteHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteHistoryTestCase(in.ID) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteHistoryTestCase(in.ID) + return } func (s *server) DeleteAllHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteAllHistoryTestCase(in.SuiteName, in.CaseName) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteAllHistoryTestCase(in.SuiteName, in.CaseName) + return } func (s *server) DuplicateTestCase(ctx context.Context, in *TestCaseDuplicate) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} - if in.SourceCaseName == in.TargetCaseName { - reply.Error = "source and target case name should be different" - return - } + if in.SourceCaseName == in.TargetCaseName { + reply.Error = "source and target case name should be different" + return + } - var testcase testing.TestCase - if testcase, err = loader.GetTestCase(in.SourceSuiteName, in.SourceCaseName); err == nil { - testcase.Name = in.TargetCaseName - err = loader.CreateTestCase(in.TargetSuiteName, testcase) - } - return + var testcase testing.TestCase + if testcase, err = loader.GetTestCase(in.SourceSuiteName, in.SourceCaseName); err == nil { + testcase.Name = in.TargetCaseName + err = loader.CreateTestCase(in.TargetSuiteName, testcase) + } + return } func (s *server) RenameTestCase(ctx context.Context, in *TestCaseDuplicate) (result *HelloReply, err error) { - result = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.RenameTestCase(in.SourceSuiteName, in.SourceCaseName, in.TargetCaseName) - return + result = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.RenameTestCase(in.SourceSuiteName, in.SourceCaseName, in.TargetCaseName) + return } // code generator func (s *server) ListCodeGenerator(ctx context.Context, in *Empty) (reply *SimpleList, err error) { - reply = &SimpleList{} + reply = &SimpleList{} - generators := generator.GetCodeGenerators() - for name := range generators { - reply.Data = append(reply.Data, &Pair{ - Key: name, - }) - } - return + generators := generator.GetCodeGenerators() + for name := range generators { + reply.Data = append(reply.Data, &Pair{ + Key: name, + }) + } + return } func (s *server) GenerateCode(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - instance := generator.GetCodeGenerator(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) - } else { - var result testing.TestCase - var suite testing.TestSuite - - loader := s.getLoader(ctx) - if suite, err = loader.GetTestSuite(in.TestSuite, true); err != nil { - return - } - - dataContext := map[string]interface{}{} - if err = suite.Render(dataContext); err != nil { - return - } - - var output string - var genErr error - if in.TestCase == "" { - output, genErr = instance.Generate(&suite, nil) - } else { - if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil { - result.Request.RenderAPI(suite.API) - - output, genErr = instance.Generate(&suite, &result) - } - } - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - return + reply = &CommonResult{} + instance := generator.GetCodeGenerator(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) + } else { + var result testing.TestCase + var suite testing.TestSuite + + loader := s.getLoader(ctx) + if suite, err = loader.GetTestSuite(in.TestSuite, true); err != nil { + return + } + + dataContext := map[string]interface{}{} + if err = suite.Render(dataContext); err != nil { + return + } + + var output string + var genErr error + if in.TestCase == "" { + output, genErr = instance.Generate(&suite, nil) + } else { + if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil { + result.Request.RenderAPI(suite.API) + + output, genErr = instance.Generate(&suite, &result) + } + } + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + return } func (s *server) HistoryGenerateCode(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - instance := generator.GetCodeGenerator(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) - } else { - loader := s.getLoader(ctx) - var result testing.HistoryTestCase - result, err = loader.GetHistoryTestCase(in.ID) - var testCase testing.TestCase - var suite testing.TestSuite - testCase = result.Data - suite.Name = result.SuiteName - suite.API = result.SuiteAPI - suite.Spec = result.SuiteSpec - suite.Param = result.SuiteParam - - output, genErr := instance.Generate(&suite, &testCase) - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - return + reply = &CommonResult{} + instance := generator.GetCodeGenerator(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) + } else { + loader := s.getLoader(ctx) + var result testing.HistoryTestCase + result, err = loader.GetHistoryTestCase(in.ID) + var testCase testing.TestCase + var suite testing.TestSuite + testCase = result.Data + suite.Name = result.SuiteName + suite.API = result.SuiteAPI + suite.Spec = result.SuiteSpec + suite.Param = result.SuiteParam + + output, genErr := instance.Generate(&suite, &testCase) + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + return } // converter func (s *server) ListConverter(ctx context.Context, in *Empty) (reply *SimpleList, err error) { - reply = &SimpleList{} - converters := generator.GetTestSuiteConverters() - for name := range converters { - reply.Data = append(reply.Data, &Pair{ - Key: name, - }) - } - return + reply = &SimpleList{} + converters := generator.GetTestSuiteConverters() + for name := range converters { + reply.Data = append(reply.Data, &Pair{ + Key: name, + }) + } + return } func (s *server) ConvertTestSuite(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - - instance := generator.GetTestSuiteConverter(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("converter '%s' not found", in.Generator) - } else { - var result testing.TestSuite - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetTestSuite(in.TestSuite, true); err == nil { - output, genErr := instance.Convert(&result) - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - } - return + reply = &CommonResult{} + + instance := generator.GetTestSuiteConverter(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("converter '%s' not found", in.Generator) + } else { + var result testing.TestSuite + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetTestSuite(in.TestSuite, true); err == nil { + output, genErr := instance.Convert(&result) + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + } + return } // Sample returns a sample of the test task func (s *server) Sample(ctx context.Context, in *Empty) (reply *HelloReply, err error) { - reply = &HelloReply{Message: sample.TestSuiteGitLab} - return + reply = &HelloReply{Message: sample.TestSuiteGitLab} + return } // PopularHeaders returns a list of popular headers func (s *server) PopularHeaders(ctx context.Context, in *Empty) (pairs *Pairs, err error) { - pairs = &Pairs{ - Data: []*Pair{}, - } + pairs = &Pairs{ + Data: []*Pair{}, + } - err = yaml.Unmarshal(popularHeaders, &pairs.Data) - return + err = yaml.Unmarshal(popularHeaders, &pairs.Data) + return } // GetSuggestedAPIs returns a list of suggested APIs func (s *server) GetSuggestedAPIs(ctx context.Context, in *TestSuiteIdentity) (reply *TestCases, err error) { - reply = &TestCases{} + reply = &TestCases{} - var suite *testing.TestSuite - loader := s.getLoader(ctx) - defer loader.Close() - if suite, _, err = loader.GetSuite(in.Name); err != nil || suite == nil { - return - } + var suite *testing.TestSuite + loader := s.getLoader(ctx) + defer loader.Close() + if suite, _, err = loader.GetSuite(in.Name); err != nil || suite == nil { + return + } - remoteServerLogger.Info("Finding APIs from", "name", in.Name, "with loader", reflect.TypeOf(loader)) + remoteServerLogger.Info("Finding APIs from", "name", in.Name, "with loader", reflect.TypeOf(loader)) - suiteRunner := runner.GetTestSuiteRunner(suite) - var result []*testing.TestCase - if result, err = suiteRunner.GetSuggestedAPIs(suite, in.Api); err == nil && result != nil { - for i := range result { - reply.Data = append(reply.Data, ToGRPCTestCase(*result[i])) - } - } - return + suiteRunner := runner.GetTestSuiteRunner(suite) + var result []*testing.TestCase + if result, err = suiteRunner.GetSuggestedAPIs(suite, in.Api); err == nil && result != nil { + for i := range result { + reply.Data = append(reply.Data, ToGRPCTestCase(*result[i])) + } + } + return } // FunctionsQuery returns a list of functions func (s *server) FunctionsQuery(ctx context.Context, in *SimpleQuery) (reply *Pairs, err error) { - reply = &Pairs{} - in.Name = strings.ToLower(in.Name) - - if in.Kind == "verify" { - for _, fn := range builtin.Builtins { - lowerName := strings.ToLower(fn.Name) - if in.Name == "" || strings.Contains(lowerName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: fn.Name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn.Func)), - }) - } - } - } else { - for name, fn := range render.FuncMap() { - lowerName := strings.ToLower(name) - if in.Name == "" || strings.Contains(lowerName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), - Description: render.FuncUsage(name), - }) - } - } - } - return + reply = &Pairs{} + in.Name = strings.ToLower(in.Name) + + if in.Kind == "verify" { + for _, fn := range builtin.Builtins { + lowerName := strings.ToLower(fn.Name) + if in.Name == "" || strings.Contains(lowerName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: fn.Name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn.Func)), + }) + } + } + } else { + for name, fn := range render.FuncMap() { + lowerName := strings.ToLower(name) + if in.Name == "" || strings.Contains(lowerName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), + Description: render.FuncUsage(name), + }) + } + } + } + return } // FunctionsQueryStream works like FunctionsQuery but is implemented in bidirectional streaming func (s *server) FunctionsQueryStream(srv Runner_FunctionsQueryStreamServer) error { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - in, err := srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - reply := &Pairs{} - in.Name = strings.ToLower(in.Name) - - for name, fn := range render.FuncMap() { - lowerCaseName := strings.ToLower(name) - if in.Name == "" || strings.Contains(lowerCaseName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), - }) - } - } - if err := srv.Send(reply); err != nil { - return err - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + in, err := srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + reply := &Pairs{} + in.Name = strings.ToLower(in.Name) + + for name, fn := range render.FuncMap() { + lowerCaseName := strings.ToLower(name) + if in.Name == "" || strings.Contains(lowerCaseName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), + }) + } + } + if err := srv.Send(reply); err != nil { + return err + } + } + } } func (s *server) GetStoreKinds(context.Context, *Empty) (kinds *StoreKinds, err error) { - storeFactory := testing.NewStoreFactory(s.configDir) - var stores []testing.StoreKind - if stores, err = storeFactory.GetStoreKinds(); err == nil { - kinds = &StoreKinds{} - for _, store := range stores { - kinds.Data = append(kinds.Data, &StoreKind{ - Name: store.Name, - Enabled: store.Enabled, - Url: store.URL, - }) - } - } - return + storeFactory := testing.NewStoreFactory(s.configDir) + var stores []testing.StoreKind + if stores, err = storeFactory.GetStoreKinds(); err == nil { + kinds = &StoreKinds{} + for _, store := range stores { + kinds.Data = append(kinds.Data, &StoreKind{ + Name: store.Name, + Enabled: store.Enabled, + Url: store.URL, + }) + } + } + return } func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err error) { - user := oauth.GetUserFromContext(ctx) - storeFactory := testing.NewStoreFactory(s.configDir) - var stores []testing.Store - var owner string - if user != nil { - owner = user.Name - } - if stores, err = storeFactory.GetStoresByOwner(owner); err == nil { - reply = &Stores{ - Data: make([]*Store, 0), - } - for _, item := range stores { - grpcStore := ToGRPCStore(item) - - storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name}) - grpcStore.Ready = sErr == nil && storeStatus.Ready - grpcStore.ReadOnly = storeStatus.ReadOnly - grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason - - reply.Data = append(reply.Data, grpcStore) - } - reply.Data = append(reply.Data, &Store{ - Name: "local", - Kind: &StoreKind{}, - Ready: true, - }) - } - return + user := oauth.GetUserFromContext(ctx) + storeFactory := testing.NewStoreFactory(s.configDir) + var stores []testing.Store + var owner string + if user != nil { + owner = user.Name + } + if stores, err = storeFactory.GetStoresByOwner(owner); err == nil { + reply = &Stores{ + Data: make([]*Store, 0), + } + for _, item := range stores { + grpcStore := ToGRPCStore(item) + + storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name}) + grpcStore.Ready = sErr == nil && storeStatus.Ready + grpcStore.ReadOnly = storeStatus.ReadOnly + grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason + + reply.Data = append(reply.Data, grpcStore) + } + reply.Data = append(reply.Data, &Store{ + Name: "local", + Kind: &StoreKind{}, + Ready: true, + }) + } + return } func (s *server) CreateStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - user := oauth.GetUserFromContext(ctx) - if user != nil { - in.Owner = user.Name - } + reply = &Store{} + user := oauth.GetUserFromContext(ctx) + if user != nil { + in.Owner = user.Name + } - storeFactory := testing.NewStoreFactory(s.configDir) - store := ToNormalStore(in) + storeFactory := testing.NewStoreFactory(s.configDir) + store := ToNormalStore(in) - if store.Kind.URL == "" { - store.Kind.URL = fmt.Sprintf("unix://%s", home.GetExtensionSocketPath(store.Kind.Name)) - } + if store.Kind.URL == "" { + store.Kind.URL = fmt.Sprintf("unix://%s", home.GetExtensionSocketPath(store.Kind.Name)) + } - if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil { - err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) - } - return + if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil { + err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) + } + return } func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - storeFactory := testing.NewStoreFactory(s.configDir) - store := ToNormalStore(in) - if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil { - // TODO need to restart extension if config was changed - err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) - } - return + reply = &Store{} + storeFactory := testing.NewStoreFactory(s.configDir) + store := ToNormalStore(in) + if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil { + // TODO need to restart extension if config was changed + err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) + } + return } func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - storeFactory := testing.NewStoreFactory(s.configDir) - err = storeFactory.DeleteStore(in.Name) - return + reply = &Store{} + storeFactory := testing.NewStoreFactory(s.configDir) + err = storeFactory.DeleteStore(in.Name) + return } func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *ExtensionStatus, err error) { - reply = &ExtensionStatus{} - var loader testing.Writer - if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil { - readOnly, verifyErr := loader.Verify() - reply.Ready = verifyErr == nil - reply.ReadOnly = readOnly - reply.Message = util.OKOrErrorMessage(verifyErr) - } - return + reply = &ExtensionStatus{} + var loader testing.Writer + if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil { + readOnly, verifyErr := loader.Verify() + reply.Ready = verifyErr == nil + reply.ReadOnly = readOnly + reply.Message = util.OKOrErrorMessage(verifyErr) + } + return } // secret related interfaces func (s *server) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) { - return s.secretServer.GetSecrets(ctx, in) + return s.secretServer.GetSecrets(ctx, in) } func (s *server) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.CreateSecret(ctx, in) + return s.secretServer.CreateSecret(ctx, in) } func (s *server) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.DeleteSecret(ctx, in) + return s.secretServer.DeleteSecret(ctx, in) } func (s *server) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.UpdateSecret(ctx, in) + return s.secretServer.UpdateSecret(ctx, in) } func (s *server) PProf(ctx context.Context, in *PProfRequest) (reply *PProfData, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &PProfData{ - Data: loader.PProf(in.Name), - } - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &PProfData{ + Data: loader.PProf(in.Name), + } + return } // implement the mock server // Start starts the mock server type mockServerController struct { - UnimplementedMockServer - mockWriter mock.ReaderAndWriter - loader mock.Loadable - reader mock.Reader - prefix string - combinePort int + UnimplementedMockServer + mockWriter mock.ReaderAndWriter + loader mock.Loadable + reader mock.Reader + prefix string + combinePort int } func NewMockServerController(mockWriter mock.ReaderAndWriter, loader mock.Loadable, combinePort int) MockServer { - return &mockServerController{ - mockWriter: mockWriter, - loader: loader, - prefix: "/mock/server", - combinePort: combinePort, - } + return &mockServerController{ + mockWriter: mockWriter, + loader: loader, + prefix: "/mock/server", + combinePort: combinePort, + } } func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (reply *Empty, err error) { - s.mockWriter.Write([]byte(in.Config)) - s.prefix = in.Prefix - if dServer, ok := s.loader.(mock.DynamicServer); ok && dServer.GetPort() != strconv.Itoa(int(in.GetPort())) { - if strconv.Itoa(s.combinePort) != dServer.GetPort() { - if stopErr := dServer.Stop(); stopErr != nil { - remoteServerLogger.Info("failed to stop old server", "error", stopErr) - } else { - remoteServerLogger.Info("old server stopped", "port", dServer.GetPort()) - } - } - - server := mock.NewInMemoryServer(int(in.GetPort())) - server.Start(s.mockWriter, in.Prefix) - s.loader = server - } - err = s.loader.Load() - return + s.mockWriter.Write([]byte(in.Config)) + s.prefix = in.Prefix + if dServer, ok := s.loader.(mock.DynamicServer); ok && dServer.GetPort() != strconv.Itoa(int(in.GetPort())) { + if strconv.Itoa(s.combinePort) != dServer.GetPort() { + if stopErr := dServer.Stop(); stopErr != nil { + remoteServerLogger.Info("failed to stop old server", "error", stopErr) + } else { + remoteServerLogger.Info("old server stopped", "port", dServer.GetPort()) + } + } + + server := mock.NewInMemoryServer(int(in.GetPort())) + server.Start(s.mockWriter, in.Prefix) + s.loader = server + } + err = s.loader.Load() + return } func (s *mockServerController) GetConfig(ctx context.Context, in *Empty) (reply *MockConfig, err error) { - reply = &MockConfig{ - Prefix: s.prefix, - Config: string(s.mockWriter.GetData()), - } - if dServer, ok := s.loader.(mock.DynamicServer); ok { - if port, pErr := strconv.ParseInt(dServer.GetPort(), 10, 32); pErr == nil { - reply.Port = int32(port) - } - } - return + reply = &MockConfig{ + Prefix: s.prefix, + Config: string(s.mockWriter.GetData()), + } + if dServer, ok := s.loader.(mock.DynamicServer); ok { + if port, pErr := strconv.ParseInt(dServer.GetPort(), 10, 32); pErr == nil { + reply.Port = int32(port) + } + } + return } func (s *server) getLoaderByStoreName(storeName string) (loader testing.Writer, err error) { - var store *testing.Store - store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName) - if err == nil && store != nil { - loader, err = s.storeWriterFactory.NewInstance(*store) - if err != nil { - err = fmt.Errorf("failed to new grpc loader from store %s, err: %v", store.Name, err) - } - } else { - err = fmt.Errorf("failed to get store %s, err: %v", storeName, err) - } - return + var store *testing.Store + store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName) + if err == nil && store != nil { + loader, err = s.storeWriterFactory.NewInstance(*store) + if err != nil { + err = fmt.Errorf("failed to new grpc loader from store %s, err: %v", store.Name, err) + } + } else { + err = fmt.Errorf("failed to get store %s, err: %v", storeName, err) + } + return } //go:embed data/headers.yaml var popularHeaders []byte func findParentTestCases(testcase *testing.TestCase, suite *testing.TestSuite) (testcases []testing.TestCase) { - reg, matchErr := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) - targetReg, targetErr := regexp.Compile(`\.\w*`) - - expectNames := new(UniqueSlice[string]) - if matchErr == nil && targetErr == nil { - var expectName string - for _, val := range testcase.Request.Header { - if matched := reg.MatchString(val); matched { - expectName = targetReg.FindString(val) - expectName = strings.TrimPrefix(expectName, ".") - expectNames.Push(expectName) - } - } - - findExpectNames(testcase.Request.API, expectNames) - findExpectNames(testcase.Request.Body.String(), expectNames) - - remoteServerLogger.Info("expect test case names", "name", expectNames.GetAll()) - for _, item := range suite.Items { - if expectNames.Exist(item.Name) { - testcases = append(testcases, item) - } - } - } - return + reg, matchErr := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) + targetReg, targetErr := regexp.Compile(`\.\w*`) + + expectNames := new(UniqueSlice[string]) + if matchErr == nil && targetErr == nil { + var expectName string + for _, val := range testcase.Request.Header { + if matched := reg.MatchString(val); matched { + expectName = targetReg.FindString(val) + expectName = strings.TrimPrefix(expectName, ".") + expectNames.Push(expectName) + } + } + + findExpectNames(testcase.Request.API, expectNames) + findExpectNames(testcase.Request.Body.String(), expectNames) + + remoteServerLogger.Info("expect test case names", "name", expectNames.GetAll()) + for _, item := range suite.Items { + if expectNames.Exist(item.Name) { + testcases = append(testcases, item) + } + } + } + return } func findExpectNames(target string, expectNames *UniqueSlice[string]) { - reg, _ := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) - targetReg, _ := regexp.Compile(`\.\w*`) + reg, _ := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) + targetReg, _ := regexp.Compile(`\.\w*`) - for _, sub := range reg.FindStringSubmatch(target) { - // remove {{ and }} - if left, leftErr := regexp.Compile(`.*\{\{`); leftErr == nil { - body := left.ReplaceAllString(sub, "") + for _, sub := range reg.FindStringSubmatch(target) { + // remove {{ and }} + if left, leftErr := regexp.Compile(`.*\{\{`); leftErr == nil { + body := left.ReplaceAllString(sub, "") - expectName := targetReg.FindString(body) - expectName = strings.TrimPrefix(expectName, ".") - expectNames.Push(expectName) - } - } + expectName := targetReg.FindString(body) + expectName = strings.TrimPrefix(expectName, ".") + expectNames.Push(expectName) + } + } } // UniqueSlice represents an unique slice type UniqueSlice[T comparable] struct { - data []T + data []T } // Push pushes an item if it's not exist func (s *UniqueSlice[T]) Push(item T) *UniqueSlice[T] { - if s.data == nil { - s.data = []T{item} - } else { - for _, it := range s.data { - if it == item { - return s - } - } - s.data = append(s.data, item) - } - return s + if s.data == nil { + s.data = []T{item} + } else { + for _, it := range s.data { + if it == item { + return s + } + } + s.data = append(s.data, item) + } + return s } // Exist checks if the item exist, return true it exists func (s *UniqueSlice[T]) Exist(item T) bool { - if s.data != nil { - for _, it := range s.data { - if it == item { - return true - } - } - } - return false + if s.data != nil { + for _, it := range s.data { + if it == item { + return true + } + } + } + return false } // GetAll returns all the items func (s *UniqueSlice[T]) GetAll() []T { - return s.data + return s.data } var errNoTestSuiteFound = errors.New("no test suite found") diff --git a/pkg/testing/case.go b/pkg/testing/case.go index 6d411421..bb3c7bfc 100644 --- a/pkg/testing/case.go +++ b/pkg/testing/case.go @@ -16,159 +16,159 @@ limitations under the License. package testing import ( - "encoding/base64" - "encoding/json" - "log" - "sort" - "strings" - "time" - - "github.com/linuxsuren/api-testing/pkg/util" - "gopkg.in/yaml.v3" + "encoding/base64" + "encoding/json" + "log" + "sort" + "strings" + "time" + + "github.com/linuxsuren/api-testing/pkg/util" + "gopkg.in/yaml.v3" ) // TestSuite represents a set of test cases type TestSuite struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - API string `yaml:"api,omitempty" json:"api,omitempty"` - Spec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` - Param map[string]string `yaml:"param,omitempty" json:"param,omitempty"` - Items []TestCase `yaml:"items,omitempty" json:"items,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + API string `yaml:"api,omitempty" json:"api,omitempty"` + Spec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` + Param map[string]string `yaml:"param,omitempty" json:"param,omitempty"` + Items []TestCase `yaml:"items,omitempty" json:"items,omitempty"` } type APISpec struct { - Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` - RPC *RPCDesc `yaml:"rpc,omitempty" json:"rpc,omitempty"` - Secure *Secure `yaml:"secure,omitempty" json:"secure,omitempty"` - Metric *Metric `yaml:"metric,omitempty" json:"metric,omitempty"` + Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` + RPC *RPCDesc `yaml:"rpc,omitempty" json:"rpc,omitempty"` + Secure *Secure `yaml:"secure,omitempty" json:"secure,omitempty"` + Metric *Metric `yaml:"metric,omitempty" json:"metric,omitempty"` } type HistoryTestSuite struct { - HistorySuiteName string `yaml:"name,omitempty" json:"name,omitempty"` - Items []HistoryTestCase `yaml:"items,omitempty" json:"items,omitempty"` + HistorySuiteName string `yaml:"name,omitempty" json:"name,omitempty"` + Items []HistoryTestCase `yaml:"items,omitempty" json:"items,omitempty"` } type HistoryTestCase struct { - ID string `yaml:"id,omitempty" json:"id,omitempty"` - CaseName string `yaml:"caseName,omitempty" json:"name,omitempty"` - SuiteName string `yaml:"suiteName,omitempty" json:"suiteName,omitempty"` - HistorySuiteName string `yaml:"historySuiteName,omitempty" json:"historySuiteName,omitempty"` - CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` - SuiteAPI string `yaml:"api,omitempty" json:"api,omitempty"` - SuiteSpec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` - SuiteParam map[string]string `yaml:"param,omitempty" json:"param,omitempty"` - Data TestCase `yaml:"data,omitempty" json:"data,omitempty"` - HistoryHeader map[string]string `yaml:"historyHeader,omitempty" json:"historyHeader,omitempty"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` + CaseName string `yaml:"caseName,omitempty" json:"name,omitempty"` + SuiteName string `yaml:"suiteName,omitempty" json:"suiteName,omitempty"` + HistorySuiteName string `yaml:"historySuiteName,omitempty" json:"historySuiteName,omitempty"` + CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` + SuiteAPI string `yaml:"api,omitempty" json:"api,omitempty"` + SuiteSpec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` + SuiteParam map[string]string `yaml:"param,omitempty" json:"param,omitempty"` + Data TestCase `yaml:"data,omitempty" json:"data,omitempty"` + HistoryHeader map[string]string `yaml:"historyHeader,omitempty" json:"historyHeader,omitempty"` } type HistoryTestResult struct { - Message string `yaml:"message,omitempty" json:"message,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - TestCaseResult []TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` - Data HistoryTestCase `yaml:"data,omitempty" json:"data,omitempty"` - CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + TestCaseResult []TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` + Data HistoryTestCase `yaml:"data,omitempty" json:"data,omitempty"` + CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` } type RPCDesc struct { - ImportPath []string `yaml:"import,omitempty" json:"import,omitempty"` - ServerReflection bool `yaml:"serverReflection,omitempty" json:"serverReflection,omitempty"` - ProtoFile string `yaml:"protofile,omitempty" json:"protofile,omitempty"` - ProtoSet string `yaml:"protoset,omitempty" json:"protoset,omitempty"` - Raw string `yaml:"raw,omitempty" json:"raw,omitempty"` + ImportPath []string `yaml:"import,omitempty" json:"import,omitempty"` + ServerReflection bool `yaml:"serverReflection,omitempty" json:"serverReflection,omitempty"` + ProtoFile string `yaml:"protofile,omitempty" json:"protofile,omitempty"` + ProtoSet string `yaml:"protoset,omitempty" json:"protoset,omitempty"` + Raw string `yaml:"raw,omitempty" json:"raw,omitempty"` } type Secure struct { - Insecure bool `yaml:"insecure,omitempty" json:"insecure,omitempty"` - CertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` - CAFile string `yaml:"ca,omitempty" json:"ca,omitempty"` - KeyFile string `yaml:"key,omitempty" json:"key,omitempty"` - ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"` + Insecure bool `yaml:"insecure,omitempty" json:"insecure,omitempty"` + CertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` + CAFile string `yaml:"ca,omitempty" json:"ca,omitempty"` + KeyFile string `yaml:"key,omitempty" json:"key,omitempty"` + ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"` } type Metric struct { - Type string `yaml:"type,omitempty" json:"type,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` } // TestCase represents a test case type TestCase struct { - ID string `yaml:"id,omitempty" json:"id,omitempty"` - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Group string `yaml:"group,omitempty" json:"group,omitempty"` - Before *Job `yaml:"before,omitempty" json:"before,omitempty"` - After *Job `yaml:"after,omitempty" json:"after,omitempty"` - Request Request `yaml:"request" json:"request"` - Expect Response `yaml:"expect,omitempty" json:"expect,omitempty"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Group string `yaml:"group,omitempty" json:"group,omitempty"` + Before *Job `yaml:"before,omitempty" json:"before,omitempty"` + After *Job `yaml:"after,omitempty" json:"after,omitempty"` + Request Request `yaml:"request" json:"request"` + Expect Response `yaml:"expect,omitempty" json:"expect,omitempty"` } // InScope returns true if the test case is in scope with the given items. // Returns true if the items is empty. func (c *TestCase) InScope(items []string) bool { - if len(items) == 0 { - return true - } - for _, item := range items { - if item == c.Name { - return true - } - } - return false + if len(items) == 0 { + return true + } + for _, item := range items { + if item == c.Name { + return true + } + } + return false } // Job contains a list of jobs type Job struct { - Items []string `yaml:"items,omitempty" json:"items,omitempty"` + Items []string `yaml:"items,omitempty" json:"items,omitempty"` } // Request represents a HTTP request type Request struct { - API string `yaml:"api" json:"api"` - Method string `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"enum=GET,enum=POST,enum=PUT,enum=DELETE"` - Query SortedKeysStringMap `yaml:"query,omitempty" json:"query,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - Cookie map[string]string `yaml:"cookie,omitempty" json:"cookie,omitempty"` - Form map[string]string `yaml:"form,omitempty" json:"form,omitempty"` - Body RequestBody `yaml:"body,omitempty" json:"body,omitempty"` - BodyFromFile string `yaml:"bodyFromFile,omitempty" json:"bodyFromFile,omitempty"` + API string `yaml:"api" json:"api"` + Method string `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"enum=GET,enum=POST,enum=PUT,enum=DELETE"` + Query SortedKeysStringMap `yaml:"query,omitempty" json:"query,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + Cookie map[string]string `yaml:"cookie,omitempty" json:"cookie,omitempty"` + Form map[string]string `yaml:"form,omitempty" json:"form,omitempty"` + Body RequestBody `yaml:"body,omitempty" json:"body,omitempty"` + BodyFromFile string `yaml:"bodyFromFile,omitempty" json:"bodyFromFile,omitempty"` } type RequestBody struct { - Value string `json:"value" yaml:"value"` - isJson bool + Value string `json:"value" yaml:"value"` + isJson bool } func NewRequestBody(val string) RequestBody { - return RequestBody{Value: val} + return RequestBody{Value: val} } func (e *RequestBody) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { - gql := &GraphQLRequestBody{} - err = unmarshal(gql) - if err != nil { - val := "" - if err = unmarshal(&val); err == nil { - e.Value = val - } - } else { - var data []byte - if data, err = json.Marshal(gql); err == nil { - e.Value = string(data) - e.isJson = true - } - } - return + gql := &GraphQLRequestBody{} + err = unmarshal(gql) + if err != nil { + val := "" + if err = unmarshal(&val); err == nil { + e.Value = val + } + } else { + var data []byte + if data, err = json.Marshal(gql); err == nil { + e.Value = string(data) + e.isJson = true + } + } + return } func (e RequestBody) MarshalYAML() (val interface{}, err error) { - val = e.Value - if e.isJson { - gql := &GraphQLRequestBody{} - if err = json.Unmarshal([]byte(e.Value), gql); err == nil { - val = gql - } - } - return + val = e.Value + if e.isJson { + gql := &GraphQLRequestBody{} + if err = json.Unmarshal([]byte(e.Value), gql); err == nil { + val = gql + } + } + return } var _ yaml.Marshaler = &RequestBody{} @@ -176,136 +176,136 @@ var _ yaml.Marshaler = &RequestBody{} // var _ yaml.Unmarshaler = &RequestBody{} func (e RequestBody) String() string { - return e.Value + return e.Value } func (e RequestBody) IsEmpty() bool { - return e.Value == "" + return e.Value == "" } func (e RequestBody) Bytes() (data []byte) { - var err error - if strings.HasPrefix(e.Value, util.ImageBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.ImageBase64Prefix) - } else if strings.HasPrefix(e.Value, util.PDFBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.PDFBase64Prefix) - } else if strings.HasPrefix(e.Value, util.ZIPBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.ZIPBase64Prefix) - } else if strings.HasPrefix(e.Value, util.BinaryBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.BinaryBase64Prefix) - } else { - data = []byte(e.Value) - } - - if err != nil { - log.Printf("Error decoding: %v", err) - } - return + var err error + if strings.HasPrefix(e.Value, util.ImageBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.ImageBase64Prefix) + } else if strings.HasPrefix(e.Value, util.PDFBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.PDFBase64Prefix) + } else if strings.HasPrefix(e.Value, util.ZIPBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.ZIPBase64Prefix) + } else if strings.HasPrefix(e.Value, util.BinaryBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.BinaryBase64Prefix) + } else { + data = []byte(e.Value) + } + + if err != nil { + log.Printf("Error decoding: %v", err) + } + return } func decodeBase64Body(raw, prefix string) ([]byte, error) { - rawStr := strings.TrimPrefix(raw, prefix) - return base64.StdEncoding.DecodeString(rawStr) + rawStr := strings.TrimPrefix(raw, prefix) + return base64.StdEncoding.DecodeString(rawStr) } type GraphQLRequestBody struct { - Query string `yaml:"query" json:"query"` - OperationName string `yaml:"operationName" json:"operationName"` - Variables map[string]string `yaml:"variables" json:"variables"` + Query string `yaml:"query" json:"query"` + OperationName string `yaml:"operationName" json:"operationName"` + Variables map[string]string `yaml:"variables" json:"variables"` } // Response is the expected response type Response struct { - StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` - Body string `yaml:"body,omitempty" json:"body,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - BodyFieldsExpect map[string]interface{} `yaml:"bodyFieldsExpect,omitempty" json:"bodyFieldsExpect,omitempty"` - Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` - ConditionalVerify []ConditionalVerify `yaml:"conditionalVerify,omitempty" json:"conditionalVerify,omitempty"` - Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` + StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` + Body string `yaml:"body,omitempty" json:"body,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + BodyFieldsExpect map[string]interface{} `yaml:"bodyFieldsExpect,omitempty" json:"bodyFieldsExpect,omitempty"` + Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` + ConditionalVerify []ConditionalVerify `yaml:"conditionalVerify,omitempty" json:"conditionalVerify,omitempty"` + Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` } func (r Response) GetBody() string { - return r.Body + return r.Body } func (r Response) GetBodyFieldsExpect() map[string]interface{} { - return r.BodyFieldsExpect + return r.BodyFieldsExpect } type ConditionalVerify struct { - Condition []string `yaml:"condition,omitempty" json:"condition,omitempty"` - Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` + Condition []string `yaml:"condition,omitempty" json:"condition,omitempty"` + Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` } type SortedKeysStringMap map[string]interface{} func (m SortedKeysStringMap) Keys() (keys []string) { - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - return + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return } func (m SortedKeysStringMap) GetValue(key string) string { - val := m[key] + val := m[key] - switch o := any(val).(type) { - case string: - return val.(string) - case map[string]interface{}: - verifier := convertToVerifier(o) - return verifier.Value - case *Verifier: - return o.Value - } + switch o := any(val).(type) { + case string: + return val.(string) + case map[string]interface{}: + verifier := convertToVerifier(o) + return verifier.Value + case *Verifier: + return o.Value + } - return "" + return "" } func (m SortedKeysStringMap) GetVerifier(key string) (verifier *Verifier) { - val := m[key] + val := m[key] - switch o := any(val).(type) { - case map[string]interface{}: - verifier = convertToVerifier(o) - } + switch o := any(val).(type) { + case map[string]interface{}: + verifier = convertToVerifier(o) + } - return + return } func convertToVerifier(data map[string]interface{}) (verifier *Verifier) { - verifier = &Verifier{} + verifier = &Verifier{} - if data, err := yaml.Marshal(data); err == nil { - if err = yaml.Unmarshal(data, verifier); err != nil { - verifier = nil - } - } - return + if data, err := yaml.Marshal(data); err == nil { + if err = yaml.Unmarshal(data, verifier); err != nil { + verifier = nil + } + } + return } type Verifier struct { - Value string `yaml:"value,omitempty" json:"value,omitempty"` - Required bool `yaml:"required,omitempty" json:"required,omitempty"` - Max int `yaml:"max"` - Min int `yaml:"min"` - MaxLength int `yaml:"maxLength"` - MinLength int `yaml:"minLength"` + Value string `yaml:"value,omitempty" json:"value,omitempty"` + Required bool `yaml:"required,omitempty" json:"required,omitempty"` + Max int `yaml:"max"` + Min int `yaml:"min"` + MaxLength int `yaml:"maxLength"` + MinLength int `yaml:"minLength"` } type TestResult struct { - Message string `yaml:"message,omitempty" json:"message,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - TestCaseResult []*TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + TestCaseResult []*TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` } type TestCaseResult struct { - StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` - Body string `yaml:"body,omitempty" json:"body,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - Id string `yaml:"id,omitempty" json:"id,omitempty"` - Output string `yaml:"output,omitempty" json:"output,omitempty"` + StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` + Body string `yaml:"body,omitempty" json:"body,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + Id string `yaml:"id,omitempty" json:"id,omitempty"` + Output string `yaml:"output,omitempty" json:"output,omitempty"` } diff --git a/pkg/testing/case_test.go b/pkg/testing/case_test.go index 4107dda5..6c4523da 100644 --- a/pkg/testing/case_test.go +++ b/pkg/testing/case_test.go @@ -16,25 +16,25 @@ limitations under the License. package testing_test import ( - "github.com/linuxsuren/api-testing/pkg/util" - "testing" + "github.com/linuxsuren/api-testing/pkg/util" + "testing" - atesting "github.com/linuxsuren/api-testing/pkg/testing" - "github.com/stretchr/testify/assert" + atesting "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" ) func TestInScope(t *testing.T) { - testCase := &atesting.TestCase{Name: "foo"} - assert.True(t, testCase.InScope(nil)) - assert.True(t, testCase.InScope([]string{"foo"})) - assert.False(t, testCase.InScope([]string{"bar"})) + testCase := &atesting.TestCase{Name: "foo"} + assert.True(t, testCase.InScope(nil)) + assert.True(t, testCase.InScope([]string{"foo"})) + assert.False(t, testCase.InScope([]string{"bar"})) } func TestRequestBody(t *testing.T) { - req := &atesting.Request{} - graphqlBody := `api: /api + req := &atesting.Request{} + graphqlBody := `api: /api body: query: query operationName: "" @@ -42,88 +42,88 @@ body: name: rick ` - err := yaml.Unmarshal([]byte(graphqlBody), req) - assert.Nil(t, err) - assert.Equal(t, `{"query":"query","operationName":"","variables":{"name":"rick"}}`, req.Body.String()) + err := yaml.Unmarshal([]byte(graphqlBody), req) + assert.Nil(t, err) + assert.Equal(t, `{"query":"query","operationName":"","variables":{"name":"rick"}}`, req.Body.String()) - var data []byte - data, err = yaml.Marshal(req) - assert.Nil(t, err) - assert.Equal(t, graphqlBody, string(data)) + var data []byte + data, err = yaml.Marshal(req) + assert.Nil(t, err) + assert.Equal(t, graphqlBody, string(data)) - err = yaml.Unmarshal([]byte(`body: plain`), req) - assert.Nil(t, err) - assert.Equal(t, "plain", req.Body.String()) + err = yaml.Unmarshal([]byte(`body: plain`), req) + assert.Nil(t, err) + assert.Equal(t, "plain", req.Body.String()) } func TestResponse(t *testing.T) { - resp := &atesting.Response{ - Body: "body", - BodyFieldsExpect: map[string]interface{}{ - "name": "rick", - }, - } - assert.Equal(t, "body", resp.GetBody()) - assert.Equal(t, map[string]interface{}{"name": "rick"}, resp.GetBodyFieldsExpect()) + resp := &atesting.Response{ + Body: "body", + BodyFieldsExpect: map[string]interface{}{ + "name": "rick", + }, + } + assert.Equal(t, "body", resp.GetBody()) + assert.Equal(t, map[string]interface{}{"name": "rick"}, resp.GetBodyFieldsExpect()) } func TestSortedKeysStringMap(t *testing.T) { - obj := atesting.SortedKeysStringMap{ - "c": "d", - "f": map[string]interface{}{ - "value": "f", - }, - "e": &atesting.Verifier{ - Value: "e", - }, - "a": "b", - } - assert.Equal(t, []string{"a", "c", "e", "f"}, obj.Keys()) - assert.Equal(t, "b", obj.GetValue("a")) - assert.Nil(t, obj.GetVerifier("b")) - assert.Equal(t, "e", obj.GetValue("e")) - assert.Equal(t, "f", obj.GetValue("f")) - assert.Equal(t, "f", obj.GetVerifier("f").Value) - assert.Empty(t, obj.GetValue("not-found")) + obj := atesting.SortedKeysStringMap{ + "c": "d", + "f": map[string]interface{}{ + "value": "f", + }, + "e": &atesting.Verifier{ + Value: "e", + }, + "a": "b", + } + assert.Equal(t, []string{"a", "c", "e", "f"}, obj.Keys()) + assert.Equal(t, "b", obj.GetValue("a")) + assert.Nil(t, obj.GetVerifier("b")) + assert.Equal(t, "e", obj.GetValue("e")) + assert.Equal(t, "f", obj.GetValue("f")) + assert.Equal(t, "f", obj.GetVerifier("f").Value) + assert.Empty(t, obj.GetValue("not-found")) } func TestBodyBytes(t *testing.T) { - const defaultPlainText = "hello" - const defaultBase64Text = "aGVsbG8=" + const defaultPlainText = "hello" + const defaultBase64Text = "aGVsbG8=" - tt := []struct { - name string - rawBody string - expect []byte - }{{ - name: "image base64", - rawBody: util.ImageBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "pdf", - rawBody: util.PDFBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "zip", - rawBody: util.ZIPBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "binary", - rawBody: util.BinaryBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "raw", - rawBody: defaultPlainText, - expect: []byte(defaultPlainText), - }} - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - body := atesting.RequestBody{ - Value: tc.rawBody, - } - data := body.Bytes() - assert.Equal(t, tc.expect, data) - assert.False(t, body.IsEmpty()) - }) - } + tt := []struct { + name string + rawBody string + expect []byte + }{{ + name: "image base64", + rawBody: util.ImageBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "pdf", + rawBody: util.PDFBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "zip", + rawBody: util.ZIPBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "binary", + rawBody: util.BinaryBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "raw", + rawBody: defaultPlainText, + expect: []byte(defaultPlainText), + }} + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + body := atesting.RequestBody{ + Value: tc.rawBody, + } + data := body.Bytes() + assert.Equal(t, tc.expect, data) + assert.False(t, body.IsEmpty()) + }) + } } diff --git a/tools/linter/codespell/.codespell.ignorewords b/tools/linter/codespell/.codespell.ignorewords new file mode 100644 index 00000000..37514b40 --- /dev/null +++ b/tools/linter/codespell/.codespell.ignorewords @@ -0,0 +1,5 @@ +keypair +keypairs +als +requestor +immediatedly diff --git a/tools/linter/codespell/.codespell.skip b/tools/linter/codespell/.codespell.skip new file mode 100644 index 00000000..1b469f96 --- /dev/null +++ b/tools/linter/codespell/.codespell.skip @@ -0,0 +1,14 @@ +.git +.idea +*.png +*.woff +*.woff2 +*.eot +*.ttf +*.jpg +*.ico +*.svg +./charts +*.js +./site/public/* +./site/node_modules/* diff --git a/tools/linter/license/.licenserc.yaml b/tools/linter/license/.licenserc.yaml new file mode 100644 index 00000000..859321e1 --- /dev/null +++ b/tools/linter/license/.licenserc.yaml @@ -0,0 +1,26 @@ +header: + license: + spdx-id: Apache License + copyright-owner: API Testing Authors + + paths: + - "**" + + paths-ignore: + - "dist" + - "licenses" + - "**/*.md" + - "LICENSE" + - "NOTICE" + + comment: on-failure + + language: + Go: + extensions: + - ".go" + comment_style_id: SlashAsterisk + + dependency: + files: + - ../../../go.mod diff --git a/.github/markdown_lint_config.json b/tools/linter/markdownlint/markdown_lint_config.json similarity index 100% rename from .github/markdown_lint_config.json rename to tools/linter/markdownlint/markdown_lint_config.json diff --git a/tools/make/docs.mk b/tools/make/docs.mk index 759a8016..f5ff5dfb 100644 --- a/tools/make/docs.mk +++ b/tools/make/docs.mk @@ -4,6 +4,8 @@ DOCS_OUTPUT_DIR := site/public +LINKINATOR_IGNORE := "github.com githubusercontent.com github.com _print v0.0.1" + ##@ Docs .PHONY: docs @@ -12,19 +14,6 @@ docs: docs.clean cd $(ROOT_DIR)/docs/site && npm install cd $(ROOT_DIR)/docs/site && npm run build:production -.PHONY: check-links -check-links: ## Check for broken links in the docs. -check-links: docs-check-links - - -.PHONY: docs-check-links -docs-check-links: - @$(LOG_TARGET) - # Check for broken links - npm install -g linkinator@6.0.4 - # https://github.com/JustinBeckwith/linkinator?tab=readme-ov-file#command-usage - linkinator docs/site/public -r --concurrency 25 -s "github.com _print v0.0.1" - # Docs site, make by hexo. .PHONY: docs-serve diff --git a/tools/make/golang.mk b/tools/make/golang.mk index 5cc6d5c4..b27991fd 100644 --- a/tools/make/golang.mk +++ b/tools/make/golang.mk @@ -68,12 +68,22 @@ go.clean: ## Clean the building output files @$(LOG_TARGET) rm -rf $(OUTPUT_DIR) +>PHONY: go.mod.vet +go.mod.vet: + @$(LOG_TARGET) + @go vet ./... + +>PHONY: go.mod.fmt +go.mod.fmt: + @$(LOG_TARGET) + @go fmt ./... + .PHONY: go.mod.lint lint: go.mod.lint go.mod.lint: @$(LOG_TARGET) @go mod tidy -compat=$(GO_VERSION) - @go fmt ./... + @make go.mod.fmt @if test -n "$$(git status -s -- go.mod go.sum)"; then \ git diff --exit-code go.mod; \ git diff --exit-code go.sum; \ diff --git a/tools/make/lint.mk b/tools/make/lint.mk index 40834860..29b7ba14 100644 --- a/tools/make/lint.mk +++ b/tools/make/lint.mk @@ -29,3 +29,39 @@ lint-deps: $(tools/yamllint) lint.yamllint: $(tools/yamllint) @$(LOG_TARGET) $(tools/yamllint) --config-file=tools/linter/yamllint/.yamllint $$(git ls-files :*.yml :*.yaml | xargs -L1 dirname | sort -u) + +.PHONY: lint.markdown +lint: lint.markdown +lint-deps: $(tools/markdownlint) +lint.markdown: + @$(LOG_TARGET) + $(tools/markdownlint) -c tools/linter/markdownlint/markdown_lint_config.json docs/site/content/** + +.PHONY: lint.markdown.fix +lint: lint.markdown.fix +lint-deps: $(tools/markdownlint) +lint.markdown.fix: + @$(LOG_TARGET) + $(tools/markdownlint) -c tools/linter/markdownlint/markdown_lint_config.json --fix docs/site/content/** + +.PHONY: lint.codespell +lint: lint.codespell +lint-deps: $(tools/codespell) +lint.codespell: CODESPELL_SKIP := $(shell cat tools/linter/codespell/.codespell.skip | tr \\n ',') +lint.codespell: + @$(LOG_TARGET) + $(tools/codespell) --skip $(CODESPELL_SKIP) --ignore-words tools/linter/codespell/.codespell.ignorewords --check-filenames + +.PHONY: lint.checklinks +lint: lint.checklinks +lint-deps: $(tools/linkinator) +lint.checklinks: # Check for broken links in the docs + @$(LOG_TARGET) + $(tools/linkinator) docs/site/public/ -r --concurrency 25 --skip $(LINKINATOR_IGNORE) + +.PHONY: lint.checklicense +lint: lint.checklicense +lint-deps: $(tools/skywalking-eyes) +lint.checklicense: # Check for broken links in the docs + @$(LOG_TARGET) + $(tools/skywalking-eyes) -c tools/linter/license/.licenserc.yaml header check diff --git a/tools/make/tools.mk b/tools/make/tools.mk index d1ba730e..83c52deb 100644 --- a/tools/make/tools.mk +++ b/tools/make/tools.mk @@ -9,6 +9,7 @@ tools/protoc-gen-go = $(tools.bindir)/protoc-gen-go tools/protoc-gen-go-grpc = $(tools.bindir)/protoc-gen-go-grpc tools/goreleaser = $(tools.bindir)/goreleaser tools/buf = $(tools.bindir)/buf +tools/skywalking-eyes = $(tools.bindir)/skywalking-eyes $(tools.bindir)/%: $(tools.srcdir)/%/pin.go $(tools.srcdir)/%/go.mod cd $( Date: Sun, 12 Jan 2025 22:38:42 +0800 Subject: [PATCH 02/10] fix Signed-off-by: yuluo-yx --- pkg/testing/case_test.go | 166 +++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/pkg/testing/case_test.go b/pkg/testing/case_test.go index 6c4523da..4107dda5 100644 --- a/pkg/testing/case_test.go +++ b/pkg/testing/case_test.go @@ -16,25 +16,25 @@ limitations under the License. package testing_test import ( - "github.com/linuxsuren/api-testing/pkg/util" - "testing" + "github.com/linuxsuren/api-testing/pkg/util" + "testing" - atesting "github.com/linuxsuren/api-testing/pkg/testing" - "github.com/stretchr/testify/assert" + atesting "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" ) func TestInScope(t *testing.T) { - testCase := &atesting.TestCase{Name: "foo"} - assert.True(t, testCase.InScope(nil)) - assert.True(t, testCase.InScope([]string{"foo"})) - assert.False(t, testCase.InScope([]string{"bar"})) + testCase := &atesting.TestCase{Name: "foo"} + assert.True(t, testCase.InScope(nil)) + assert.True(t, testCase.InScope([]string{"foo"})) + assert.False(t, testCase.InScope([]string{"bar"})) } func TestRequestBody(t *testing.T) { - req := &atesting.Request{} - graphqlBody := `api: /api + req := &atesting.Request{} + graphqlBody := `api: /api body: query: query operationName: "" @@ -42,88 +42,88 @@ body: name: rick ` - err := yaml.Unmarshal([]byte(graphqlBody), req) - assert.Nil(t, err) - assert.Equal(t, `{"query":"query","operationName":"","variables":{"name":"rick"}}`, req.Body.String()) + err := yaml.Unmarshal([]byte(graphqlBody), req) + assert.Nil(t, err) + assert.Equal(t, `{"query":"query","operationName":"","variables":{"name":"rick"}}`, req.Body.String()) - var data []byte - data, err = yaml.Marshal(req) - assert.Nil(t, err) - assert.Equal(t, graphqlBody, string(data)) + var data []byte + data, err = yaml.Marshal(req) + assert.Nil(t, err) + assert.Equal(t, graphqlBody, string(data)) - err = yaml.Unmarshal([]byte(`body: plain`), req) - assert.Nil(t, err) - assert.Equal(t, "plain", req.Body.String()) + err = yaml.Unmarshal([]byte(`body: plain`), req) + assert.Nil(t, err) + assert.Equal(t, "plain", req.Body.String()) } func TestResponse(t *testing.T) { - resp := &atesting.Response{ - Body: "body", - BodyFieldsExpect: map[string]interface{}{ - "name": "rick", - }, - } - assert.Equal(t, "body", resp.GetBody()) - assert.Equal(t, map[string]interface{}{"name": "rick"}, resp.GetBodyFieldsExpect()) + resp := &atesting.Response{ + Body: "body", + BodyFieldsExpect: map[string]interface{}{ + "name": "rick", + }, + } + assert.Equal(t, "body", resp.GetBody()) + assert.Equal(t, map[string]interface{}{"name": "rick"}, resp.GetBodyFieldsExpect()) } func TestSortedKeysStringMap(t *testing.T) { - obj := atesting.SortedKeysStringMap{ - "c": "d", - "f": map[string]interface{}{ - "value": "f", - }, - "e": &atesting.Verifier{ - Value: "e", - }, - "a": "b", - } - assert.Equal(t, []string{"a", "c", "e", "f"}, obj.Keys()) - assert.Equal(t, "b", obj.GetValue("a")) - assert.Nil(t, obj.GetVerifier("b")) - assert.Equal(t, "e", obj.GetValue("e")) - assert.Equal(t, "f", obj.GetValue("f")) - assert.Equal(t, "f", obj.GetVerifier("f").Value) - assert.Empty(t, obj.GetValue("not-found")) + obj := atesting.SortedKeysStringMap{ + "c": "d", + "f": map[string]interface{}{ + "value": "f", + }, + "e": &atesting.Verifier{ + Value: "e", + }, + "a": "b", + } + assert.Equal(t, []string{"a", "c", "e", "f"}, obj.Keys()) + assert.Equal(t, "b", obj.GetValue("a")) + assert.Nil(t, obj.GetVerifier("b")) + assert.Equal(t, "e", obj.GetValue("e")) + assert.Equal(t, "f", obj.GetValue("f")) + assert.Equal(t, "f", obj.GetVerifier("f").Value) + assert.Empty(t, obj.GetValue("not-found")) } func TestBodyBytes(t *testing.T) { - const defaultPlainText = "hello" - const defaultBase64Text = "aGVsbG8=" + const defaultPlainText = "hello" + const defaultBase64Text = "aGVsbG8=" - tt := []struct { - name string - rawBody string - expect []byte - }{{ - name: "image base64", - rawBody: util.ImageBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "pdf", - rawBody: util.PDFBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "zip", - rawBody: util.ZIPBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "binary", - rawBody: util.BinaryBase64Prefix + defaultBase64Text, - expect: []byte(defaultPlainText), - }, { - name: "raw", - rawBody: defaultPlainText, - expect: []byte(defaultPlainText), - }} - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - body := atesting.RequestBody{ - Value: tc.rawBody, - } - data := body.Bytes() - assert.Equal(t, tc.expect, data) - assert.False(t, body.IsEmpty()) - }) - } + tt := []struct { + name string + rawBody string + expect []byte + }{{ + name: "image base64", + rawBody: util.ImageBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "pdf", + rawBody: util.PDFBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "zip", + rawBody: util.ZIPBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "binary", + rawBody: util.BinaryBase64Prefix + defaultBase64Text, + expect: []byte(defaultPlainText), + }, { + name: "raw", + rawBody: defaultPlainText, + expect: []byte(defaultPlainText), + }} + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + body := atesting.RequestBody{ + Value: tc.rawBody, + } + data := body.Bytes() + assert.Equal(t, tc.expect, data) + assert.False(t, body.IsEmpty()) + }) + } } From b6258b1a570c0dd446bf62c99f6c45a42f39946a Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Sun, 12 Jan 2025 22:40:53 +0800 Subject: [PATCH 03/10] fix Signed-off-by: yuluo-yx --- pkg/server/remote_server.go | 2192 +++++++++++++++++------------------ pkg/testing/case.go | 356 +++--- 2 files changed, 1274 insertions(+), 1274 deletions(-) diff --git a/pkg/server/remote_server.go b/pkg/server/remote_server.go index 09fa25e1..9727a547 100644 --- a/pkg/server/remote_server.go +++ b/pkg/server/remote_server.go @@ -17,72 +17,72 @@ limitations under the License. package server import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "mime" - "net/http" - "os" - "path/filepath" - reflect "reflect" - "regexp" - "strconv" - "strings" - "time" - - "github.com/expr-lang/expr/builtin" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" - - "github.com/linuxsuren/api-testing/pkg/util/home" - - "github.com/linuxsuren/api-testing/pkg/mock" - - _ "embed" - - "github.com/linuxsuren/api-testing/pkg/generator" - "github.com/linuxsuren/api-testing/pkg/logging" - "github.com/linuxsuren/api-testing/pkg/oauth" - "github.com/linuxsuren/api-testing/pkg/render" - "github.com/linuxsuren/api-testing/pkg/runner" - "github.com/linuxsuren/api-testing/pkg/testing" - "github.com/linuxsuren/api-testing/pkg/util" - "github.com/linuxsuren/api-testing/pkg/version" - "github.com/linuxsuren/api-testing/sample" - - "google.golang.org/grpc/metadata" - "gopkg.in/yaml.v3" + "bytes" + "context" + "errors" + "fmt" + "io" + "mime" + "net/http" + "os" + "path/filepath" + reflect "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/expr-lang/expr/builtin" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/linuxsuren/api-testing/pkg/util/home" + + "github.com/linuxsuren/api-testing/pkg/mock" + + _ "embed" + + "github.com/linuxsuren/api-testing/pkg/generator" + "github.com/linuxsuren/api-testing/pkg/logging" + "github.com/linuxsuren/api-testing/pkg/oauth" + "github.com/linuxsuren/api-testing/pkg/render" + "github.com/linuxsuren/api-testing/pkg/runner" + "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/linuxsuren/api-testing/pkg/util" + "github.com/linuxsuren/api-testing/pkg/version" + "github.com/linuxsuren/api-testing/sample" + + "google.golang.org/grpc/metadata" + "gopkg.in/yaml.v3" ) var ( - remoteServerLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("remote_server") - GrpcMaxRecvMsgSize int + remoteServerLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("remote_server") + GrpcMaxRecvMsgSize int ) type server struct { - UnimplementedRunnerServer - loader testing.Writer - storeWriterFactory testing.StoreWriterFactory - configDir string - storeExtMgr ExtManager + UnimplementedRunnerServer + loader testing.Writer + storeWriterFactory testing.StoreWriterFactory + configDir string + storeExtMgr ExtManager - secretServer SecretServiceServer + secretServer SecretServiceServer - grpcMaxRecvMsgSize int + grpcMaxRecvMsgSize int } type SecretServiceServer interface { - GetSecrets(context.Context, *Empty) (*Secrets, error) - CreateSecret(context.Context, *Secret) (*CommonResult, error) - DeleteSecret(context.Context, *Secret) (*CommonResult, error) - UpdateSecret(context.Context, *Secret) (*CommonResult, error) + GetSecrets(context.Context, *Empty) (*Secrets, error) + CreateSecret(context.Context, *Secret) (*CommonResult, error) + DeleteSecret(context.Context, *Secret) (*CommonResult, error) + UpdateSecret(context.Context, *Secret) (*CommonResult, error) } type SecertServiceGetable interface { - GetSecret(context.Context, *Secret) (*Secret, error) + GetSecret(context.Context, *Secret) (*Secret, error) } type fakeSecretServer struct{} @@ -90,1305 +90,1305 @@ type fakeSecretServer struct{} var errNoSecretService = errors.New("no secret service found") func (f *fakeSecretServer) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - err = errNoSecretService - return + err = errNoSecretService + return } // NewRemoteServer creates a remote server instance func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, storeExtMgr ExtManager, configDir string, grpcMaxRecvMsgSize int) RunnerServer { - if secretServer == nil { - secretServer = &fakeSecretServer{} - } - GrpcMaxRecvMsgSize = grpcMaxRecvMsgSize - return &server{ - loader: loader, - storeWriterFactory: storeWriterFactory, - configDir: configDir, - secretServer: secretServer, - storeExtMgr: storeExtMgr, - grpcMaxRecvMsgSize: grpcMaxRecvMsgSize, - } + if secretServer == nil { + secretServer = &fakeSecretServer{} + } + GrpcMaxRecvMsgSize = grpcMaxRecvMsgSize + return &server{ + loader: loader, + storeWriterFactory: storeWriterFactory, + configDir: configDir, + secretServer: secretServer, + storeExtMgr: storeExtMgr, + grpcMaxRecvMsgSize: grpcMaxRecvMsgSize, + } } func withDefaultValue(old, defVal any) any { - if old == "" || old == nil { - old = defVal - } - return old + if old == "" || old == nil { + old = defVal + } + return old } func parseSuiteWithItems(data []byte) (suite *testing.TestSuite, err error) { - suite, err = testing.ParseFromData(data) - if err == nil && (suite == nil || suite.Items == nil) { - err = errNoTestSuiteFound - } - return + suite, err = testing.ParseFromData(data) + if err == nil && (suite == nil || suite.Items == nil) { + err = errNoTestSuiteFound + } + return } func (s *server) getSuiteFromTestTask(task *TestTask) (suite *testing.TestSuite, err error) { - switch task.Kind { - case "suite": - suite, err = parseSuiteWithItems([]byte(task.Data)) - case "testcase": - var testCase *testing.TestCase - if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil { - return - } - suite = &testing.TestSuite{ - Items: []testing.TestCase{*testCase}, - } - case "testcaseInSuite": - suite, err = parseSuiteWithItems([]byte(task.Data)) - if err != nil { - return - } - - var targetTestcase *testing.TestCase - for _, item := range suite.Items { - if item.Name == task.CaseName { - targetTestcase = &item - break - } - } - - if targetTestcase != nil { - parentCases := findParentTestCases(targetTestcase, suite) - remoteServerLogger.Info("find parent cases", "num", len(parentCases)) - suite.Items = append(parentCases, *targetTestcase) - } else { - err = fmt.Errorf("cannot found testcase %s", task.CaseName) - } - default: - err = fmt.Errorf("not support '%s'", task.Kind) - } - return + switch task.Kind { + case "suite": + suite, err = parseSuiteWithItems([]byte(task.Data)) + case "testcase": + var testCase *testing.TestCase + if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil { + return + } + suite = &testing.TestSuite{ + Items: []testing.TestCase{*testCase}, + } + case "testcaseInSuite": + suite, err = parseSuiteWithItems([]byte(task.Data)) + if err != nil { + return + } + + var targetTestcase *testing.TestCase + for _, item := range suite.Items { + if item.Name == task.CaseName { + targetTestcase = &item + break + } + } + + if targetTestcase != nil { + parentCases := findParentTestCases(targetTestcase, suite) + remoteServerLogger.Info("find parent cases", "num", len(parentCases)) + suite.Items = append(parentCases, *targetTestcase) + } else { + err = fmt.Errorf("cannot found testcase %s", task.CaseName) + } + default: + err = fmt.Errorf("not support '%s'", task.Kind) + } + return } func resetEnv(oldEnv map[string]string) { - for key, val := range oldEnv { - os.Setenv(key, val) - } + for key, val := range oldEnv { + os.Setenv(key, val) + } } func (s *server) getLoader(ctx context.Context) (loader testing.Writer) { - var ok bool - loader = s.loader - - var mdd metadata.MD - if mdd, ok = metadata.FromIncomingContext(ctx); ok { - storeNameMeta := mdd.Get(HeaderKeyStoreName) - if len(storeNameMeta) > 0 { - storeName := strings.TrimSpace(storeNameMeta[0]) - if storeName == "local" || storeName == "" { - return - } - - var err error - if loader, err = s.getLoaderByStoreName(storeName); err != nil { - remoteServerLogger.Info("failed to get loader", "name", storeName, "error", err) - loader = testing.NewNonWriter() - } - } - } - return + var ok bool + loader = s.loader + + var mdd metadata.MD + if mdd, ok = metadata.FromIncomingContext(ctx); ok { + storeNameMeta := mdd.Get(HeaderKeyStoreName) + if len(storeNameMeta) > 0 { + storeName := strings.TrimSpace(storeNameMeta[0]) + if storeName == "local" || storeName == "" { + return + } + + var err error + if loader, err = s.getLoaderByStoreName(storeName); err != nil { + remoteServerLogger.Info("failed to get loader", "name", storeName, "error", err) + loader = testing.NewNonWriter() + } + } + } + return } // Run start to run the test task func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, err error) { - task.Level = withDefaultValue(task.Level, "info").(string) - task.Env = withDefaultValue(task.Env, map[string]string{}).(map[string]string) - - var suite *testing.TestSuite - // TODO may not safe in multiple threads - oldEnv := map[string]string{} - for key, val := range task.Env { - oldEnv[key] = os.Getenv(key) - os.Setenv(key, val) - } - - defer func() { - resetEnv(oldEnv) - }() - - if suite, err = s.getSuiteFromTestTask(task); err != nil { - return - } - - remoteServerLogger.Info("prepare to run", "name", suite.Name, " with level: ", task.Level) - remoteServerLogger.Info("task kind to run", "kind", task.Kind, "lens", len(suite.Items)) - dataContext := map[string]interface{}{} - - if err = suite.Render(dataContext); err != nil { - reply.Error = err.Error() - err = nil - return - } - // inject the parameters from input - if len(task.Parameters) > 0 { - dataContext[testing.ContextKeyGlobalParam] = pairToMap(task.Parameters) - } - - buf := new(bytes.Buffer) - reply = &TestResult{} - - for _, testCase := range suite.Items { - suiteRunner := runner.GetTestSuiteRunner(suite) - suiteRunner.WithOutputWriter(buf) - suiteRunner.WithWriteLevel(task.Level) - suiteRunner.WithSecure(suite.Spec.Secure) - - // reuse the API prefix - testCase.Request.RenderAPI(suite.API) - historyHeader := make(map[string]string) - for k, v := range testCase.Request.Header { - historyHeader[k] = v - } - - output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx) - if getter, ok := suiteRunner.(runner.ResponseRecord); ok { - resp := getter.GetResponseRecord() - //resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name) - reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{ - StatusCode: int32(resp.StatusCode), - Body: resp.Body, - Header: mapToPair(resp.Header), - Id: testCase.ID, - Output: buf.String(), - }) - } - - if testErr == nil { - dataContext[testCase.Name] = output - } else { - reply.Error = testErr.Error() - break - } - // create history record - go func(historyHeader map[string]string) { - loader := s.getLoader(ctx) - defer loader.Close() - for _, testCaseResult := range reply.TestCaseResult { - err = loader.CreateHistoryTestCase(ToNormalTestCaseResult(testCaseResult), suite, historyHeader) - if err != nil { - remoteServerLogger.Info("error create history") - } - } - }(historyHeader) - } - - if reply.Error != "" { - fmt.Fprintln(buf, reply.Error) - } - reply.Message = buf.String() - return + task.Level = withDefaultValue(task.Level, "info").(string) + task.Env = withDefaultValue(task.Env, map[string]string{}).(map[string]string) + + var suite *testing.TestSuite + // TODO may not safe in multiple threads + oldEnv := map[string]string{} + for key, val := range task.Env { + oldEnv[key] = os.Getenv(key) + os.Setenv(key, val) + } + + defer func() { + resetEnv(oldEnv) + }() + + if suite, err = s.getSuiteFromTestTask(task); err != nil { + return + } + + remoteServerLogger.Info("prepare to run", "name", suite.Name, " with level: ", task.Level) + remoteServerLogger.Info("task kind to run", "kind", task.Kind, "lens", len(suite.Items)) + dataContext := map[string]interface{}{} + + if err = suite.Render(dataContext); err != nil { + reply.Error = err.Error() + err = nil + return + } + // inject the parameters from input + if len(task.Parameters) > 0 { + dataContext[testing.ContextKeyGlobalParam] = pairToMap(task.Parameters) + } + + buf := new(bytes.Buffer) + reply = &TestResult{} + + for _, testCase := range suite.Items { + suiteRunner := runner.GetTestSuiteRunner(suite) + suiteRunner.WithOutputWriter(buf) + suiteRunner.WithWriteLevel(task.Level) + suiteRunner.WithSecure(suite.Spec.Secure) + + // reuse the API prefix + testCase.Request.RenderAPI(suite.API) + historyHeader := make(map[string]string) + for k, v := range testCase.Request.Header { + historyHeader[k] = v + } + + output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx) + if getter, ok := suiteRunner.(runner.ResponseRecord); ok { + resp := getter.GetResponseRecord() + //resp, err = runner.HandleLargeResponseBody(resp, suite.Name, testCase.Name) + reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{ + StatusCode: int32(resp.StatusCode), + Body: resp.Body, + Header: mapToPair(resp.Header), + Id: testCase.ID, + Output: buf.String(), + }) + } + + if testErr == nil { + dataContext[testCase.Name] = output + } else { + reply.Error = testErr.Error() + break + } + // create history record + go func(historyHeader map[string]string) { + loader := s.getLoader(ctx) + defer loader.Close() + for _, testCaseResult := range reply.TestCaseResult { + err = loader.CreateHistoryTestCase(ToNormalTestCaseResult(testCaseResult), suite, historyHeader) + if err != nil { + remoteServerLogger.Info("error create history") + } + } + }(historyHeader) + } + + if reply.Error != "" { + fmt.Fprintln(buf, reply.Error) + } + reply.Message = buf.String() + return } func (s *server) BatchRun(srv Runner_BatchRunServer) (err error) { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - var in *BatchTestTask - in, err = srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - for i := 0; i < int(in.Count); i++ { - var reply *TestCaseResult - if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ - Suite: in.SuiteName, - Testcase: in.CaseName, - }); err != nil { - return - } - - if err = srv.Send(&TestResult{ - TestCaseResult: []*TestCaseResult{reply}, - Error: reply.Error, - }); err != nil { - return err - } - - var interval string - if interval, err = render.Render("batch run interval", in.Interval, nil); err != nil { - return - } - - var duration time.Duration - if duration, err = time.ParseDuration(interval); err != nil { - return - } - time.Sleep(duration) - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + var in *BatchTestTask + in, err = srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + for i := 0; i < int(in.Count); i++ { + var reply *TestCaseResult + if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ + Suite: in.SuiteName, + Testcase: in.CaseName, + }); err != nil { + return + } + + if err = srv.Send(&TestResult{ + TestCaseResult: []*TestCaseResult{reply}, + Error: reply.Error, + }); err != nil { + return err + } + + var interval string + if interval, err = render.Render("batch run interval", in.Interval, nil); err != nil { + return + } + + var duration time.Duration + if duration, err = time.ParseDuration(interval); err != nil { + return + } + time.Sleep(duration) + } + } + } } func (s *server) DownloadResponseFile(ctx context.Context, in *TestCase) (reply *FileData, err error) { - if in.Response != nil { - tempFileName := in.Response.Body - if tempFileName == "" { - return nil, errors.New("file name is empty") - } - - tempDir := os.TempDir() - filePath := filepath.Join(tempDir, tempFileName) - if filepath.Clean(filePath) != filepath.Join(tempDir, filepath.Base(tempFileName)) { - return nil, errors.New("invalid file path") - } - - fmt.Println("get file from", filePath) - fileContent, err := os.ReadFile(filePath) - if err != nil { - return nil, fmt.Errorf("failed to read file: %s", filePath) - } - - mimeType := mime.TypeByExtension(filepath.Ext(filePath)) - if mimeType == "" { - mimeType = "application/octet-stream" - } - - filename := filepath.Base(filePath) - // try to get the original filename - var originalFileName []byte - if originalFileName, err = os.ReadFile(filePath + "name"); err == nil && len(originalFileName) > 0 { - filename = string(originalFileName) - } - - reply = &FileData{ - Data: fileContent, - ContentType: mimeType, - Filename: filename, - } - - return reply, nil - } else { - return reply, errors.New("response is empty") - } + if in.Response != nil { + tempFileName := in.Response.Body + if tempFileName == "" { + return nil, errors.New("file name is empty") + } + + tempDir := os.TempDir() + filePath := filepath.Join(tempDir, tempFileName) + if filepath.Clean(filePath) != filepath.Join(tempDir, filepath.Base(tempFileName)) { + return nil, errors.New("invalid file path") + } + + fmt.Println("get file from", filePath) + fileContent, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read file: %s", filePath) + } + + mimeType := mime.TypeByExtension(filepath.Ext(filePath)) + if mimeType == "" { + mimeType = "application/octet-stream" + } + + filename := filepath.Base(filePath) + // try to get the original filename + var originalFileName []byte + if originalFileName, err = os.ReadFile(filePath + "name"); err == nil && len(originalFileName) > 0 { + filename = string(originalFileName) + } + + reply = &FileData{ + Data: fileContent, + ContentType: mimeType, + Filename: filename, + } + + return reply, nil + } else { + return reply, errors.New("response is empty") + } } func (s *server) RunTestSuite(srv Runner_RunTestSuiteServer) (err error) { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - var in *TestSuiteIdentity - in, err = srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - - var suite *Suite - if suite, err = s.ListTestCase(ctx, in); err != nil { - return - } - - for _, item := range suite.Items { - var reply *TestCaseResult - if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ - Suite: in.Name, - Testcase: item.Name, - }); err != nil { - return - } - - if err = srv.Send(&TestResult{ - TestCaseResult: []*TestCaseResult{reply}, - Error: reply.Error, - }); err != nil { - return err - } - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + var in *TestSuiteIdentity + in, err = srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + var suite *Suite + if suite, err = s.ListTestCase(ctx, in); err != nil { + return + } + + for _, item := range suite.Items { + var reply *TestCaseResult + if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{ + Suite: in.Name, + Testcase: item.Name, + }); err != nil { + return + } + + if err = srv.Send(&TestResult{ + TestCaseResult: []*TestCaseResult{reply}, + Error: reply.Error, + }); err != nil { + return err + } + } + } + } } // GetVersion returns the version func (s *server) GetVersion(ctx context.Context, in *Empty) (reply *Version, err error) { - reply = &Version{ - Version: version.GetVersion(), - Date: version.GetDate(), - Commit: version.GetCommit(), - } - return + reply = &Version{ + Version: version.GetVersion(), + Date: version.GetDate(), + Commit: version.GetCommit(), + } + return } func (s *server) GetSuites(ctx context.Context, in *Empty) (reply *Suites, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &Suites{ - Data: make(map[string]*Items), - } - - var suites []testing.TestSuite - if suites, err = loader.ListTestSuite(); err == nil && suites != nil { - for _, suite := range suites { - items := &Items{} - for _, item := range suite.Items { - items.Data = append(items.Data, item.Name) - } - items.Kind = suite.Spec.Kind - reply.Data[suite.Name] = items - } - } - - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &Suites{ + Data: make(map[string]*Items), + } + + var suites []testing.TestSuite + if suites, err = loader.ListTestSuite(); err == nil && suites != nil { + for _, suite := range suites { + items := &Items{} + for _, item := range suite.Items { + items.Data = append(items.Data, item.Name) + } + items.Kind = suite.Spec.Kind + reply.Data[suite.Name] = items + } + } + + return } func (s *server) GetHistorySuites(ctx context.Context, in *Empty) (reply *HistorySuites, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HistorySuites{ - Data: make(map[string]*HistoryItems), - } - - var suites []testing.HistoryTestSuite - if suites, err = loader.ListHistoryTestSuite(); err == nil && suites != nil { - for _, suite := range suites { - items := &HistoryItems{} - for _, item := range suite.Items { - data := &HistoryCaseIdentity{ - ID: item.ID, - HistorySuiteName: item.HistorySuiteName, - Kind: item.SuiteSpec.Kind, - Suite: item.SuiteName, - Testcase: item.CaseName, - } - items.Data = append(items.Data, data) - } - reply.Data[suite.HistorySuiteName] = items - } - } - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HistorySuites{ + Data: make(map[string]*HistoryItems), + } + + var suites []testing.HistoryTestSuite + if suites, err = loader.ListHistoryTestSuite(); err == nil && suites != nil { + for _, suite := range suites { + items := &HistoryItems{} + for _, item := range suite.Items { + data := &HistoryCaseIdentity{ + ID: item.ID, + HistorySuiteName: item.HistorySuiteName, + Kind: item.SuiteSpec.Kind, + Suite: item.SuiteName, + Testcase: item.CaseName, + } + items.Data = append(items.Data, data) + } + reply.Data[suite.HistorySuiteName] = items + } + } + return } func (s *server) CreateTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - if loader == nil { - reply.Error = "no loader found" - } else { - if err = loader.CreateSuite(in.Name, in.Api); err == nil { - toUpdate := testing.TestSuite{ - Name: in.Name, - API: in.Api, - Spec: testing.APISpec{ - Kind: in.Kind, - }, - } - - switch strings.ToLower(in.Kind) { - case "grpc", "trpc": - toUpdate.Spec.RPC = &testing.RPCDesc{} - } - - err = loader.UpdateSuite(toUpdate) - } - } - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + if loader == nil { + reply.Error = "no loader found" + } else { + if err = loader.CreateSuite(in.Name, in.Api); err == nil { + toUpdate := testing.TestSuite{ + Name: in.Name, + API: in.Api, + Spec: testing.APISpec{ + Kind: in.Kind, + }, + } + + switch strings.ToLower(in.Kind) { + case "grpc", "trpc": + toUpdate.Spec.RPC = &testing.RPCDesc{} + } + + err = loader.UpdateSuite(toUpdate) + } + } + return } func (s *server) ImportTestSuite(ctx context.Context, in *TestSuiteSource) (result *CommonResult, err error) { - result = &CommonResult{} - var dataImporter generator.Importer - switch in.Kind { - case "postman": - dataImporter = generator.NewPostmanImporter() - case "native", "": - dataImporter = generator.NewNativeImporter() - default: - result.Success = false - result.Message = fmt.Sprintf("not support kind: %s", in.Kind) - return - } - - remoteServerLogger.Logger.Info("import test suite", "kind", in.Kind, "url", in.Url) - var suite *testing.TestSuite - if in.Url != "" { - suite, err = dataImporter.ConvertFromURL(in.Url) - } else if in.Data != "" { - suite, err = dataImporter.Convert([]byte(in.Data)) - } else { - err = errors.New("url or data is required") - } - - if err != nil { - result.Success = false - result.Message = err.Error() - return - } - - loader := s.getLoader(ctx) - defer loader.Close() - - if err = loader.CreateSuite(suite.Name, suite.API); err != nil { - return - } - - for _, item := range suite.Items { - if err = loader.CreateTestCase(suite.Name, item); err != nil { - break - } - } - result.Success = true - return + result = &CommonResult{} + var dataImporter generator.Importer + switch in.Kind { + case "postman": + dataImporter = generator.NewPostmanImporter() + case "native", "": + dataImporter = generator.NewNativeImporter() + default: + result.Success = false + result.Message = fmt.Sprintf("not support kind: %s", in.Kind) + return + } + + remoteServerLogger.Logger.Info("import test suite", "kind", in.Kind, "url", in.Url) + var suite *testing.TestSuite + if in.Url != "" { + suite, err = dataImporter.ConvertFromURL(in.Url) + } else if in.Data != "" { + suite, err = dataImporter.Convert([]byte(in.Data)) + } else { + err = errors.New("url or data is required") + } + + if err != nil { + result.Success = false + result.Message = err.Error() + return + } + + loader := s.getLoader(ctx) + defer loader.Close() + + if err = loader.CreateSuite(suite.Name, suite.API); err != nil { + return + } + + for _, item := range suite.Items { + if err = loader.CreateTestCase(suite.Name, item); err != nil { + break + } + } + result.Success = true + return } func (s *server) GetTestSuite(ctx context.Context, in *TestSuiteIdentity) (result *TestSuite, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - var suite *testing.TestSuite - if suite, _, err = loader.GetSuite(in.Name); err == nil && suite != nil { - result = ToGRPCSuite(suite) - } - return + loader := s.getLoader(ctx) + defer loader.Close() + var suite *testing.TestSuite + if suite, _, err = loader.GetSuite(in.Name); err == nil && suite != nil { + result = ToGRPCSuite(suite) + } + return } func (s *server) UpdateTestSuite(ctx context.Context, in *TestSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.UpdateSuite(*ToNormalSuite(in)) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.UpdateSuite(*ToNormalSuite(in)) + return } func (s *server) DeleteTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.DeleteSuite(in.Name) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.DeleteSuite(in.Name) + return } func (s *server) DuplicateTestSuite(ctx context.Context, in *TestSuiteDuplicate) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - - if in.SourceSuiteName == in.TargetSuiteName { - reply.Error = "source and target suite name should be different" - return - } - - var suite testing.TestSuite - if suite, err = loader.GetTestSuite(in.SourceSuiteName, true); err == nil { - suite.Name = in.TargetSuiteName - if err = loader.CreateSuite(suite.Name, suite.API); err == nil { - for _, testCase := range suite.Items { - if err = loader.CreateTestCase(suite.Name, testCase); err != nil { - break - } - } - } - } - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + + if in.SourceSuiteName == in.TargetSuiteName { + reply.Error = "source and target suite name should be different" + return + } + + var suite testing.TestSuite + if suite, err = loader.GetTestSuite(in.SourceSuiteName, true); err == nil { + suite.Name = in.TargetSuiteName + if err = loader.CreateSuite(suite.Name, suite.API); err == nil { + for _, testCase := range suite.Items { + if err = loader.CreateTestCase(suite.Name, testCase); err != nil { + break + } + } + } + } + return } func (s *server) RenameTestSuite(ctx context.Context, in *TestSuiteDuplicate) (reply *HelloReply, err error) { - reply = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.RenameTestSuite(in.SourceSuiteName, in.TargetSuiteName) - return + reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.RenameTestSuite(in.SourceSuiteName, in.TargetSuiteName) + return } func (s *server) ListTestCase(ctx context.Context, in *TestSuiteIdentity) (result *Suite, err error) { - var items []testing.TestCase - loader := s.getLoader(ctx) - defer loader.Close() - if items, err = loader.ListTestCase(in.Name); err == nil { - result = &Suite{} - for _, item := range items { - result.Items = append(result.Items, ToGRPCTestCase(item)) - } - } - return + var items []testing.TestCase + loader := s.getLoader(ctx) + defer loader.Close() + if items, err = loader.ListTestCase(in.Name); err == nil { + result = &Suite{} + for _, item := range items { + result.Items = append(result.Items, ToGRPCTestCase(item)) + } + } + return } func (s *server) GetTestSuiteYaml(ctx context.Context, in *TestSuiteIdentity) (reply *YamlData, err error) { - var data []byte - loader := s.getLoader(ctx) - defer loader.Close() - if data, err = loader.GetTestSuiteYaml(in.Name); err == nil { - reply = &YamlData{ - Data: data, - } - } - return + var data []byte + loader := s.getLoader(ctx) + defer loader.Close() + if data, err = loader.GetTestSuiteYaml(in.Name); err == nil { + reply = &YamlData{ + Data: data, + } + } + return } func (s *server) GetTestCase(ctx context.Context, in *TestCaseIdentity) (reply *TestCase, err error) { - var result testing.TestCase - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetTestCase(in.Suite, in.Testcase); err == nil { - reply = ToGRPCTestCase(result) + var result testing.TestCase + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetTestCase(in.Suite, in.Testcase); err == nil { + reply = ToGRPCTestCase(result) - var suite testing.TestSuite - if suite, err = loader.GetTestSuite(in.Suite, false); err == nil { - reply.Server = suite.API - } - } - return + var suite testing.TestSuite + if suite, err = loader.GetTestSuite(in.Suite, false); err == nil { + reply.Server = suite.API + } + } + return } func (s *server) GetHistoryTestCaseWithResult(ctx context.Context, in *HistoryTestCase) (reply *HistoryTestResult, err error) { - var result testing.HistoryTestResult - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetHistoryTestCaseWithResult(in.ID); err == nil { - reply = ToGRPCHistoryTestCaseResult(result) - } - return + var result testing.HistoryTestResult + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetHistoryTestCaseWithResult(in.ID); err == nil { + reply = ToGRPCHistoryTestCaseResult(result) + } + return } func (s *server) GetHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HistoryTestCase, err error) { - var result testing.HistoryTestCase - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetHistoryTestCase(in.ID); err == nil { - reply = ConvertToGRPCHistoryTestCase(result) - } - return + var result testing.HistoryTestCase + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetHistoryTestCase(in.ID); err == nil { + reply = ConvertToGRPCHistoryTestCase(result) + } + return } var ExecutionCountNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_count", - Help: "The total number of request execution", + Name: "atest_execution_count", + Help: "The total number of request execution", }) var ExecutionSuccessNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_success", - Help: "The total number of request execution success", + Name: "atest_execution_success", + Help: "The total number of request execution success", }) var ExecutionFailNum = promauto.NewCounter(prometheus.CounterOpts{ - Name: "atest_execution_fail", - Help: "The total number of request execution fail", + Name: "atest_execution_fail", + Help: "The total number of request execution fail", }) func (s *server) GetTestCaseAllHistory(ctx context.Context, in *TestCase) (result *HistoryTestCases, err error) { - var items []testing.HistoryTestCase - loader := s.getLoader(ctx) - defer loader.Close() - if items, err = loader.GetTestCaseAllHistory(in.SuiteName, in.Name); err == nil { - result = &HistoryTestCases{} - for _, item := range items { - result.Data = append(result.Data, ConvertToGRPCHistoryTestCase(item)) - } - } - return + var items []testing.HistoryTestCase + loader := s.getLoader(ctx) + defer loader.Close() + if items, err = loader.GetTestCaseAllHistory(in.SuiteName, in.Name); err == nil { + result = &HistoryTestCases{} + for _, item := range items { + result.Data = append(result.Data, ConvertToGRPCHistoryTestCase(item)) + } + } + return } func (s *server) RunTestCase(ctx context.Context, in *TestCaseIdentity) (result *TestCaseResult, err error) { - var targetTestSuite testing.TestSuite - ExecutionCountNum.Inc() - defer func() { - if result.Error == "" { - ExecutionSuccessNum.Inc() - } else { - ExecutionFailNum.Inc() - } - }() - - result = &TestCaseResult{} - loader := s.getLoader(ctx) - defer loader.Close() - targetTestSuite, err = loader.GetTestSuite(in.Suite, true) - if err != nil || targetTestSuite.Name == "" { - err = nil - result.Error = fmt.Sprintf("not found suite: %s", in.Suite) - return - } - - var data []byte - if data, err = yaml.Marshal(targetTestSuite); err == nil { - task := &TestTask{ - Kind: "testcaseInSuite", - Data: string(data), - CaseName: in.Testcase, - Level: "debug", - Parameters: in.Parameters, - } - - var reply *TestResult - var lastItem *TestCaseResult - if reply, err = s.Run(ctx, task); err == nil && len(reply.TestCaseResult) > 0 { - lastIndex := len(reply.TestCaseResult) - 1 - lastItem = reply.TestCaseResult[lastIndex] - - if len(lastItem.Body) > GrpcMaxRecvMsgSize { - e := "the HTTP response body exceeded the maximum message size limit received by the gRPC client" - result = &TestCaseResult{ - Output: reply.Message, - Error: e, - Body: "", - Header: lastItem.Header, - StatusCode: http.StatusOK, - } - return - } - - result = &TestCaseResult{ - Output: reply.Message, - Error: reply.Error, - Body: lastItem.Body, - Header: lastItem.Header, - StatusCode: lastItem.StatusCode, - } - } else if err != nil { - result.Error = err.Error() - } else { - result = &TestCaseResult{ - Output: reply.Message, - Error: reply.Error, - } - } - - if reply != nil { - result.Output = reply.Message - result.Error = reply.Error - } - if lastItem != nil { - result.Body = lastItem.Body - result.Header = lastItem.Header - result.StatusCode = lastItem.StatusCode - } - } - return + var targetTestSuite testing.TestSuite + ExecutionCountNum.Inc() + defer func() { + if result.Error == "" { + ExecutionSuccessNum.Inc() + } else { + ExecutionFailNum.Inc() + } + }() + + result = &TestCaseResult{} + loader := s.getLoader(ctx) + defer loader.Close() + targetTestSuite, err = loader.GetTestSuite(in.Suite, true) + if err != nil || targetTestSuite.Name == "" { + err = nil + result.Error = fmt.Sprintf("not found suite: %s", in.Suite) + return + } + + var data []byte + if data, err = yaml.Marshal(targetTestSuite); err == nil { + task := &TestTask{ + Kind: "testcaseInSuite", + Data: string(data), + CaseName: in.Testcase, + Level: "debug", + Parameters: in.Parameters, + } + + var reply *TestResult + var lastItem *TestCaseResult + if reply, err = s.Run(ctx, task); err == nil && len(reply.TestCaseResult) > 0 { + lastIndex := len(reply.TestCaseResult) - 1 + lastItem = reply.TestCaseResult[lastIndex] + + if len(lastItem.Body) > GrpcMaxRecvMsgSize { + e := "the HTTP response body exceeded the maximum message size limit received by the gRPC client" + result = &TestCaseResult{ + Output: reply.Message, + Error: e, + Body: "", + Header: lastItem.Header, + StatusCode: http.StatusOK, + } + return + } + + result = &TestCaseResult{ + Output: reply.Message, + Error: reply.Error, + Body: lastItem.Body, + Header: lastItem.Header, + StatusCode: lastItem.StatusCode, + } + } else if err != nil { + result.Error = err.Error() + } else { + result = &TestCaseResult{ + Output: reply.Message, + Error: reply.Error, + } + } + + if reply != nil { + result.Output = reply.Message + result.Error = reply.Error + } + if lastItem != nil { + result.Body = lastItem.Body + result.Header = lastItem.Header + result.StatusCode = lastItem.StatusCode + } + } + return } func mapInterToPair(data map[string]interface{}) (pairs []*Pair) { - pairs = make([]*Pair, 0) - for k, v := range data { - pairs = append(pairs, &Pair{ - Key: k, - Value: fmt.Sprintf("%v", v), - }) - } - return + pairs = make([]*Pair, 0) + for k, v := range data { + pairs = append(pairs, &Pair{ + Key: k, + Value: fmt.Sprintf("%v", v), + }) + } + return } func mapToPair(data map[string]string) (pairs []*Pair) { - pairs = make([]*Pair, 0) - for k, v := range data { - pairs = append(pairs, &Pair{ - Key: k, - Value: v, - }) - } - return + pairs = make([]*Pair, 0) + for k, v := range data { + pairs = append(pairs, &Pair{ + Key: k, + Value: v, + }) + } + return } func pairToInterMap(pairs []*Pair) (data map[string]interface{}) { - data = make(map[string]interface{}) - for _, pair := range pairs { - if pair.Key == "" { - continue - } - data[pair.Key] = pair.Value - } - return + data = make(map[string]interface{}) + for _, pair := range pairs { + if pair.Key == "" { + continue + } + data[pair.Key] = pair.Value + } + return } func pairToMap(pairs []*Pair) (data map[string]string) { - data = make(map[string]string) - for _, pair := range pairs { - if pair.Key == "" { - continue - } - data[pair.Key] = pair.Value - } - return + data = make(map[string]string) + for _, pair := range pairs { + if pair.Key == "" { + continue + } + data[pair.Key] = pair.Value + } + return } func convertConditionalVerify(verify []*ConditionalVerify) (result []testing.ConditionalVerify) { - if verify != nil { - result = make([]testing.ConditionalVerify, 0) + if verify != nil { + result = make([]testing.ConditionalVerify, 0) - for _, item := range verify { - result = append(result, testing.ConditionalVerify{ - Condition: item.Condition, - Verify: item.Verify, - }) - } - } - return + for _, item := range verify { + result = append(result, testing.ConditionalVerify{ + Condition: item.Condition, + Verify: item.Verify, + }) + } + } + return } func (s *server) CreateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - if in.Data == nil { - err = errors.New("data is required") - } else { - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.CreateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) - } - return + reply = &HelloReply{} + if in.Data == nil { + err = errors.New("data is required") + } else { + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.CreateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) + } + return } func (s *server) UpdateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) { - reply = &HelloReply{} - if in.Data == nil { - err = errors.New("data is required") - return - } - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.UpdateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) - return + reply = &HelloReply{} + if in.Data == nil { + err = errors.New("data is required") + return + } + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.UpdateTestCase(in.SuiteName, ToNormalTestCase(in.Data)) + return } func (s *server) DeleteTestCase(ctx context.Context, in *TestCaseIdentity) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteTestCase(in.Suite, in.Testcase) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteTestCase(in.Suite, in.Testcase) + return } func (s *server) DeleteHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteHistoryTestCase(in.ID) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteHistoryTestCase(in.ID) + return } func (s *server) DeleteAllHistoryTestCase(ctx context.Context, in *HistoryTestCase) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} - err = loader.DeleteAllHistoryTestCase(in.SuiteName, in.CaseName) - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} + err = loader.DeleteAllHistoryTestCase(in.SuiteName, in.CaseName) + return } func (s *server) DuplicateTestCase(ctx context.Context, in *TestCaseDuplicate) (reply *HelloReply, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + reply = &HelloReply{} - if in.SourceCaseName == in.TargetCaseName { - reply.Error = "source and target case name should be different" - return - } + if in.SourceCaseName == in.TargetCaseName { + reply.Error = "source and target case name should be different" + return + } - var testcase testing.TestCase - if testcase, err = loader.GetTestCase(in.SourceSuiteName, in.SourceCaseName); err == nil { - testcase.Name = in.TargetCaseName - err = loader.CreateTestCase(in.TargetSuiteName, testcase) - } - return + var testcase testing.TestCase + if testcase, err = loader.GetTestCase(in.SourceSuiteName, in.SourceCaseName); err == nil { + testcase.Name = in.TargetCaseName + err = loader.CreateTestCase(in.TargetSuiteName, testcase) + } + return } func (s *server) RenameTestCase(ctx context.Context, in *TestCaseDuplicate) (result *HelloReply, err error) { - result = &HelloReply{} - loader := s.getLoader(ctx) - defer loader.Close() - err = loader.RenameTestCase(in.SourceSuiteName, in.SourceCaseName, in.TargetCaseName) - return + result = &HelloReply{} + loader := s.getLoader(ctx) + defer loader.Close() + err = loader.RenameTestCase(in.SourceSuiteName, in.SourceCaseName, in.TargetCaseName) + return } // code generator func (s *server) ListCodeGenerator(ctx context.Context, in *Empty) (reply *SimpleList, err error) { - reply = &SimpleList{} + reply = &SimpleList{} - generators := generator.GetCodeGenerators() - for name := range generators { - reply.Data = append(reply.Data, &Pair{ - Key: name, - }) - } - return + generators := generator.GetCodeGenerators() + for name := range generators { + reply.Data = append(reply.Data, &Pair{ + Key: name, + }) + } + return } func (s *server) GenerateCode(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - instance := generator.GetCodeGenerator(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) - } else { - var result testing.TestCase - var suite testing.TestSuite - - loader := s.getLoader(ctx) - if suite, err = loader.GetTestSuite(in.TestSuite, true); err != nil { - return - } - - dataContext := map[string]interface{}{} - if err = suite.Render(dataContext); err != nil { - return - } - - var output string - var genErr error - if in.TestCase == "" { - output, genErr = instance.Generate(&suite, nil) - } else { - if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil { - result.Request.RenderAPI(suite.API) - - output, genErr = instance.Generate(&suite, &result) - } - } - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - return + reply = &CommonResult{} + instance := generator.GetCodeGenerator(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) + } else { + var result testing.TestCase + var suite testing.TestSuite + + loader := s.getLoader(ctx) + if suite, err = loader.GetTestSuite(in.TestSuite, true); err != nil { + return + } + + dataContext := map[string]interface{}{} + if err = suite.Render(dataContext); err != nil { + return + } + + var output string + var genErr error + if in.TestCase == "" { + output, genErr = instance.Generate(&suite, nil) + } else { + if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil { + result.Request.RenderAPI(suite.API) + + output, genErr = instance.Generate(&suite, &result) + } + } + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + return } func (s *server) HistoryGenerateCode(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - instance := generator.GetCodeGenerator(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) - } else { - loader := s.getLoader(ctx) - var result testing.HistoryTestCase - result, err = loader.GetHistoryTestCase(in.ID) - var testCase testing.TestCase - var suite testing.TestSuite - testCase = result.Data - suite.Name = result.SuiteName - suite.API = result.SuiteAPI - suite.Spec = result.SuiteSpec - suite.Param = result.SuiteParam - - output, genErr := instance.Generate(&suite, &testCase) - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - return + reply = &CommonResult{} + instance := generator.GetCodeGenerator(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator) + } else { + loader := s.getLoader(ctx) + var result testing.HistoryTestCase + result, err = loader.GetHistoryTestCase(in.ID) + var testCase testing.TestCase + var suite testing.TestSuite + testCase = result.Data + suite.Name = result.SuiteName + suite.API = result.SuiteAPI + suite.Spec = result.SuiteSpec + suite.Param = result.SuiteParam + + output, genErr := instance.Generate(&suite, &testCase) + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + return } // converter func (s *server) ListConverter(ctx context.Context, in *Empty) (reply *SimpleList, err error) { - reply = &SimpleList{} - converters := generator.GetTestSuiteConverters() - for name := range converters { - reply.Data = append(reply.Data, &Pair{ - Key: name, - }) - } - return + reply = &SimpleList{} + converters := generator.GetTestSuiteConverters() + for name := range converters { + reply.Data = append(reply.Data, &Pair{ + Key: name, + }) + } + return } func (s *server) ConvertTestSuite(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) { - reply = &CommonResult{} - - instance := generator.GetTestSuiteConverter(in.Generator) - if instance == nil { - reply.Success = false - reply.Message = fmt.Sprintf("converter '%s' not found", in.Generator) - } else { - var result testing.TestSuite - loader := s.getLoader(ctx) - defer loader.Close() - if result, err = loader.GetTestSuite(in.TestSuite, true); err == nil { - output, genErr := instance.Convert(&result) - reply.Success = genErr == nil - reply.Message = util.OrErrorMessage(genErr, output) - } - } - return + reply = &CommonResult{} + + instance := generator.GetTestSuiteConverter(in.Generator) + if instance == nil { + reply.Success = false + reply.Message = fmt.Sprintf("converter '%s' not found", in.Generator) + } else { + var result testing.TestSuite + loader := s.getLoader(ctx) + defer loader.Close() + if result, err = loader.GetTestSuite(in.TestSuite, true); err == nil { + output, genErr := instance.Convert(&result) + reply.Success = genErr == nil + reply.Message = util.OrErrorMessage(genErr, output) + } + } + return } // Sample returns a sample of the test task func (s *server) Sample(ctx context.Context, in *Empty) (reply *HelloReply, err error) { - reply = &HelloReply{Message: sample.TestSuiteGitLab} - return + reply = &HelloReply{Message: sample.TestSuiteGitLab} + return } // PopularHeaders returns a list of popular headers func (s *server) PopularHeaders(ctx context.Context, in *Empty) (pairs *Pairs, err error) { - pairs = &Pairs{ - Data: []*Pair{}, - } + pairs = &Pairs{ + Data: []*Pair{}, + } - err = yaml.Unmarshal(popularHeaders, &pairs.Data) - return + err = yaml.Unmarshal(popularHeaders, &pairs.Data) + return } // GetSuggestedAPIs returns a list of suggested APIs func (s *server) GetSuggestedAPIs(ctx context.Context, in *TestSuiteIdentity) (reply *TestCases, err error) { - reply = &TestCases{} + reply = &TestCases{} - var suite *testing.TestSuite - loader := s.getLoader(ctx) - defer loader.Close() - if suite, _, err = loader.GetSuite(in.Name); err != nil || suite == nil { - return - } + var suite *testing.TestSuite + loader := s.getLoader(ctx) + defer loader.Close() + if suite, _, err = loader.GetSuite(in.Name); err != nil || suite == nil { + return + } - remoteServerLogger.Info("Finding APIs from", "name", in.Name, "with loader", reflect.TypeOf(loader)) + remoteServerLogger.Info("Finding APIs from", "name", in.Name, "with loader", reflect.TypeOf(loader)) - suiteRunner := runner.GetTestSuiteRunner(suite) - var result []*testing.TestCase - if result, err = suiteRunner.GetSuggestedAPIs(suite, in.Api); err == nil && result != nil { - for i := range result { - reply.Data = append(reply.Data, ToGRPCTestCase(*result[i])) - } - } - return + suiteRunner := runner.GetTestSuiteRunner(suite) + var result []*testing.TestCase + if result, err = suiteRunner.GetSuggestedAPIs(suite, in.Api); err == nil && result != nil { + for i := range result { + reply.Data = append(reply.Data, ToGRPCTestCase(*result[i])) + } + } + return } // FunctionsQuery returns a list of functions func (s *server) FunctionsQuery(ctx context.Context, in *SimpleQuery) (reply *Pairs, err error) { - reply = &Pairs{} - in.Name = strings.ToLower(in.Name) - - if in.Kind == "verify" { - for _, fn := range builtin.Builtins { - lowerName := strings.ToLower(fn.Name) - if in.Name == "" || strings.Contains(lowerName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: fn.Name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn.Func)), - }) - } - } - } else { - for name, fn := range render.FuncMap() { - lowerName := strings.ToLower(name) - if in.Name == "" || strings.Contains(lowerName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), - Description: render.FuncUsage(name), - }) - } - } - } - return + reply = &Pairs{} + in.Name = strings.ToLower(in.Name) + + if in.Kind == "verify" { + for _, fn := range builtin.Builtins { + lowerName := strings.ToLower(fn.Name) + if in.Name == "" || strings.Contains(lowerName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: fn.Name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn.Func)), + }) + } + } + } else { + for name, fn := range render.FuncMap() { + lowerName := strings.ToLower(name) + if in.Name == "" || strings.Contains(lowerName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), + Description: render.FuncUsage(name), + }) + } + } + } + return } // FunctionsQueryStream works like FunctionsQuery but is implemented in bidirectional streaming func (s *server) FunctionsQueryStream(srv Runner_FunctionsQueryStreamServer) error { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - in, err := srv.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - reply := &Pairs{} - in.Name = strings.ToLower(in.Name) - - for name, fn := range render.FuncMap() { - lowerCaseName := strings.ToLower(name) - if in.Name == "" || strings.Contains(lowerCaseName, in.Name) { - reply.Data = append(reply.Data, &Pair{ - Key: name, - Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), - }) - } - } - if err := srv.Send(reply); err != nil { - return err - } - } - } + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + in, err := srv.Recv() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + reply := &Pairs{} + in.Name = strings.ToLower(in.Name) + + for name, fn := range render.FuncMap() { + lowerCaseName := strings.ToLower(name) + if in.Name == "" || strings.Contains(lowerCaseName, in.Name) { + reply.Data = append(reply.Data, &Pair{ + Key: name, + Value: fmt.Sprintf("%v", reflect.TypeOf(fn)), + }) + } + } + if err := srv.Send(reply); err != nil { + return err + } + } + } } func (s *server) GetStoreKinds(context.Context, *Empty) (kinds *StoreKinds, err error) { - storeFactory := testing.NewStoreFactory(s.configDir) - var stores []testing.StoreKind - if stores, err = storeFactory.GetStoreKinds(); err == nil { - kinds = &StoreKinds{} - for _, store := range stores { - kinds.Data = append(kinds.Data, &StoreKind{ - Name: store.Name, - Enabled: store.Enabled, - Url: store.URL, - }) - } - } - return + storeFactory := testing.NewStoreFactory(s.configDir) + var stores []testing.StoreKind + if stores, err = storeFactory.GetStoreKinds(); err == nil { + kinds = &StoreKinds{} + for _, store := range stores { + kinds.Data = append(kinds.Data, &StoreKind{ + Name: store.Name, + Enabled: store.Enabled, + Url: store.URL, + }) + } + } + return } func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err error) { - user := oauth.GetUserFromContext(ctx) - storeFactory := testing.NewStoreFactory(s.configDir) - var stores []testing.Store - var owner string - if user != nil { - owner = user.Name - } - if stores, err = storeFactory.GetStoresByOwner(owner); err == nil { - reply = &Stores{ - Data: make([]*Store, 0), - } - for _, item := range stores { - grpcStore := ToGRPCStore(item) - - storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name}) - grpcStore.Ready = sErr == nil && storeStatus.Ready - grpcStore.ReadOnly = storeStatus.ReadOnly - grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason - - reply.Data = append(reply.Data, grpcStore) - } - reply.Data = append(reply.Data, &Store{ - Name: "local", - Kind: &StoreKind{}, - Ready: true, - }) - } - return + user := oauth.GetUserFromContext(ctx) + storeFactory := testing.NewStoreFactory(s.configDir) + var stores []testing.Store + var owner string + if user != nil { + owner = user.Name + } + if stores, err = storeFactory.GetStoresByOwner(owner); err == nil { + reply = &Stores{ + Data: make([]*Store, 0), + } + for _, item := range stores { + grpcStore := ToGRPCStore(item) + + storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name}) + grpcStore.Ready = sErr == nil && storeStatus.Ready + grpcStore.ReadOnly = storeStatus.ReadOnly + grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason + + reply.Data = append(reply.Data, grpcStore) + } + reply.Data = append(reply.Data, &Store{ + Name: "local", + Kind: &StoreKind{}, + Ready: true, + }) + } + return } func (s *server) CreateStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - user := oauth.GetUserFromContext(ctx) - if user != nil { - in.Owner = user.Name - } + reply = &Store{} + user := oauth.GetUserFromContext(ctx) + if user != nil { + in.Owner = user.Name + } - storeFactory := testing.NewStoreFactory(s.configDir) - store := ToNormalStore(in) + storeFactory := testing.NewStoreFactory(s.configDir) + store := ToNormalStore(in) - if store.Kind.URL == "" { - store.Kind.URL = fmt.Sprintf("unix://%s", home.GetExtensionSocketPath(store.Kind.Name)) - } + if store.Kind.URL == "" { + store.Kind.URL = fmt.Sprintf("unix://%s", home.GetExtensionSocketPath(store.Kind.Name)) + } - if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil { - err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) - } - return + if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil { + err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) + } + return } func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - storeFactory := testing.NewStoreFactory(s.configDir) - store := ToNormalStore(in) - if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil { - // TODO need to restart extension if config was changed - err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) - } - return + reply = &Store{} + storeFactory := testing.NewStoreFactory(s.configDir) + store := ToNormalStore(in) + if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil { + // TODO need to restart extension if config was changed + err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL) + } + return } func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err error) { - reply = &Store{} - storeFactory := testing.NewStoreFactory(s.configDir) - err = storeFactory.DeleteStore(in.Name) - return + reply = &Store{} + storeFactory := testing.NewStoreFactory(s.configDir) + err = storeFactory.DeleteStore(in.Name) + return } func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *ExtensionStatus, err error) { - reply = &ExtensionStatus{} - var loader testing.Writer - if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil { - readOnly, verifyErr := loader.Verify() - reply.Ready = verifyErr == nil - reply.ReadOnly = readOnly - reply.Message = util.OKOrErrorMessage(verifyErr) - } - return + reply = &ExtensionStatus{} + var loader testing.Writer + if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil { + readOnly, verifyErr := loader.Verify() + reply.Ready = verifyErr == nil + reply.ReadOnly = readOnly + reply.Message = util.OKOrErrorMessage(verifyErr) + } + return } // secret related interfaces func (s *server) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) { - return s.secretServer.GetSecrets(ctx, in) + return s.secretServer.GetSecrets(ctx, in) } func (s *server) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.CreateSecret(ctx, in) + return s.secretServer.CreateSecret(ctx, in) } func (s *server) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.DeleteSecret(ctx, in) + return s.secretServer.DeleteSecret(ctx, in) } func (s *server) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) { - return s.secretServer.UpdateSecret(ctx, in) + return s.secretServer.UpdateSecret(ctx, in) } func (s *server) PProf(ctx context.Context, in *PProfRequest) (reply *PProfData, err error) { - loader := s.getLoader(ctx) - defer loader.Close() - reply = &PProfData{ - Data: loader.PProf(in.Name), - } - return + loader := s.getLoader(ctx) + defer loader.Close() + reply = &PProfData{ + Data: loader.PProf(in.Name), + } + return } // implement the mock server // Start starts the mock server type mockServerController struct { - UnimplementedMockServer - mockWriter mock.ReaderAndWriter - loader mock.Loadable - reader mock.Reader - prefix string - combinePort int + UnimplementedMockServer + mockWriter mock.ReaderAndWriter + loader mock.Loadable + reader mock.Reader + prefix string + combinePort int } func NewMockServerController(mockWriter mock.ReaderAndWriter, loader mock.Loadable, combinePort int) MockServer { - return &mockServerController{ - mockWriter: mockWriter, - loader: loader, - prefix: "/mock/server", - combinePort: combinePort, - } + return &mockServerController{ + mockWriter: mockWriter, + loader: loader, + prefix: "/mock/server", + combinePort: combinePort, + } } func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (reply *Empty, err error) { - s.mockWriter.Write([]byte(in.Config)) - s.prefix = in.Prefix - if dServer, ok := s.loader.(mock.DynamicServer); ok && dServer.GetPort() != strconv.Itoa(int(in.GetPort())) { - if strconv.Itoa(s.combinePort) != dServer.GetPort() { - if stopErr := dServer.Stop(); stopErr != nil { - remoteServerLogger.Info("failed to stop old server", "error", stopErr) - } else { - remoteServerLogger.Info("old server stopped", "port", dServer.GetPort()) - } - } - - server := mock.NewInMemoryServer(int(in.GetPort())) - server.Start(s.mockWriter, in.Prefix) - s.loader = server - } - err = s.loader.Load() - return + s.mockWriter.Write([]byte(in.Config)) + s.prefix = in.Prefix + if dServer, ok := s.loader.(mock.DynamicServer); ok && dServer.GetPort() != strconv.Itoa(int(in.GetPort())) { + if strconv.Itoa(s.combinePort) != dServer.GetPort() { + if stopErr := dServer.Stop(); stopErr != nil { + remoteServerLogger.Info("failed to stop old server", "error", stopErr) + } else { + remoteServerLogger.Info("old server stopped", "port", dServer.GetPort()) + } + } + + server := mock.NewInMemoryServer(int(in.GetPort())) + server.Start(s.mockWriter, in.Prefix) + s.loader = server + } + err = s.loader.Load() + return } func (s *mockServerController) GetConfig(ctx context.Context, in *Empty) (reply *MockConfig, err error) { - reply = &MockConfig{ - Prefix: s.prefix, - Config: string(s.mockWriter.GetData()), - } - if dServer, ok := s.loader.(mock.DynamicServer); ok { - if port, pErr := strconv.ParseInt(dServer.GetPort(), 10, 32); pErr == nil { - reply.Port = int32(port) - } - } - return + reply = &MockConfig{ + Prefix: s.prefix, + Config: string(s.mockWriter.GetData()), + } + if dServer, ok := s.loader.(mock.DynamicServer); ok { + if port, pErr := strconv.ParseInt(dServer.GetPort(), 10, 32); pErr == nil { + reply.Port = int32(port) + } + } + return } func (s *server) getLoaderByStoreName(storeName string) (loader testing.Writer, err error) { - var store *testing.Store - store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName) - if err == nil && store != nil { - loader, err = s.storeWriterFactory.NewInstance(*store) - if err != nil { - err = fmt.Errorf("failed to new grpc loader from store %s, err: %v", store.Name, err) - } - } else { - err = fmt.Errorf("failed to get store %s, err: %v", storeName, err) - } - return + var store *testing.Store + store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName) + if err == nil && store != nil { + loader, err = s.storeWriterFactory.NewInstance(*store) + if err != nil { + err = fmt.Errorf("failed to new grpc loader from store %s, err: %v", store.Name, err) + } + } else { + err = fmt.Errorf("failed to get store %s, err: %v", storeName, err) + } + return } //go:embed data/headers.yaml var popularHeaders []byte func findParentTestCases(testcase *testing.TestCase, suite *testing.TestSuite) (testcases []testing.TestCase) { - reg, matchErr := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) - targetReg, targetErr := regexp.Compile(`\.\w*`) - - expectNames := new(UniqueSlice[string]) - if matchErr == nil && targetErr == nil { - var expectName string - for _, val := range testcase.Request.Header { - if matched := reg.MatchString(val); matched { - expectName = targetReg.FindString(val) - expectName = strings.TrimPrefix(expectName, ".") - expectNames.Push(expectName) - } - } - - findExpectNames(testcase.Request.API, expectNames) - findExpectNames(testcase.Request.Body.String(), expectNames) - - remoteServerLogger.Info("expect test case names", "name", expectNames.GetAll()) - for _, item := range suite.Items { - if expectNames.Exist(item.Name) { - testcases = append(testcases, item) - } - } - } - return + reg, matchErr := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) + targetReg, targetErr := regexp.Compile(`\.\w*`) + + expectNames := new(UniqueSlice[string]) + if matchErr == nil && targetErr == nil { + var expectName string + for _, val := range testcase.Request.Header { + if matched := reg.MatchString(val); matched { + expectName = targetReg.FindString(val) + expectName = strings.TrimPrefix(expectName, ".") + expectNames.Push(expectName) + } + } + + findExpectNames(testcase.Request.API, expectNames) + findExpectNames(testcase.Request.Body.String(), expectNames) + + remoteServerLogger.Info("expect test case names", "name", expectNames.GetAll()) + for _, item := range suite.Items { + if expectNames.Exist(item.Name) { + testcases = append(testcases, item) + } + } + } + return } func findExpectNames(target string, expectNames *UniqueSlice[string]) { - reg, _ := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) - targetReg, _ := regexp.Compile(`\.\w*`) + reg, _ := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`) + targetReg, _ := regexp.Compile(`\.\w*`) - for _, sub := range reg.FindStringSubmatch(target) { - // remove {{ and }} - if left, leftErr := regexp.Compile(`.*\{\{`); leftErr == nil { - body := left.ReplaceAllString(sub, "") + for _, sub := range reg.FindStringSubmatch(target) { + // remove {{ and }} + if left, leftErr := regexp.Compile(`.*\{\{`); leftErr == nil { + body := left.ReplaceAllString(sub, "") - expectName := targetReg.FindString(body) - expectName = strings.TrimPrefix(expectName, ".") - expectNames.Push(expectName) - } - } + expectName := targetReg.FindString(body) + expectName = strings.TrimPrefix(expectName, ".") + expectNames.Push(expectName) + } + } } // UniqueSlice represents an unique slice type UniqueSlice[T comparable] struct { - data []T + data []T } // Push pushes an item if it's not exist func (s *UniqueSlice[T]) Push(item T) *UniqueSlice[T] { - if s.data == nil { - s.data = []T{item} - } else { - for _, it := range s.data { - if it == item { - return s - } - } - s.data = append(s.data, item) - } - return s + if s.data == nil { + s.data = []T{item} + } else { + for _, it := range s.data { + if it == item { + return s + } + } + s.data = append(s.data, item) + } + return s } // Exist checks if the item exist, return true it exists func (s *UniqueSlice[T]) Exist(item T) bool { - if s.data != nil { - for _, it := range s.data { - if it == item { - return true - } - } - } - return false + if s.data != nil { + for _, it := range s.data { + if it == item { + return true + } + } + } + return false } // GetAll returns all the items func (s *UniqueSlice[T]) GetAll() []T { - return s.data + return s.data } var errNoTestSuiteFound = errors.New("no test suite found") diff --git a/pkg/testing/case.go b/pkg/testing/case.go index bb3c7bfc..6d411421 100644 --- a/pkg/testing/case.go +++ b/pkg/testing/case.go @@ -16,159 +16,159 @@ limitations under the License. package testing import ( - "encoding/base64" - "encoding/json" - "log" - "sort" - "strings" - "time" - - "github.com/linuxsuren/api-testing/pkg/util" - "gopkg.in/yaml.v3" + "encoding/base64" + "encoding/json" + "log" + "sort" + "strings" + "time" + + "github.com/linuxsuren/api-testing/pkg/util" + "gopkg.in/yaml.v3" ) // TestSuite represents a set of test cases type TestSuite struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - API string `yaml:"api,omitempty" json:"api,omitempty"` - Spec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` - Param map[string]string `yaml:"param,omitempty" json:"param,omitempty"` - Items []TestCase `yaml:"items,omitempty" json:"items,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + API string `yaml:"api,omitempty" json:"api,omitempty"` + Spec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` + Param map[string]string `yaml:"param,omitempty" json:"param,omitempty"` + Items []TestCase `yaml:"items,omitempty" json:"items,omitempty"` } type APISpec struct { - Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` - RPC *RPCDesc `yaml:"rpc,omitempty" json:"rpc,omitempty"` - Secure *Secure `yaml:"secure,omitempty" json:"secure,omitempty"` - Metric *Metric `yaml:"metric,omitempty" json:"metric,omitempty"` + Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` + RPC *RPCDesc `yaml:"rpc,omitempty" json:"rpc,omitempty"` + Secure *Secure `yaml:"secure,omitempty" json:"secure,omitempty"` + Metric *Metric `yaml:"metric,omitempty" json:"metric,omitempty"` } type HistoryTestSuite struct { - HistorySuiteName string `yaml:"name,omitempty" json:"name,omitempty"` - Items []HistoryTestCase `yaml:"items,omitempty" json:"items,omitempty"` + HistorySuiteName string `yaml:"name,omitempty" json:"name,omitempty"` + Items []HistoryTestCase `yaml:"items,omitempty" json:"items,omitempty"` } type HistoryTestCase struct { - ID string `yaml:"id,omitempty" json:"id,omitempty"` - CaseName string `yaml:"caseName,omitempty" json:"name,omitempty"` - SuiteName string `yaml:"suiteName,omitempty" json:"suiteName,omitempty"` - HistorySuiteName string `yaml:"historySuiteName,omitempty" json:"historySuiteName,omitempty"` - CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` - SuiteAPI string `yaml:"api,omitempty" json:"api,omitempty"` - SuiteSpec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` - SuiteParam map[string]string `yaml:"param,omitempty" json:"param,omitempty"` - Data TestCase `yaml:"data,omitempty" json:"data,omitempty"` - HistoryHeader map[string]string `yaml:"historyHeader,omitempty" json:"historyHeader,omitempty"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` + CaseName string `yaml:"caseName,omitempty" json:"name,omitempty"` + SuiteName string `yaml:"suiteName,omitempty" json:"suiteName,omitempty"` + HistorySuiteName string `yaml:"historySuiteName,omitempty" json:"historySuiteName,omitempty"` + CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` + SuiteAPI string `yaml:"api,omitempty" json:"api,omitempty"` + SuiteSpec APISpec `yaml:"spec,omitempty" json:"spec,omitempty"` + SuiteParam map[string]string `yaml:"param,omitempty" json:"param,omitempty"` + Data TestCase `yaml:"data,omitempty" json:"data,omitempty"` + HistoryHeader map[string]string `yaml:"historyHeader,omitempty" json:"historyHeader,omitempty"` } type HistoryTestResult struct { - Message string `yaml:"message,omitempty" json:"message,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - TestCaseResult []TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` - Data HistoryTestCase `yaml:"data,omitempty" json:"data,omitempty"` - CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + TestCaseResult []TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` + Data HistoryTestCase `yaml:"data,omitempty" json:"data,omitempty"` + CreateTime time.Time `yaml:"createTime,omitempty" json:"createTime,omitempty"` } type RPCDesc struct { - ImportPath []string `yaml:"import,omitempty" json:"import,omitempty"` - ServerReflection bool `yaml:"serverReflection,omitempty" json:"serverReflection,omitempty"` - ProtoFile string `yaml:"protofile,omitempty" json:"protofile,omitempty"` - ProtoSet string `yaml:"protoset,omitempty" json:"protoset,omitempty"` - Raw string `yaml:"raw,omitempty" json:"raw,omitempty"` + ImportPath []string `yaml:"import,omitempty" json:"import,omitempty"` + ServerReflection bool `yaml:"serverReflection,omitempty" json:"serverReflection,omitempty"` + ProtoFile string `yaml:"protofile,omitempty" json:"protofile,omitempty"` + ProtoSet string `yaml:"protoset,omitempty" json:"protoset,omitempty"` + Raw string `yaml:"raw,omitempty" json:"raw,omitempty"` } type Secure struct { - Insecure bool `yaml:"insecure,omitempty" json:"insecure,omitempty"` - CertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` - CAFile string `yaml:"ca,omitempty" json:"ca,omitempty"` - KeyFile string `yaml:"key,omitempty" json:"key,omitempty"` - ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"` + Insecure bool `yaml:"insecure,omitempty" json:"insecure,omitempty"` + CertFile string `yaml:"cert,omitempty" json:"cert,omitempty"` + CAFile string `yaml:"ca,omitempty" json:"ca,omitempty"` + KeyFile string `yaml:"key,omitempty" json:"key,omitempty"` + ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"` } type Metric struct { - Type string `yaml:"type,omitempty" json:"type,omitempty"` - URL string `yaml:"url,omitempty" json:"url,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + URL string `yaml:"url,omitempty" json:"url,omitempty"` } // TestCase represents a test case type TestCase struct { - ID string `yaml:"id,omitempty" json:"id,omitempty"` - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Group string `yaml:"group,omitempty" json:"group,omitempty"` - Before *Job `yaml:"before,omitempty" json:"before,omitempty"` - After *Job `yaml:"after,omitempty" json:"after,omitempty"` - Request Request `yaml:"request" json:"request"` - Expect Response `yaml:"expect,omitempty" json:"expect,omitempty"` + ID string `yaml:"id,omitempty" json:"id,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Group string `yaml:"group,omitempty" json:"group,omitempty"` + Before *Job `yaml:"before,omitempty" json:"before,omitempty"` + After *Job `yaml:"after,omitempty" json:"after,omitempty"` + Request Request `yaml:"request" json:"request"` + Expect Response `yaml:"expect,omitempty" json:"expect,omitempty"` } // InScope returns true if the test case is in scope with the given items. // Returns true if the items is empty. func (c *TestCase) InScope(items []string) bool { - if len(items) == 0 { - return true - } - for _, item := range items { - if item == c.Name { - return true - } - } - return false + if len(items) == 0 { + return true + } + for _, item := range items { + if item == c.Name { + return true + } + } + return false } // Job contains a list of jobs type Job struct { - Items []string `yaml:"items,omitempty" json:"items,omitempty"` + Items []string `yaml:"items,omitempty" json:"items,omitempty"` } // Request represents a HTTP request type Request struct { - API string `yaml:"api" json:"api"` - Method string `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"enum=GET,enum=POST,enum=PUT,enum=DELETE"` - Query SortedKeysStringMap `yaml:"query,omitempty" json:"query,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - Cookie map[string]string `yaml:"cookie,omitempty" json:"cookie,omitempty"` - Form map[string]string `yaml:"form,omitempty" json:"form,omitempty"` - Body RequestBody `yaml:"body,omitempty" json:"body,omitempty"` - BodyFromFile string `yaml:"bodyFromFile,omitempty" json:"bodyFromFile,omitempty"` + API string `yaml:"api" json:"api"` + Method string `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"enum=GET,enum=POST,enum=PUT,enum=DELETE"` + Query SortedKeysStringMap `yaml:"query,omitempty" json:"query,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + Cookie map[string]string `yaml:"cookie,omitempty" json:"cookie,omitempty"` + Form map[string]string `yaml:"form,omitempty" json:"form,omitempty"` + Body RequestBody `yaml:"body,omitempty" json:"body,omitempty"` + BodyFromFile string `yaml:"bodyFromFile,omitempty" json:"bodyFromFile,omitempty"` } type RequestBody struct { - Value string `json:"value" yaml:"value"` - isJson bool + Value string `json:"value" yaml:"value"` + isJson bool } func NewRequestBody(val string) RequestBody { - return RequestBody{Value: val} + return RequestBody{Value: val} } func (e *RequestBody) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { - gql := &GraphQLRequestBody{} - err = unmarshal(gql) - if err != nil { - val := "" - if err = unmarshal(&val); err == nil { - e.Value = val - } - } else { - var data []byte - if data, err = json.Marshal(gql); err == nil { - e.Value = string(data) - e.isJson = true - } - } - return + gql := &GraphQLRequestBody{} + err = unmarshal(gql) + if err != nil { + val := "" + if err = unmarshal(&val); err == nil { + e.Value = val + } + } else { + var data []byte + if data, err = json.Marshal(gql); err == nil { + e.Value = string(data) + e.isJson = true + } + } + return } func (e RequestBody) MarshalYAML() (val interface{}, err error) { - val = e.Value - if e.isJson { - gql := &GraphQLRequestBody{} - if err = json.Unmarshal([]byte(e.Value), gql); err == nil { - val = gql - } - } - return + val = e.Value + if e.isJson { + gql := &GraphQLRequestBody{} + if err = json.Unmarshal([]byte(e.Value), gql); err == nil { + val = gql + } + } + return } var _ yaml.Marshaler = &RequestBody{} @@ -176,136 +176,136 @@ var _ yaml.Marshaler = &RequestBody{} // var _ yaml.Unmarshaler = &RequestBody{} func (e RequestBody) String() string { - return e.Value + return e.Value } func (e RequestBody) IsEmpty() bool { - return e.Value == "" + return e.Value == "" } func (e RequestBody) Bytes() (data []byte) { - var err error - if strings.HasPrefix(e.Value, util.ImageBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.ImageBase64Prefix) - } else if strings.HasPrefix(e.Value, util.PDFBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.PDFBase64Prefix) - } else if strings.HasPrefix(e.Value, util.ZIPBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.ZIPBase64Prefix) - } else if strings.HasPrefix(e.Value, util.BinaryBase64Prefix) { - data, err = decodeBase64Body(e.Value, util.BinaryBase64Prefix) - } else { - data = []byte(e.Value) - } - - if err != nil { - log.Printf("Error decoding: %v", err) - } - return + var err error + if strings.HasPrefix(e.Value, util.ImageBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.ImageBase64Prefix) + } else if strings.HasPrefix(e.Value, util.PDFBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.PDFBase64Prefix) + } else if strings.HasPrefix(e.Value, util.ZIPBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.ZIPBase64Prefix) + } else if strings.HasPrefix(e.Value, util.BinaryBase64Prefix) { + data, err = decodeBase64Body(e.Value, util.BinaryBase64Prefix) + } else { + data = []byte(e.Value) + } + + if err != nil { + log.Printf("Error decoding: %v", err) + } + return } func decodeBase64Body(raw, prefix string) ([]byte, error) { - rawStr := strings.TrimPrefix(raw, prefix) - return base64.StdEncoding.DecodeString(rawStr) + rawStr := strings.TrimPrefix(raw, prefix) + return base64.StdEncoding.DecodeString(rawStr) } type GraphQLRequestBody struct { - Query string `yaml:"query" json:"query"` - OperationName string `yaml:"operationName" json:"operationName"` - Variables map[string]string `yaml:"variables" json:"variables"` + Query string `yaml:"query" json:"query"` + OperationName string `yaml:"operationName" json:"operationName"` + Variables map[string]string `yaml:"variables" json:"variables"` } // Response is the expected response type Response struct { - StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` - Body string `yaml:"body,omitempty" json:"body,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - BodyFieldsExpect map[string]interface{} `yaml:"bodyFieldsExpect,omitempty" json:"bodyFieldsExpect,omitempty"` - Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` - ConditionalVerify []ConditionalVerify `yaml:"conditionalVerify,omitempty" json:"conditionalVerify,omitempty"` - Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` + StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` + Body string `yaml:"body,omitempty" json:"body,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + BodyFieldsExpect map[string]interface{} `yaml:"bodyFieldsExpect,omitempty" json:"bodyFieldsExpect,omitempty"` + Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` + ConditionalVerify []ConditionalVerify `yaml:"conditionalVerify,omitempty" json:"conditionalVerify,omitempty"` + Schema string `yaml:"schema,omitempty" json:"schema,omitempty"` } func (r Response) GetBody() string { - return r.Body + return r.Body } func (r Response) GetBodyFieldsExpect() map[string]interface{} { - return r.BodyFieldsExpect + return r.BodyFieldsExpect } type ConditionalVerify struct { - Condition []string `yaml:"condition,omitempty" json:"condition,omitempty"` - Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` + Condition []string `yaml:"condition,omitempty" json:"condition,omitempty"` + Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"` } type SortedKeysStringMap map[string]interface{} func (m SortedKeysStringMap) Keys() (keys []string) { - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - return + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return } func (m SortedKeysStringMap) GetValue(key string) string { - val := m[key] + val := m[key] - switch o := any(val).(type) { - case string: - return val.(string) - case map[string]interface{}: - verifier := convertToVerifier(o) - return verifier.Value - case *Verifier: - return o.Value - } + switch o := any(val).(type) { + case string: + return val.(string) + case map[string]interface{}: + verifier := convertToVerifier(o) + return verifier.Value + case *Verifier: + return o.Value + } - return "" + return "" } func (m SortedKeysStringMap) GetVerifier(key string) (verifier *Verifier) { - val := m[key] + val := m[key] - switch o := any(val).(type) { - case map[string]interface{}: - verifier = convertToVerifier(o) - } + switch o := any(val).(type) { + case map[string]interface{}: + verifier = convertToVerifier(o) + } - return + return } func convertToVerifier(data map[string]interface{}) (verifier *Verifier) { - verifier = &Verifier{} + verifier = &Verifier{} - if data, err := yaml.Marshal(data); err == nil { - if err = yaml.Unmarshal(data, verifier); err != nil { - verifier = nil - } - } - return + if data, err := yaml.Marshal(data); err == nil { + if err = yaml.Unmarshal(data, verifier); err != nil { + verifier = nil + } + } + return } type Verifier struct { - Value string `yaml:"value,omitempty" json:"value,omitempty"` - Required bool `yaml:"required,omitempty" json:"required,omitempty"` - Max int `yaml:"max"` - Min int `yaml:"min"` - MaxLength int `yaml:"maxLength"` - MinLength int `yaml:"minLength"` + Value string `yaml:"value,omitempty" json:"value,omitempty"` + Required bool `yaml:"required,omitempty" json:"required,omitempty"` + Max int `yaml:"max"` + Min int `yaml:"min"` + MaxLength int `yaml:"maxLength"` + MinLength int `yaml:"minLength"` } type TestResult struct { - Message string `yaml:"message,omitempty" json:"message,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - TestCaseResult []*TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` + Message string `yaml:"message,omitempty" json:"message,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + TestCaseResult []*TestCaseResult `yaml:"testCaseResult,omitempty" json:"testCaseResult,omitempty"` } type TestCaseResult struct { - StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` - Body string `yaml:"body,omitempty" json:"body,omitempty"` - Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` - Error string `yaml:"error,omitempty" json:"error,omitempty"` - Id string `yaml:"id,omitempty" json:"id,omitempty"` - Output string `yaml:"output,omitempty" json:"output,omitempty"` + StatusCode int `yaml:"statusCode,omitempty" json:"statusCode,omitempty"` + Body string `yaml:"body,omitempty" json:"body,omitempty"` + Header map[string]string `yaml:"header,omitempty" json:"header,omitempty"` + Error string `yaml:"error,omitempty" json:"error,omitempty"` + Id string `yaml:"id,omitempty" json:"id,omitempty"` + Output string `yaml:"output,omitempty" json:"output,omitempty"` } From ee80abddab1f6ca075f20c85e6bdd5c834c53fa6 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Sun, 12 Jan 2025 22:43:14 +0800 Subject: [PATCH 04/10] fix: update check workflows names Signed-off-by: yuluo-yx --- .github/workflows/check.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 521eec10..59329a34 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -1,4 +1,5 @@ -name: Hugo Docs +name: File Lint Check + on: push: branches: From f4f1972554cf8d4f9257c101a541127743b507b9 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Fri, 7 Feb 2025 22:34:47 +0800 Subject: [PATCH 05/10] fix: fix node tools install Signed-off-by: yuluo-yx --- .github/workflows/build.yaml | 6 +++--- .github/workflows/check.yaml | 4 ++-- .github/workflows/docs.yaml | 10 +++++----- tools/make/lint.mk | 4 ---- tools/src/markdownlint/package.json | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7669c73a..f6ebe67b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: pull-requests: write runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@v4.1.4 - uses: ./tools/github-actions/setup-deps - name: API Test env: @@ -65,7 +65,7 @@ jobs: Build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@v4.1.4 - uses: ./tools/github-actions/setup-deps - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 @@ -80,7 +80,7 @@ jobs: E2E: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@v4.1.4 - uses: ./tools/github-actions/setup-deps - name: Set output id: vars diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 59329a34..3fc5a0ff 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -15,10 +15,10 @@ jobs: files-lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@v4.1.6 - uses: ./tools/github-actions/setup-deps - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.1.0 + uses: actions/setup-node@v4 with: node-version: "21" diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6411007d..3b4c386b 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -20,7 +20,7 @@ jobs: contents: write steps: - name: Git checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@v4.1.6 with: submodules: true ref: ${{ github.event.pull_request.head.sha }} @@ -28,13 +28,13 @@ jobs: - uses: ./tools/github-actions/setup-deps - name: Setup Hugo - uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 + uses: peaceiris/actions-hugo@v3.0.0 with: hugo-version: "latest" extended: true - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.1.0 + uses: actions/setup-node@v4.1.0 with: node-version: "18" @@ -48,7 +48,7 @@ jobs: # Upload docs for GitHub Pages - name: Upload GitHub Pages artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + uses: actions/upload-pages-artifact@v3.0.1 with: # Path of the directory containing the static assets. path: docs/site/public @@ -75,4 +75,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 + uses: actions/deploy-pages@v4.0.5 diff --git a/tools/make/lint.mk b/tools/make/lint.mk index 29b7ba14..e61188d1 100644 --- a/tools/make/lint.mk +++ b/tools/make/lint.mk @@ -4,8 +4,6 @@ ##@ Lint -GITHUB_ACTION ?= - .PHONY: lint lint: ## Run all linter of code sources, including golint, yamllint. @@ -14,8 +12,6 @@ lint: ## Run all linter of code sources, including golint, yamllint. .PHONY: lint-deps lint-deps: ## Everything necessary to lint -GOLANGCI_LINT_FLAGS ?= $(if $(GITHUB_ACTION),--out-format=github-actions) - .PHONY: lint.golint lint: lint.golint lint-deps: $(tools/golangci-lint) diff --git a/tools/src/markdownlint/package.json b/tools/src/markdownlint/package.json index 9311cad8..b638d1dd 100644 --- a/tools/src/markdownlint/package.json +++ b/tools/src/markdownlint/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "markdownlint": "^0.37.3" + "markdownlint-cli": "^0.44.0" } } From 31eb9d4d4a53f087ac11757c693d6477c6d0df67 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Fri, 7 Feb 2025 22:47:26 +0800 Subject: [PATCH 06/10] fix: fix makefile tools install Signed-off-by: yuluo-yx --- tools/make/lint.mk | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/make/lint.mk b/tools/make/lint.mk index e61188d1..854373ca 100644 --- a/tools/make/lint.mk +++ b/tools/make/lint.mk @@ -29,14 +29,14 @@ lint.yamllint: $(tools/yamllint) .PHONY: lint.markdown lint: lint.markdown lint-deps: $(tools/markdownlint) -lint.markdown: +lint.markdown: $(tools/markdownlint) @$(LOG_TARGET) $(tools/markdownlint) -c tools/linter/markdownlint/markdown_lint_config.json docs/site/content/** .PHONY: lint.markdown.fix lint: lint.markdown.fix lint-deps: $(tools/markdownlint) -lint.markdown.fix: +lint.markdown.fix: $(tools/markdownlint) @$(LOG_TARGET) $(tools/markdownlint) -c tools/linter/markdownlint/markdown_lint_config.json --fix docs/site/content/** @@ -44,20 +44,20 @@ lint.markdown.fix: lint: lint.codespell lint-deps: $(tools/codespell) lint.codespell: CODESPELL_SKIP := $(shell cat tools/linter/codespell/.codespell.skip | tr \\n ',') -lint.codespell: +lint.codespell: $(tools/codespell) @$(LOG_TARGET) $(tools/codespell) --skip $(CODESPELL_SKIP) --ignore-words tools/linter/codespell/.codespell.ignorewords --check-filenames .PHONY: lint.checklinks lint: lint.checklinks lint-deps: $(tools/linkinator) -lint.checklinks: # Check for broken links in the docs +lint.checklinks: $(tools/linkinator) # Check for broken links in the docs @$(LOG_TARGET) $(tools/linkinator) docs/site/public/ -r --concurrency 25 --skip $(LINKINATOR_IGNORE) .PHONY: lint.checklicense lint: lint.checklicense lint-deps: $(tools/skywalking-eyes) -lint.checklicense: # Check for broken links in the docs +lint.checklicense: $(tools/skywalking-eyes) # Check for broken links in the docs @$(LOG_TARGET) $(tools/skywalking-eyes) -c tools/linter/license/.licenserc.yaml header check From 71f9fc06bcff1a617808ae2957e6e56d2570f320 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Fri, 7 Feb 2025 22:52:58 +0800 Subject: [PATCH 07/10] fix: fix makefile tools install deps Signed-off-by: yuluo-yx --- tools/make/tools.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/make/tools.mk b/tools/make/tools.mk index 83c52deb..cb928dd3 100644 --- a/tools/make/tools.mk +++ b/tools/make/tools.mk @@ -12,6 +12,7 @@ tools/buf = $(tools.bindir)/buf tools/skywalking-eyes = $(tools.bindir)/skywalking-eyes $(tools.bindir)/%: $(tools.srcdir)/%/pin.go $(tools.srcdir)/%/go.mod + mkdir -p $(@D) cd $( Date: Fri, 7 Feb 2025 22:54:54 +0800 Subject: [PATCH 08/10] debug Signed-off-by: yuluo-yx --- .github/workflows/check.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 3fc5a0ff..85c2b9ca 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -24,6 +24,7 @@ jobs: - name: Markdown Lint check run: | + ls -ahl tools/bin make lint.markdown - name: Yaml Lint check From 5eb30c67daa8e72196d450183c7a23716163d88b Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Fri, 7 Feb 2025 22:55:24 +0800 Subject: [PATCH 09/10] debug Signed-off-by: yuluo-yx --- tools/make/tools.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/make/tools.mk b/tools/make/tools.mk index cb928dd3..355282fb 100644 --- a/tools/make/tools.mk +++ b/tools/make/tools.mk @@ -39,6 +39,7 @@ $(tools.bindir)/%: $(tools.srcdir)/%/package.json mkdir -p $(@D) cd $( Date: Fri, 7 Feb 2025 22:56:54 +0800 Subject: [PATCH 10/10] debug Signed-off-by: yuluo-yx --- .github/workflows/check.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 85c2b9ca..3fc5a0ff 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -24,7 +24,6 @@ jobs: - name: Markdown Lint check run: | - ls -ahl tools/bin make lint.markdown - name: Yaml Lint check