Skip to content

Commit 2884073

Browse files
authored
feat: support running before and after jobs with API request (#69)
1 parent 2a3c594 commit 2884073

12 files changed

+163
-69
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
This is a API testing tool.
77

8-
## Feature
8+
## Features
99

1010
* Response Body fields equation check
1111
* Response Body [eval](https://expr.medv.io/)
1212
* Verify the Kubernetes resources
1313
* Validate the response body with [JSON schema](https://json-schema.org/)
14+
* Pre and post handle with the API request
1415
* Output reference between TestCase
1516
* Run in server mode, and provide the gRPC endpoint
1617
* [VS Code extension](https://github.com/LinuxSuRen/vscode-api-testing) support
@@ -71,6 +72,7 @@ You could use all the common functions which comes from [sprig](http://mastermin
7172
| Name | Usage |
7273
|---|---|
7374
| `randomKubernetesName` | `{{randomKubernetesName}}` to generate Kubernetes resource name randomly, the name will have 8 chars |
75+
| `sleep` | `{{sleep(1)}}` in the pre and post request handle |
7476
7577
## Verify against Kubernetes
7678

cmd/root_test.go

-32
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,9 @@ import (
55

66
"github.com/stretchr/testify/assert"
77

8-
atesting "github.com/linuxsuren/api-testing/pkg/testing"
98
exec "github.com/linuxsuren/go-fake-runtime"
109
)
1110

12-
func TestSetRelativeDir(t *testing.T) {
13-
type args struct {
14-
configFile string
15-
testcase *atesting.TestCase
16-
}
17-
tests := []struct {
18-
name string
19-
args args
20-
verify func(*testing.T, *atesting.TestCase)
21-
}{{
22-
name: "normal",
23-
args: args{
24-
configFile: "a/b/c.yaml",
25-
testcase: &atesting.TestCase{
26-
Prepare: atesting.Prepare{
27-
Kubernetes: []string{"deploy.yaml"},
28-
},
29-
},
30-
},
31-
verify: func(t *testing.T, testCase *atesting.TestCase) {
32-
assert.Equal(t, "a/b/deploy.yaml", testCase.Prepare.Kubernetes[0])
33-
},
34-
}}
35-
for _, tt := range tests {
36-
t.Run(tt.name, func(t *testing.T) {
37-
setRelativeDir(tt.args.configFile, tt.args.testcase)
38-
tt.verify(t, tt.args.testcase)
39-
})
40-
}
41-
}
42-
4311
func TestCreateRunCommand(t *testing.T) {
4412
cmd := createRunCommand()
4513
assert.Equal(t, "run", cmd.Use)

cmd/run.go

-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"path"
87
"path/filepath"
98
"strings"
109
"sync"
@@ -216,7 +215,6 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
216215
case <-stopSingal:
217216
return
218217
default:
219-
setRelativeDir(suite, &testCase)
220218
o.limiter.Accept()
221219

222220
ctxWithTimeout, _ := context.WithTimeout(ctx, o.requestTimeout)
@@ -238,11 +236,3 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
238236
func getDefaultContext() map[string]interface{} {
239237
return map[string]interface{}{}
240238
}
241-
242-
func setRelativeDir(configFile string, testcase *testing.TestCase) {
243-
dir := filepath.Dir(configFile)
244-
245-
for i := range testcase.Prepare.Kubernetes {
246-
testcase.Prepare.Kubernetes[i] = path.Join(dir, testcase.Prepare.Kubernetes[i])
247-
}
248-
}

pkg/runner/expr_function.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Package runner provides the common expr style functions
2+
package runner
3+
4+
import (
5+
"fmt"
6+
"time"
7+
)
8+
9+
// ExprFuncSleep is a expr function for sleeping
10+
func ExprFuncSleep(params ...interface{}) (res interface{}, err error) {
11+
if len(params) < 1 {
12+
err = fmt.Errorf("the duration param is required")
13+
return
14+
}
15+
16+
switch duration := params[0].(type) {
17+
case int:
18+
time.Sleep(time.Duration(duration) * time.Second)
19+
case string:
20+
var dur time.Duration
21+
if dur, err = time.ParseDuration(duration); err == nil {
22+
time.Sleep(dur)
23+
}
24+
}
25+
return
26+
}

pkg/runner/expr_function_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package runner_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/linuxsuren/api-testing/pkg/runner"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestExprFuncSleep(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
params []interface{}
14+
hasErr bool
15+
}{{
16+
name: "string format duration",
17+
params: []interface{}{"0.01s"},
18+
hasErr: false,
19+
}, {
20+
name: "without params",
21+
hasErr: true,
22+
}}
23+
for _, tt := range tests {
24+
t.Run(tt.name, func(t *testing.T) {
25+
_, err := runner.ExprFuncSleep(tt.params...)
26+
if tt.hasErr {
27+
assert.Error(t, err)
28+
} else {
29+
assert.NoError(t, err)
30+
}
31+
})
32+
}
33+
}

pkg/runner/simple.go

+29-18
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,14 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
180180
r.testReporter.PutRecord(rr)
181181
}(record)
182182

183-
if err = r.doPrepare(testcase); err != nil {
184-
err = fmt.Errorf("failed to prepare, error: %v", err)
185-
return
186-
}
187-
188183
defer func() {
189184
if testcase.Clean.CleanPrepare {
190185
err = r.doCleanPrepare(testcase)
191186
}
187+
188+
if err == nil {
189+
err = runJob(testcase.After)
190+
}
192191
}()
193192

194193
client := http.Client{
@@ -216,6 +215,10 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
216215
request.Header.Add(key, val)
217216
}
218217

218+
if err = runJob(testcase.Before); err != nil {
219+
return
220+
}
221+
219222
r.log.Info("start to send request to %s\n", testcase.Request.API)
220223

221224
// TODO only do this for unit testing, should remove it once we have a better way
@@ -285,21 +288,10 @@ func (r *simpleTestCaseRunner) WithExecer(execer fakeruntime.Execer) TestCaseRun
285288
return r
286289
}
287290

288-
func (r *simpleTestCaseRunner) doPrepare(testcase *testing.TestCase) (err error) {
289-
for i := range testcase.Prepare.Kubernetes {
290-
item := testcase.Prepare.Kubernetes[i]
291-
292-
if err = r.execer.RunCommand("kubectl", "apply", "-f", item); err != nil {
293-
return
294-
}
295-
}
296-
return
297-
}
298-
299291
func (r *simpleTestCaseRunner) doCleanPrepare(testcase *testing.TestCase) (err error) {
300-
count := len(testcase.Prepare.Kubernetes)
292+
count := len(testcase.Before.Items)
301293
for i := count - 1; i >= 0; i-- {
302-
item := testcase.Prepare.Kubernetes[i]
294+
item := testcase.Before.Items[i]
303295

304296
if err = r.execer.RunCommand("kubectl", "delete", "-f", item); err != nil {
305297
return
@@ -413,3 +405,22 @@ func verifyResponseBodyData(caseName string, expect testing.Response, responseBo
413405
}
414406
return
415407
}
408+
409+
func runJob(job testing.Job) (err error) {
410+
var program *vm.Program
411+
env := struct{}{}
412+
413+
for _, item := range job.Items {
414+
if program, err = expr.Compile(item, expr.Env(env),
415+
expr.Function("sleep", ExprFuncSleep)); err != nil {
416+
fmt.Printf("failed to compile: %s, %v\n", item, err)
417+
return
418+
}
419+
420+
if _, err = expr.Run(program, env); err != nil {
421+
fmt.Printf("failed to Run: %s, %v\n", item, err)
422+
return
423+
}
424+
}
425+
return
426+
}

pkg/runner/simple_test.go

+30-4
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func TestTestCase(t *testing.T) {
4343
}{{
4444
name: "failed during the prepare stage",
4545
testCase: &atest.TestCase{
46-
Prepare: atest.Prepare{
47-
Kubernetes: []string{"demo.yaml"},
46+
Before: atest.Job{
47+
Items: []string{"demo.yaml"},
4848
},
4949
},
5050
execer: fakeruntime.FakeExecer{ExpectError: errors.New("fake")},
@@ -69,8 +69,8 @@ func TestTestCase(t *testing.T) {
6969
`data.name == "linuxsuren"`,
7070
},
7171
},
72-
Prepare: atest.Prepare{
73-
Kubernetes: []string{"demo.yaml"},
72+
Before: atest.Job{
73+
Items: []string{"sleep(1)"},
7474
},
7575
Clean: atest.Clean{
7676
CleanPrepare: true,
@@ -411,6 +411,32 @@ func TestJSONSchemaValidation(t *testing.T) {
411411
}
412412
}
413413

414+
func TestRunJob(t *testing.T) {
415+
tests := []struct {
416+
name string
417+
job atest.Job
418+
hasErr bool
419+
}{{
420+
name: "sleep 1s",
421+
job: atest.Job{
422+
Items: []string{"sleep(1)"},
423+
},
424+
hasErr: false,
425+
}, {
426+
name: "no params",
427+
job: atest.Job{
428+
Items: []string{"sleep()"},
429+
},
430+
hasErr: true,
431+
}}
432+
for _, tt := range tests {
433+
t.Run(tt.name, func(t *testing.T) {
434+
err := runJob(tt.job)
435+
assert.Equal(t, tt.hasErr, err != nil, err)
436+
})
437+
}
438+
}
439+
414440
const defaultSchemaForTest = `{"properties": {
415441
"name": {"type": "string"},
416442
"age": {"type": "integer"}

pkg/testing/case.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ type TestSuite struct {
1111
type TestCase struct {
1212
Name string `yaml:"name" json:"name"`
1313
Group string
14-
Prepare Prepare `yaml:"prepare" json:"-"`
14+
Before Job `yaml:"before" json:"before"`
15+
After Job `yaml:"after" json:"after"`
1516
Request Request `yaml:"request" json:"request"`
1617
Expect Response `yaml:"expect" json:"expect"`
1718
Clean Clean `yaml:"clean" json:"-"`
@@ -31,9 +32,9 @@ func (c *TestCase) InScope(items []string) bool {
3132
return false
3233
}
3334

34-
// Prepare does the prepare work
35-
type Prepare struct {
36-
Kubernetes []string `yaml:"kubernetes"`
35+
// Job contains a list of jobs
36+
type Job struct {
37+
Items []string `yaml:"items"`
3738
}
3839

3940
// Request represents a HTTP request

pkg/testing/parser_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ func TestParse(t *testing.T) {
2828
}
2929
`,
3030
},
31+
Before: Job{
32+
Items: []string{"sleep(1)"},
33+
},
34+
After: Job{
35+
Items: []string{"sleep(1)"},
36+
},
3137
}, suite.Items[0])
3238
}
3339

sample/api-testing-schema.json

+22
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@
3737
},
3838
"expect": {
3939
"$ref": "#/definitions/Expect"
40+
},
41+
"before": {
42+
"$ref": "#/definitions/Job"
43+
},
44+
"after": {
45+
"$ref": "#/definitions/Job"
4046
}
4147
},
4248
"required": [
@@ -121,6 +127,22 @@
121127
"api"
122128
],
123129
"title": "Request"
130+
},
131+
"Job": {
132+
"type": "object",
133+
"additionalProperties": false,
134+
"properties": {
135+
"items": {
136+
"type": "array",
137+
"items": {
138+
"type": "string"
139+
}
140+
}
141+
},
142+
"required": [
143+
"items"
144+
],
145+
"title": "Job"
124146
}
125147
}
126148
}

sample/testsuite-gitee.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ name: Gitee
44
api: https://gitee.com/api/v5
55
items:
66
- name: stargazers
7+
before:
8+
items:
9+
- sleep(1)
710
request:
811
api: /repos/linuxsuren/api-testing/stargazers
912
expect:

sample/testsuite-gitlab.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ items:
1212
{
1313
"type": "array"
1414
}
15+
before:
16+
items:
17+
- "sleep(1)"
18+
after:
19+
items:
20+
- "sleep(1)"
1521
- name: project
1622
request:
1723
api: https://gitlab.com/api/v4/projects/{{int64 (index .projects 0).id}}

0 commit comments

Comments
 (0)