Skip to content

Commit 9088297

Browse files
authored
feat: support convert testSuite to JMeter file (#170)
* feat: support convert testSuite to JMeter file * chore: report API test in the comment * add a sub-command to convert to jmeter file --------- Co-authored-by: rick <[email protected]>
1 parent 2001340 commit 9088297

26 files changed

+1165
-119
lines changed

.github/testing/core.yaml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
33
# https://docs.gitlab.com/ee/api/api_resources.html
44
name: atest
5-
api: http://localhost:8080/server.Runner
5+
api: |
6+
{{default "http://localhost:8080/server.Runner" (env "SERVER")}}
67
items:
78
- name: suites
89
request:
@@ -80,13 +81,50 @@ items:
8081
request:
8182
api: /PopularHeaders
8283
method: POST
84+
8385
- name: list-code-generators
8486
request:
8587
api: /ListCodeGenerator
8688
method: POST
8789
expect:
8890
verify:
8991
- len(data) == 1
92+
- name: GenerateCode
93+
request:
94+
api: /GenerateCode
95+
method: POST
96+
body: |
97+
{
98+
"TestSuite": "{{index (keys .suites.data) 0}}",
99+
"TestCase": "{{randAlpha 6}}",
100+
"Generator": "golang"
101+
}
102+
expect:
103+
statusCode: 500 # no testcase found
104+
verify:
105+
- indexOf(data.message, "not found") != -1
106+
107+
- name: listConverters
108+
request:
109+
api: /ListConverter
110+
method: POST
111+
expect:
112+
verify:
113+
- len(data) == 1
114+
- name: ConvertTestSuite
115+
request:
116+
api: /ConvertTestSuite
117+
method: POST
118+
body: |
119+
{
120+
"TestSuite": "{{index (keys .suites.data) 0}}",
121+
"Generator": "jmeter"
122+
}
123+
expect:
124+
verify:
125+
- data.message != ""
126+
- indexOf(data.message, "jmeterTestPlan") != -1
127+
90128
- name: list-stores
91129
request:
92130
api: /GetStores

.github/workflows/build.yaml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,36 @@ jobs:
2727
bash <(curl -Ls https://coverage.codacy.com/get.sh) report --partial --force-coverage-parser go -r store-git-coverage.out
2828
bash <(curl -Ls https://coverage.codacy.com/get.sh) report --partial --force-coverage-parser go -r operator/cover.out
2929
bash <(curl -Ls https://coverage.codacy.com/get.sh) final
30+
31+
APITest:
32+
runs-on: ubuntu-20.04
33+
steps:
34+
- name: Set up Go
35+
uses: actions/setup-go@v3
36+
with:
37+
go-version: 1.18.x
38+
- uses: actions/[email protected]
3039
- name: API Test
3140
run: |
3241
make build copy
3342
sudo atest service install
3443
sudo atest service restart
3544
sudo atest service status
36-
atest run -p .github/testing/core.yaml --request-ignore-error
45+
atest run -p .github/testing/core.yaml --request-ignore-error --report md --report-file .github/workflows/report.md
3746
sudo atest service status
3847
48+
atest convert -p .github/testing/core.yaml --converter jmeter -t sample.jmx
49+
- name: Report API Test
50+
uses: harupy/comment-on-pr@c0522c44600040927a120b9309b531e3cb6f8d48
51+
env:
52+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
with:
54+
filename: report.md
55+
- name: Run JMeter Tests
56+
uses: rbhadti94/[email protected]
57+
with:
58+
testFilePath: sample.jmx
59+
3960
Build:
4061
runs-on: ubuntu-20.04
4162
steps:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ This is a API testing tool.
99
## Features
1010

1111
* Multiple test report formats: Markdown, HTML, PDF, Stdout
12-
* Response Body fields equation check
13-
* Response Body [eval](https://expr.medv.io/)
12+
* Support converting to [JMeter](https://jmeter.apache.org/) files
13+
* Response Body fields equation check or [eval](https://expr.medv.io/)
1414
* Verify the Kubernetes resources
1515
* Validate the response body with [JSON schema](https://json-schema.org/)
1616
* Pre and post handle with the API request

cmd/convert.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
MIT License
3+
4+
Copyright (c) 2023 API Testing Authors.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
package cmd
26+
27+
import (
28+
"fmt"
29+
"os"
30+
31+
"github.com/linuxsuren/api-testing/pkg/generator"
32+
"github.com/linuxsuren/api-testing/pkg/testing"
33+
"github.com/linuxsuren/api-testing/pkg/util"
34+
"github.com/spf13/cobra"
35+
)
36+
37+
func createConvertCommand() (c *cobra.Command) {
38+
opt := &convertOption{}
39+
c = &cobra.Command{
40+
Use: "convert",
41+
Short: "Convert the API testing file to other format",
42+
PreRunE: opt.preRunE,
43+
RunE: opt.runE,
44+
}
45+
46+
converters := generator.GetTestSuiteConverters()
47+
48+
flags := c.Flags()
49+
flags.StringVarP(&opt.pattern, "pattern", "p", "test-suite-*.yaml",
50+
"The file pattern which try to execute the test cases. Brace expansion is supported, such as: test-suite-{1,2}.yaml")
51+
flags.StringVarP(&opt.converter, "converter", "", "",
52+
fmt.Sprintf("The converter format, supported: %s", util.Keys(converters)))
53+
flags.StringVarP(&opt.target, "target", "t", "", "The target file path")
54+
55+
_ = c.MarkFlagRequired("pattern")
56+
_ = c.MarkFlagRequired("converter")
57+
return
58+
}
59+
60+
type convertOption struct {
61+
pattern string
62+
converter string
63+
target string
64+
}
65+
66+
func (o *convertOption) preRunE(c *cobra.Command, args []string) (err error) {
67+
if o.target == "" {
68+
o.target = "sample.jmx"
69+
}
70+
return
71+
}
72+
73+
func (o *convertOption) runE(c *cobra.Command, args []string) (err error) {
74+
loader := testing.NewFileWriter("")
75+
if err = loader.Put(o.pattern); err != nil {
76+
return
77+
}
78+
79+
var output string
80+
var suites []testing.TestSuite
81+
if suites, err = loader.ListTestSuite(); err == nil {
82+
if len(suites) == 0 {
83+
err = fmt.Errorf("no suites found")
84+
} else {
85+
converter := generator.GetTestSuiteConverter(o.converter)
86+
if converter == nil {
87+
err = fmt.Errorf("no converter found")
88+
} else {
89+
output, err = converter.Convert(&suites[0])
90+
}
91+
}
92+
}
93+
94+
if output != "" {
95+
err = os.WriteFile(o.target, []byte(output), 0644)
96+
}
97+
return
98+
}

cmd/convert_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
MIT License
3+
4+
Copyright (c) 2023 API Testing Authors.
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
package cmd_test
26+
27+
import (
28+
"io"
29+
"os"
30+
"path"
31+
"testing"
32+
"time"
33+
34+
"github.com/linuxsuren/api-testing/cmd"
35+
"github.com/linuxsuren/api-testing/pkg/server"
36+
fakeruntime "github.com/linuxsuren/go-fake-runtime"
37+
"github.com/stretchr/testify/assert"
38+
)
39+
40+
func TestConvert(t *testing.T) {
41+
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"},
42+
cmd.NewFakeGRPCServer(), server.NewFakeHTTPServer())
43+
c.SetOut(io.Discard)
44+
45+
t.Run("normal", func(t *testing.T) {
46+
tmpFile := path.Join(os.TempDir(), time.Now().String())
47+
defer os.RemoveAll(tmpFile)
48+
49+
c.SetArgs([]string{"convert", "-p=testdata/simple-suite.yaml", "--converter=jmeter", "--target", tmpFile})
50+
51+
err := c.Execute()
52+
assert.NoError(t, err)
53+
54+
var data []byte
55+
data, err = os.ReadFile(tmpFile)
56+
if assert.NoError(t, err) {
57+
assert.NotEmpty(t, string(data))
58+
}
59+
})
60+
61+
t.Run("no testSuite", func(t *testing.T) {
62+
c.SetArgs([]string{"convert", "-p=testdata/fake.yaml", "--converter=jmeter"})
63+
64+
err := c.Execute()
65+
assert.Error(t, err)
66+
})
67+
68+
t.Run("no converter found", func(t *testing.T) {
69+
c.SetArgs([]string{"convert", "-p=testdata/simple-suite.yaml", "--converter=fake"})
70+
71+
err := c.Execute()
72+
assert.Error(t, err)
73+
})
74+
75+
t.Run("flag --pattern is required", func(t *testing.T) {
76+
c.SetArgs([]string{"convert", "--converter=fake"})
77+
78+
err := c.Execute()
79+
assert.Error(t, err)
80+
})
81+
82+
t.Run("flag --converter is required", func(t *testing.T) {
83+
c.SetArgs([]string{"convert", "-p=fake"})
84+
85+
err := c.Execute()
86+
assert.Error(t, err)
87+
})
88+
}

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func NewRootCmd(execer fakeruntime.Execer, gRPCServer gRPCServer,
2121
c.AddCommand(createInitCommand(execer),
2222
createRunCommand(), createSampleCmd(),
2323
createServerCmd(execer, gRPCServer, httpServer), createJSONSchemaCmd(),
24-
createServiceCommand(execer), createFunctionCmd())
24+
createServiceCommand(execer), createFunctionCmd(), createConvertCommand())
2525
return
2626
}
2727

cmd/run.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"io"
77
"os"
8-
"strings"
98
"sync"
109
"time"
1110

@@ -239,10 +238,7 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter
239238
continue
240239
}
241240

242-
// reuse the API prefix
243-
if strings.HasPrefix(testCase.Request.API, "/") {
244-
testCase.Request.API = fmt.Sprintf("%s%s", testSuite.API, testCase.Request.API)
245-
}
241+
testCase.Request.RenderAPI(testSuite.API)
246242

247243
var output interface{}
248244
select {

console/atest-ui/src/views/TestSuite.vue

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,43 @@ function del() {
151151
})
152152
}
153153
154+
function convert() {
155+
const requestOptions = {
156+
method: 'POST',
157+
headers: {
158+
'X-Store-Name': props.store
159+
},
160+
body: JSON.stringify({
161+
Generator: 'jmeter',
162+
TestSuite: props.name
163+
})
164+
}
165+
fetch('/server.Runner/ConvertTestSuite', requestOptions)
166+
.then((response) => response.json())
167+
.then((e) => {
168+
const blob = new Blob([e.message], { type: `text/xml;charset=utf-8;` });
169+
const link = document.createElement('a');
170+
if (link.download !== undefined) {
171+
const url = URL.createObjectURL(blob);
172+
link.setAttribute('href', url);
173+
link.setAttribute('download', `jmeter.jmx`);
174+
link.style.visibility = 'hidden';
175+
document.body.appendChild(link);
176+
link.click();
177+
document.body.removeChild(link);
178+
}
179+
180+
ElMessage({
181+
message: 'Converted.',
182+
type: 'success'
183+
})
184+
emit('updated')
185+
})
186+
.catch((e) => {
187+
ElMessage.error('Oops, ' + e)
188+
})
189+
}
190+
154191
const suiteCreatingLoading = ref(false)
155192
156193
const apiSpecKinds = [
@@ -218,6 +255,8 @@ function paramChange() {
218255
<el-button type="primary" @click="del" test-id="suite-del-but">Delete</el-button>
219256

220257
<el-button type="primary" @click="openNewTestCaseDialog" :icon="Edit" test-id="open-new-case-dialog">New TestCase</el-button>
258+
259+
<el-button type="primary" @click="convert" test-id="convert">Convert</el-button>
221260
</div>
222261

223262
<el-dialog v-model="dialogVisible" title="Create Test Case" width="40%" draggable>

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.18
55
require (
66
github.com/Masterminds/sprig/v3 v3.2.3
77
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
8-
github.com/antonmedv/expr v1.12.1
8+
github.com/antonmedv/expr v1.14.0
99
github.com/bufbuild/protocompile v0.6.0
1010
github.com/cucumber/godog v0.12.6
1111
github.com/flopp/go-findfont v0.1.0

0 commit comments

Comments
 (0)