Skip to content

Commit cef376a

Browse files
authored
fix: cannot read the query (#317)
Co-authored-by: rick <[email protected]>
1 parent ba51c74 commit cef376a

19 files changed

+316
-14
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ test-all-backend: test test-collector test-store-orm test-store-s3 test-store-gi
117117
test-all: test-all-backend test-ui
118118
test-e2e:
119119
cd e2e && ./start.sh && ./start.sh compose-k8s.yaml
120+
fuzz:
121+
cd pkg/util && go test -fuzz FuzzZeroThenDefault -fuzztime 6s
120122
install-precheck:
121123
cp .github/pre-commit .git/hooks/pre-commit
122124

cmd/run.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,15 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter
354354
return
355355
}
356356
}
357+
358+
reverseRunner := runner.NewReverseHTTPRunner(suiteRunner)
359+
reverseRunner.WithTestReporter(runner.NewDiscardTestReporter())
360+
if _, err = reverseRunner.RunTestCase(
361+
&testCase, dataContext, ctxWithTimeout); err != nil {
362+
err = fmt.Errorf("got error in reverse test: %w", err)
363+
return
364+
}
365+
suiteRunner.WithTestReporter(o.reporter)
357366
}
358367
dataContext[testCase.Name] = output
359368
}

e2e/test-suite-common.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ items:
270270
- name: downloadExtGit
271271
request:
272272
api: |
273-
{{.param.server}}/get?name=atest-store-git
273+
{{.param.server}}/get
274+
query:
275+
name: atest-store-git
274276
expect:
275277
header:
276278
Content-Type: application/octet-stream

pkg/generator/curl_generator.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ func (g *curlGenerator) Generate(testSuite *testing.TestSuite, testcase *testing
5252

5353
queryKeys := testcase.Request.Query.Keys()
5454
for _, k := range queryKeys {
55-
v := testcase.Request.Query[k]
56-
testcase.Request.API += k + "=" + v + "&"
55+
testcase.Request.API += k + "=" + testcase.Request.Query.GetValue(k) + "&"
5756
}
5857

5958
testcase.Request.API = strings.TrimSuffix(testcase.Request.API, "&")

pkg/generator/curl_generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestCurlGenerator(t *testing.T) {
5050
testCase: atest.TestCase{
5151
Request: atest.Request{
5252
API: fooForTest,
53-
Query: map[string]string{
53+
Query: map[string]interface{}{
5454
"page": "1",
5555
"size": "10",
5656
},

pkg/runner/http.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
148148
return
149149
}
150150

151+
q := request.URL.Query()
152+
for k := range testcase.Request.Query {
153+
q.Add(k, testcase.Request.Query.GetValue(k))
154+
}
155+
request.URL.RawQuery = q.Encode()
156+
151157
// set headers
152158
for key, val := range testcase.Request.Header {
153159
request.Header.Add(key, val)

pkg/runner/http_reverse.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 runner
26+
27+
import (
28+
"bytes"
29+
"context"
30+
"encoding/gob"
31+
"fmt"
32+
33+
"github.com/linuxsuren/api-testing/pkg/testing"
34+
"github.com/linuxsuren/api-testing/pkg/util"
35+
)
36+
37+
type Mutator interface {
38+
Render(*testing.TestCase) *testing.TestCase
39+
Message() string
40+
}
41+
42+
type authHeaderMissingMutator struct{}
43+
44+
func (m *authHeaderMissingMutator) Render(testcase *testing.TestCase) (result *testing.TestCase) {
45+
result = &testing.TestCase{}
46+
_ = DeepCopy(testcase, result)
47+
delete(result.Request.Header, util.Authorization)
48+
return
49+
}
50+
51+
func (m *authHeaderMissingMutator) Message() string {
52+
return "Missing Authorization in header"
53+
}
54+
55+
type authHeaderRandomMutator struct{}
56+
57+
func (m *authHeaderRandomMutator) Render(testcase *testing.TestCase) (result *testing.TestCase) {
58+
result = &testing.TestCase{}
59+
_ = DeepCopy(testcase, result)
60+
if result.Request.Header == nil {
61+
result.Request.Header = make(map[string]string)
62+
}
63+
result.Request.Header[util.Authorization] = util.String(6)
64+
return
65+
}
66+
67+
func (m *authHeaderRandomMutator) Message() string {
68+
return "Random Authorization in header"
69+
}
70+
71+
type requiredQueryMutator struct {
72+
field string
73+
}
74+
75+
func (m *requiredQueryMutator) Render(testcase *testing.TestCase) (result *testing.TestCase) {
76+
result = &testing.TestCase{}
77+
_ = DeepCopy(testcase, result)
78+
delete(result.Request.Query, m.field)
79+
return
80+
}
81+
82+
func (m *requiredQueryMutator) Message() string {
83+
return fmt.Sprintf("Missing required query field: %q", m.field)
84+
}
85+
86+
type minLengthQueryMutator struct {
87+
field string
88+
length int
89+
}
90+
91+
func (m *minLengthQueryMutator) Render(testcase *testing.TestCase) (result *testing.TestCase) {
92+
result = &testing.TestCase{}
93+
_ = DeepCopy(testcase, result)
94+
if result.Request.Query != nil && m.length > 1 {
95+
result.Request.Query[m.field] = util.String(m.length - 1)
96+
}
97+
return
98+
}
99+
100+
func (m *minLengthQueryMutator) Message() string {
101+
return fmt.Sprintf("Min length query field: %q", m.field)
102+
}
103+
104+
func DeepCopy(src, dist interface{}) (err error) {
105+
buf := bytes.Buffer{}
106+
if err = gob.NewEncoder(&buf).Encode(src); err != nil {
107+
return
108+
}
109+
return gob.NewDecoder(&buf).Decode(dist)
110+
}
111+
112+
type reverseHTTPRunner struct {
113+
TestCaseRunner
114+
}
115+
116+
func NewReverseHTTPRunner(normal TestCaseRunner) TestCaseRunner {
117+
return &reverseHTTPRunner{
118+
TestCaseRunner: normal,
119+
}
120+
}
121+
122+
func (r *reverseHTTPRunner) RunTestCase(testcase *testing.TestCase, dataContext interface{},
123+
ctx context.Context) (output interface{}, err error) {
124+
// find all the mutators
125+
126+
var mutators []Mutator
127+
if _, ok := testcase.Request.Header[util.Authorization]; ok {
128+
mutators = append(mutators, &authHeaderMissingMutator{}, &authHeaderRandomMutator{})
129+
}
130+
131+
for k := range testcase.Request.Query {
132+
verifier := testcase.Request.Query.GetVerifier(k)
133+
if verifier == nil {
134+
continue
135+
}
136+
137+
if verifier.Required {
138+
mutators = append(mutators, &requiredQueryMutator{field: k})
139+
}
140+
if verifier.MinLength > 0 {
141+
mutators = append(mutators, &minLengthQueryMutator{
142+
field: k,
143+
length: verifier.MinLength,
144+
})
145+
}
146+
}
147+
148+
for _, mutator := range mutators {
149+
mutationCase := mutator.Render(testcase)
150+
_, reverseErr := r.TestCaseRunner.RunTestCase(mutationCase, dataContext, ctx)
151+
if reverseErr == nil {
152+
err = fmt.Errorf("failed when: %q", mutator.Message())
153+
return
154+
}
155+
}
156+
return
157+
}

pkg/runner/http_reverse_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 runner
26+
27+
import (
28+
"testing"
29+
30+
atest "github.com/linuxsuren/api-testing/pkg/testing"
31+
"github.com/linuxsuren/api-testing/pkg/util"
32+
"github.com/stretchr/testify/assert"
33+
)
34+
35+
func TestAuthHeaderMutator(t *testing.T) {
36+
mutator := &authHeaderMissingMutator{}
37+
testcase := &atest.TestCase{
38+
Request: atest.Request{
39+
Header: map[string]string{
40+
util.Authorization: "Basic xxyy",
41+
},
42+
},
43+
}
44+
result := mutator.Render(testcase)
45+
_, ok := result.Request.Header[util.Authorization]
46+
assert.False(t, ok)
47+
assert.NotEmpty(t, testcase.Request.Header[util.Authorization])
48+
}

pkg/runner/http_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ func TestTestCase(t *testing.T) {
8080
Request: atest.Request{
8181
API: urlFoo,
8282
Header: defaultForm,
83-
Body: `{"foo":"bar"}`,
83+
Query: map[string]interface{}{
84+
"name": "linuxsuren",
85+
},
86+
Body: `{"foo":"bar"}`,
8487
},
8588
Expect: atest.Response{
8689
StatusCode: http.StatusOK,

pkg/server/convert.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func ToGRPCTestCase(testCase testing.TestCase) (result *TestCase) {
145145
req := &Request{
146146
Api: testCase.Request.API,
147147
Method: testCase.Request.Method,
148-
Query: mapToPair(testCase.Request.Query),
148+
// Query: mapToPair(testCase.Request.Query),
149149
Header: mapToPair(testCase.Request.Header),
150150
Form: mapToPair(testCase.Request.Form),
151151
Body: testCase.Request.Body,
@@ -181,7 +181,7 @@ func ToNormalTestCase(in *TestCase) (result testing.TestCase) {
181181
result.Request.Body = req.Body
182182
result.Request.Header = pairToMap(req.Header)
183183
result.Request.Form = pairToMap(req.Form)
184-
result.Request.Query = pairToMap(req.Query)
184+
result.Request.Query = pairToInterMap(req.Query)
185185
}
186186

187187
if resp != nil {

pkg/server/remote_server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ func TestListTestCase(t *testing.T) {
409409
Header: map[string]string{
410410
"key": "value",
411411
},
412-
Query: map[string]string{},
412+
Query: map[string]interface{}{},
413413
Form: map[string]string{},
414414
},
415415
Expect: atesting.Response{

pkg/testing/case.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ SOFTWARE.
2424

2525
package testing
2626

27-
import "sort"
27+
import (
28+
"sort"
29+
30+
"gopkg.in/yaml.v3"
31+
)
2832

2933
// TestSuite represents a set of test cases
3034
type TestSuite struct {
@@ -123,7 +127,7 @@ type ConditionalVerify struct {
123127
Verify []string `yaml:"verify,omitempty" json:"verify,omitempty"`
124128
}
125129

126-
type SortedKeysStringMap map[string]string
130+
type SortedKeysStringMap map[string]interface{}
127131

128132
func (m SortedKeysStringMap) Keys() (keys []string) {
129133
for k := range m {
@@ -132,3 +136,48 @@ func (m SortedKeysStringMap) Keys() (keys []string) {
132136
sort.Strings(keys)
133137
return
134138
}
139+
140+
func (m SortedKeysStringMap) GetValue(key string) string {
141+
val := m[key]
142+
143+
switch o := any(val).(type) {
144+
case string:
145+
return val.(string)
146+
case map[string]interface{}:
147+
verifier := convertToVerifier(o)
148+
return verifier.Value
149+
}
150+
151+
return ""
152+
}
153+
154+
func (m SortedKeysStringMap) GetVerifier(key string) (verifier *Verifier) {
155+
val := m[key]
156+
157+
switch o := any(val).(type) {
158+
case map[string]interface{}:
159+
verifier = convertToVerifier(o)
160+
}
161+
162+
return
163+
}
164+
165+
func convertToVerifier(data map[string]interface{}) (verifier *Verifier) {
166+
verifier = &Verifier{}
167+
168+
if data, err := yaml.Marshal(data); err == nil {
169+
if err = yaml.Unmarshal(data, verifier); err != nil {
170+
verifier = nil
171+
}
172+
}
173+
return
174+
}
175+
176+
type Verifier struct {
177+
Value string `yaml:"value,omitempty" json:"value,omitempty"`
178+
Required bool `yaml:"required,omitempty" json:"required,omitempty"`
179+
Max int `yaml:"max"`
180+
Min int `yaml:"min"`
181+
MaxLength int `yaml:"maxLength"`
182+
MinLength int `yaml:"minLength"`
183+
}

0 commit comments

Comments
 (0)