diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cbc2943f5..05dab2d45 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -44,12 +44,18 @@ jobs: sudo atest service status atest run -p .github/testing/core.yaml --request-ignore-error --report md --report-file .github/workflows/report.md sudo atest service status + + atest convert -p .github/testing/core.yaml -t jmeter > sample.jmx - name: Report API Test uses: harupy/comment-on-pr@c0522c44600040927a120b9309b531e3cb6f8d48 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: filename: report.md + - name: Run JMeter Tests + uses: rbhadti94/apache-jmeter-action@v0.5.0 + with: + testFilePath: sample.jmx Build: runs-on: ubuntu-20.04 diff --git a/cmd/convert.go b/cmd/convert.go new file mode 100644 index 000000000..677c3aff1 --- /dev/null +++ b/cmd/convert.go @@ -0,0 +1,84 @@ +/* +MIT License + +Copyright (c) 2023 API Testing Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package cmd + +import ( + "fmt" + + "github.com/linuxsuren/api-testing/pkg/generator" + "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/linuxsuren/api-testing/pkg/util" + "github.com/spf13/cobra" +) + +func createConvertCommand() (c *cobra.Command) { + opt := &convertOption{} + c = &cobra.Command{ + Use: "convert", + Short: "Convert the API testing file to other format", + RunE: opt.runE, + } + + converters := generator.GetTestSuiteConverters() + + flags := c.Flags() + flags.StringVarP(&opt.target, "target", "t", "", + fmt.Sprintf("The target format, supported: %s", util.Keys(converters))) + flags.StringVarP(&opt.pattern, "pattern", "p", "test-suite-*.yaml", + "The file pattern which try to execute the test cases. Brace expansion is supported, such as: test-suite-{1,2}.yaml") + return +} + +type convertOption struct { + target string + pattern string +} + +func (o *convertOption) runE(c *cobra.Command, args []string) (err error) { + loader := testing.NewFileWriter("") + if err = loader.Put(o.pattern); err != nil { + return + } + + var output string + var suites []testing.TestSuite + if suites, err = loader.ListTestSuite(); err == nil { + if len(suites) == 0 { + err = fmt.Errorf("no suites found") + } else { + converter := generator.GetTestSuiteConverter(o.target) + if converter == nil { + err = fmt.Errorf("no converter found") + } else { + output, err = converter.Convert(&suites[0]) + } + } + } + + if output != "" { + c.Println(output) + } + return +} diff --git a/cmd/convert_test.go b/cmd/convert_test.go new file mode 100644 index 000000000..2e43ee092 --- /dev/null +++ b/cmd/convert_test.go @@ -0,0 +1,67 @@ +/* +MIT License + +Copyright (c) 2023 API Testing Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package cmd_test + +import ( + "bytes" + "io" + "testing" + + "github.com/linuxsuren/api-testing/cmd" + "github.com/linuxsuren/api-testing/pkg/server" + fakeruntime "github.com/linuxsuren/go-fake-runtime" + "github.com/stretchr/testify/assert" +) + +func TestConvert(t *testing.T) { + c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, + cmd.NewFakeGRPCServer(), server.NewFakeHTTPServer()) + + t.Run("normal", func(t *testing.T) { + buf := new(bytes.Buffer) + c.SetOut(buf) + c.SetArgs([]string{"convert", "-p=testdata/simple-suite.yaml", "-t=jmeter"}) + + err := c.Execute() + assert.NoError(t, err) + assert.NotEmpty(t, buf.String()) + }) + + t.Run("no testSuite", func(t *testing.T) { + c.SetOut(io.Discard) + c.SetArgs([]string{"convert", "-p=testdata/fake.yaml", "-t=jmeter"}) + + err := c.Execute() + assert.Error(t, err) + }) + + t.Run("no converter found", func(t *testing.T) { + c.SetOut(io.Discard) + c.SetArgs([]string{"convert", "-p=testdata/simple-suite.yaml", "-t=fake"}) + + err := c.Execute() + assert.Error(t, err) + }) +} diff --git a/cmd/root.go b/cmd/root.go index 41c87fed0..1ed70607b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,7 +21,7 @@ func NewRootCmd(execer fakeruntime.Execer, gRPCServer gRPCServer, c.AddCommand(createInitCommand(execer), createRunCommand(), createSampleCmd(), createServerCmd(execer, gRPCServer, httpServer), createJSONSchemaCmd(), - createServiceCommand(execer), createFunctionCmd()) + createServiceCommand(execer), createFunctionCmd(), createConvertCommand()) return } diff --git a/cmd/run.go b/cmd/run.go index 25c782d8d..299b677b5 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "os" - "strings" "sync" "time" @@ -239,10 +238,7 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter continue } - // reuse the API prefix - if strings.HasPrefix(testCase.Request.API, "/") { - testCase.Request.API = fmt.Sprintf("%s%s", testSuite.API, testCase.Request.API) - } + testCase.Request.RenderAPI(testSuite.API) var output interface{} select { diff --git a/go.work.sum b/go.work.sum index 122dca3ad..7fcac3e92 100644 --- a/go.work.sum +++ b/go.work.sum @@ -874,3 +874,4 @@ google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwS google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= diff --git a/pkg/generator/converter_jmeter.go b/pkg/generator/converter_jmeter.go index 65cfdc2d1..7692cc400 100644 --- a/pkg/generator/converter_jmeter.go +++ b/pkg/generator/converter_jmeter.go @@ -39,18 +39,25 @@ func init() { } func (c *jmeterConverter) Convert(testSuite *testing.TestSuite) (result string, err error) { - jmeterTestPlan := c.buildJmeterTestPlan(testSuite) - - var data []byte - if data, err = xml.MarshalIndent(jmeterTestPlan, " ", " "); err == nil { - result = string(data) + var jmeterTestPlan *JmeterTestPlan + if jmeterTestPlan, err = c.buildJmeterTestPlan(testSuite); err == nil { + var data []byte + if data, err = xml.MarshalIndent(jmeterTestPlan, " ", " "); err == nil { + result = string(data) + } } return } -func (c *jmeterConverter) buildJmeterTestPlan(testSuite *testing.TestSuite) (result *JmeterTestPlan) { +func (c *jmeterConverter) buildJmeterTestPlan(testSuite *testing.TestSuite) (result *JmeterTestPlan, err error) { + if err = testSuite.Render(make(map[string]interface{})); err != nil { + return + } + requestItems := []interface{}{} for _, item := range testSuite.Items { + item.Request.RenderAPI(testSuite.API) + api, err := url.Parse(item.Request.API) if err != nil { continue diff --git a/pkg/generator/converter_jmeter_test.go b/pkg/generator/converter_jmeter_test.go index ff0ea1a3d..288b7a305 100644 --- a/pkg/generator/converter_jmeter_test.go +++ b/pkg/generator/converter_jmeter_test.go @@ -44,11 +44,12 @@ func TestJmeterConvert(t *testing.T) { testSuite := &atest.TestSuite{ Name: "API Testing", + API: "http://localhost:8080", Items: []atest.TestCase{{ Name: "hello-jmeter", Request: atest.Request{ Method: "POST", - API: "http://localhost:8080/GetSuites", + API: "/GetSuites", }, }}, } diff --git a/pkg/server/remote_server.go b/pkg/server/remote_server.go index fa2ac8460..6917adefd 100644 --- a/pkg/server/remote_server.go +++ b/pkg/server/remote_server.go @@ -204,9 +204,7 @@ func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, er suiteRunner.WithWriteLevel(task.Level) // reuse the API prefix - if strings.HasPrefix(testCase.Request.API, "/") { - testCase.Request.API = fmt.Sprintf("%s%s", suite.API, testCase.Request.API) - } + testCase.Request.RenderAPI(suite.API) output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx) if getter, ok := suiteRunner.(runner.HTTPResponseRecord); ok { diff --git a/pkg/testing/parser.go b/pkg/testing/parser.go index 3faabb235..edb6cdee4 100644 --- a/pkg/testing/parser.go +++ b/pkg/testing/parser.go @@ -160,6 +160,14 @@ func (r *Request) Render(ctx interface{}, dataDir string) (err error) { return } +// RenderAPI will combine with the base API +func (r *Request) RenderAPI(base string) { + // reuse the API prefix + if strings.HasPrefix(r.API, "/") { + r.API = fmt.Sprintf("%s%s", base, r.API) + } +} + // GetBody returns the request body func (r *Request) GetBody() (reader io.Reader, err error) { if len(r.Form) > 0 { diff --git a/pkg/util/map.go b/pkg/util/map.go new file mode 100644 index 000000000..6a76afe3b --- /dev/null +++ b/pkg/util/map.go @@ -0,0 +1,36 @@ +/** +MIT License + +Copyright (c) 2023 API Testing Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package util + +// Keys returns a list of keys +func Keys[T interface{}](data map[string]T) (keys []string) { + keys = make([]string, len(data)) + index := 0 + for k := range data { + keys[index] = k + index++ + } + return +} diff --git a/pkg/util/map_test.go b/pkg/util/map_test.go new file mode 100644 index 000000000..7af0ee6de --- /dev/null +++ b/pkg/util/map_test.go @@ -0,0 +1,39 @@ +/** +MIT License + +Copyright (c) 2023 API Testing Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package util_test + +import ( + "testing" + + "github.com/linuxsuren/api-testing/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestKeys(t *testing.T) { + t.Run("normal", func(t *testing.T) { + assert.Equal(t, []string{"foo", "bar"}, + util.Keys(map[string]interface{}{"foo": "xx", "bar": "xx"})) + }) +}