Skip to content

Commit bd78fa4

Browse files
authored
feat: support to load test suite from URL (#171)
Co-authored-by: rick <[email protected]>
1 parent 9088297 commit bd78fa4

File tree

8 files changed

+250
-10
lines changed

8 files changed

+250
-10
lines changed

docs/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ k3s kubectl apply -k sample/kubernetes/default
4747
kustomize build sample/kubernetes/docker.io/ | k3s kubectl apply -f -
4848
```
4949

50+
## Run your test cases
51+
The test suite file could be in local, or in the HTTP server. See the following different ways:
52+
53+
* `atest run -p your-local-file.yaml`
54+
* `atest run -p https://gitee.com/linuxsuren/api-testing/raw/master/sample/testsuite-gitee.yaml`
55+
* `atest run -p http://localhost:8080/server.Runner/ConvertTestSuite?suite=sample`
56+
57+
For the last one, it represents the API Testing server.
58+
59+
## Convert to JMeter
60+
[JMeter](https://jmeter.apache.org/) is a load test tool. You can run the following commands from the root directory of this repository:
61+
62+
```shell
63+
atest convert --converter jmeter -p sample/testsuite-gitee.yaml --target bin/gitee.jmx
64+
65+
jmeter -n -t bin/gitee.jmx
66+
```
67+
68+
Please feel free to bring more test tool converters.
69+
5070
## Storage
5171
There are multiple storage backends supported. See the status from the list:
5272

pkg/generator/converter_jmeter_test.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,18 @@ func TestJmeterConvert(t *testing.T) {
3939
assert.NotNil(t, jmeterConvert)
4040

4141
converters := GetTestSuiteConverters()
42-
assert.Equal(t, 1, len(converters))
42+
assert.Equal(t, 2, len(converters))
4343
})
4444

45-
testSuite := &atest.TestSuite{
45+
converter := &jmeterConverter{}
46+
output, err := converter.Convert(createTestSuiteForTest())
47+
assert.NoError(t, err)
48+
49+
assert.Equal(t, expectedJmeter, output, output)
50+
}
51+
52+
func createTestSuiteForTest() *atest.TestSuite {
53+
return &atest.TestSuite{
4654
Name: "API Testing",
4755
API: "http://localhost:8080",
4856
Items: []atest.TestCase{{
@@ -53,12 +61,6 @@ func TestJmeterConvert(t *testing.T) {
5361
},
5462
}},
5563
}
56-
57-
converter := &jmeterConverter{}
58-
output, err := converter.Convert(testSuite)
59-
assert.NoError(t, err)
60-
61-
assert.Equal(t, expectedJmeter, output, output)
6264
}
6365

6466
//go:embed testdata/expected_jmeter.jmx

pkg/generator/converter_raw.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 generator
26+
27+
import (
28+
"github.com/linuxsuren/api-testing/pkg/testing"
29+
"gopkg.in/yaml.v3"
30+
)
31+
32+
type rawConverter struct {
33+
}
34+
35+
func init() {
36+
RegisterTestSuiteConverter("raw", &rawConverter{})
37+
}
38+
39+
func (c *rawConverter) Convert(testSuite *testing.TestSuite) (result string, err error) {
40+
if err = testSuite.Render(make(map[string]interface{})); err == nil {
41+
for _, item := range testSuite.Items {
42+
item.Request.RenderAPI(testSuite.API)
43+
}
44+
45+
var data []byte
46+
if data, err = yaml.Marshal(testSuite); err == nil {
47+
result = string(data)
48+
}
49+
}
50+
return
51+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 generator
26+
27+
import (
28+
"testing"
29+
30+
_ "embed"
31+
32+
"github.com/stretchr/testify/assert"
33+
)
34+
35+
func TestRawConvert(t *testing.T) {
36+
rawConvert := GetTestSuiteConverter("raw")
37+
assert.NotNil(t, rawConvert)
38+
39+
output, err := rawConvert.Convert(createTestSuiteForTest())
40+
assert.NoError(t, err)
41+
assert.Equal(t, expectedTestSuite, output)
42+
}
43+
44+
//go:embed testdata/expected_testsuite.yaml
45+
var expectedTestSuite string
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: API Testing
2+
api: http://localhost:8080
3+
items:
4+
- name: hello-jmeter
5+
request:
6+
api: /GetSuites
7+
method: POST

pkg/server/remote_server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ func TestCodeGenerator(t *testing.T) {
608608
t.Run("ListConverter", func(t *testing.T) {
609609
list, err := server.ListConverter(ctx, &Empty{})
610610
assert.NoError(t, err)
611-
assert.Equal(t, 1, len(list.Data))
611+
assert.Equal(t, 2, len(list.Data))
612612
})
613613

614614
t.Run("ConvertTestSuite no converter given", func(t *testing.T) {

pkg/testing/loader_file.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package testing
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
510
"os"
611
"path"
712
"path/filepath"
13+
"strings"
814
"sync"
915

1016
"github.com/linuxsuren/api-testing/pkg/util"
@@ -35,7 +41,54 @@ func (l *fileLoader) HasMore() bool {
3541

3642
// Load returns the test case content
3743
func (l *fileLoader) Load() (data []byte, err error) {
38-
data, err = os.ReadFile(l.paths[l.index])
44+
targetFile := l.paths[l.index]
45+
if strings.HasPrefix(targetFile, "http://") || strings.HasPrefix(targetFile, "https://") {
46+
var ok bool
47+
data, ok, err = gRPCCompitableRequest(targetFile)
48+
if !ok && err == nil {
49+
var resp *http.Response
50+
if resp, err = http.Get(targetFile); err == nil {
51+
data, err = io.ReadAll(resp.Body)
52+
}
53+
}
54+
} else {
55+
data, err = os.ReadFile(targetFile)
56+
}
57+
return
58+
}
59+
60+
func gRPCCompitableRequest(targetURLStr string) (data []byte, ok bool, err error) {
61+
if !strings.Contains(targetURLStr, "server.Runner/ConvertTestSuite") {
62+
return
63+
}
64+
65+
var targetURL *url.URL
66+
if targetURL, err = url.Parse(targetURLStr); err != nil {
67+
return
68+
}
69+
70+
suite := targetURL.Query().Get("suite")
71+
if suite == "" {
72+
err = fmt.Errorf("suite is required")
73+
return
74+
}
75+
76+
payload := new(bytes.Buffer)
77+
payload.WriteString(fmt.Sprintf(`{"TestSuite":"%s", "Generator":"raw"}`, suite))
78+
79+
var resp *http.Response
80+
if resp, err = http.Post(targetURLStr, "", payload); err == nil {
81+
if data, err = io.ReadAll(resp.Body); err != nil {
82+
return
83+
}
84+
85+
var gRPCData map[string]interface{}
86+
if err = json.Unmarshal(data, &gRPCData); err == nil {
87+
var obj interface{}
88+
obj, ok = gRPCData["message"]
89+
data = []byte(fmt.Sprintf("%v", obj))
90+
}
91+
}
3992
return
4093
}
4194

@@ -48,6 +101,11 @@ func (l *fileLoader) Put(item string) (err error) {
48101
l.parent = path.Dir(item)
49102
}
50103

104+
if strings.HasPrefix(item, "http://") || strings.HasPrefix(item, "https://") {
105+
l.paths = append(l.paths, item)
106+
return
107+
}
108+
51109
for _, pattern := range util.Expand(item) {
52110
var files []string
53111
if files, err = filepath.Glob(pattern); err == nil {

pkg/testing/loader_file_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"testing"
77

8+
"github.com/h2non/gock"
89
atest "github.com/linuxsuren/api-testing/pkg/testing"
910
atesting "github.com/linuxsuren/api-testing/pkg/testing"
1011
"github.com/stretchr/testify/assert"
@@ -44,6 +45,62 @@ func TestFileLoader(t *testing.T) {
4445
}
4546
}
4647

48+
func TestURLLoader(t *testing.T) {
49+
const json = `{"key": "value", "message": "sample"}`
50+
51+
t.Run("normal HTTP GET request", func(t *testing.T) {
52+
loader := atest.NewFileLoader()
53+
defer gock.Off()
54+
55+
err := loader.Put(urlFake)
56+
assert.NoError(t, err)
57+
58+
gock.New(urlFake).Get("/").Reply(http.StatusOK).BodyString(json)
59+
60+
assert.True(t, loader.HasMore())
61+
var data []byte
62+
data, err = loader.Load()
63+
assert.NoError(t, err)
64+
assert.Equal(t, json, string(data))
65+
})
66+
67+
t.Run("HTTP POST request, lack of suite name", func(t *testing.T) {
68+
loader := atest.NewFileLoader()
69+
defer gock.Off()
70+
const api = "/server.Runner/ConvertTestSuite"
71+
const reqURL = urlFake + api
72+
73+
err := loader.Put(reqURL)
74+
assert.NoError(t, err)
75+
76+
gock.New(urlFake).Get(api).Reply(http.StatusOK).BodyString(json)
77+
78+
assert.True(t, loader.HasMore())
79+
_, err = loader.Load()
80+
assert.Error(t, err)
81+
})
82+
83+
t.Run("HTTP POST request", func(t *testing.T) {
84+
loader := atest.NewFileLoader()
85+
defer gock.Off()
86+
const api = "/server.Runner/ConvertTestSuite"
87+
const reqURL = urlFake + api + "?suite=sample"
88+
89+
err := loader.Put(reqURL)
90+
assert.NoError(t, err)
91+
92+
gock.New(urlFake).Post(api).BodyString(`{"TestSuite":"sample", "Generator":"raw"}`).
93+
Reply(http.StatusOK).BodyString(json)
94+
95+
assert.True(t, loader.HasMore())
96+
97+
var data []byte
98+
data, err = loader.Load()
99+
assert.NoError(t, err)
100+
assert.Equal(t, "sample", string(data))
101+
})
102+
}
103+
47104
func defaultVerify(t *testing.T, loader atest.Loader) {
48105
assert.True(t, loader.HasMore())
49106
data, err := loader.Load()

0 commit comments

Comments
 (0)