Skip to content

feat: support running before and after jobs with API request #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

This is a API testing tool.

## Feature
## Features

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

## Verify against Kubernetes

Expand Down
32 changes: 0 additions & 32 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,9 @@ import (

"github.com/stretchr/testify/assert"

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

func TestSetRelativeDir(t *testing.T) {
type args struct {
configFile string
testcase *atesting.TestCase
}
tests := []struct {
name string
args args
verify func(*testing.T, *atesting.TestCase)
}{{
name: "normal",
args: args{
configFile: "a/b/c.yaml",
testcase: &atesting.TestCase{
Prepare: atesting.Prepare{
Kubernetes: []string{"deploy.yaml"},
},
},
},
verify: func(t *testing.T, testCase *atesting.TestCase) {
assert.Equal(t, "a/b/deploy.yaml", testCase.Prepare.Kubernetes[0])
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setRelativeDir(tt.args.configFile, tt.args.testcase)
tt.verify(t, tt.args.testcase)
})
}
}

func TestCreateRunCommand(t *testing.T) {
cmd := createRunCommand()
assert.Equal(t, "run", cmd.Use)
Expand Down
10 changes: 0 additions & 10 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
Expand Down Expand Up @@ -216,7 +215,6 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
case <-stopSingal:
return
default:
setRelativeDir(suite, &testCase)
o.limiter.Accept()

ctxWithTimeout, _ := context.WithTimeout(ctx, o.requestTimeout)
Expand All @@ -238,11 +236,3 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
func getDefaultContext() map[string]interface{} {
return map[string]interface{}{}
}

func setRelativeDir(configFile string, testcase *testing.TestCase) {
dir := filepath.Dir(configFile)

for i := range testcase.Prepare.Kubernetes {
testcase.Prepare.Kubernetes[i] = path.Join(dir, testcase.Prepare.Kubernetes[i])
}
}
26 changes: 26 additions & 0 deletions pkg/runner/expr_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Package runner provides the common expr style functions
package runner

import (
"fmt"
"time"
)

// ExprFuncSleep is a expr function for sleeping
func ExprFuncSleep(params ...interface{}) (res interface{}, err error) {
if len(params) < 1 {
err = fmt.Errorf("the duration param is required")
return
}

switch duration := params[0].(type) {
case int:
time.Sleep(time.Duration(duration) * time.Second)
case string:
var dur time.Duration
if dur, err = time.ParseDuration(duration); err == nil {
time.Sleep(dur)
}
}
return
}
33 changes: 33 additions & 0 deletions pkg/runner/expr_function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package runner_test

import (
"testing"

"github.com/linuxsuren/api-testing/pkg/runner"
"github.com/stretchr/testify/assert"
)

func TestExprFuncSleep(t *testing.T) {
tests := []struct {
name string
params []interface{}
hasErr bool
}{{
name: "string format duration",
params: []interface{}{"0.01s"},
hasErr: false,
}, {
name: "without params",
hasErr: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := runner.ExprFuncSleep(tt.params...)
if tt.hasErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
47 changes: 29 additions & 18 deletions pkg/runner/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,14 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
r.testReporter.PutRecord(rr)
}(record)

if err = r.doPrepare(testcase); err != nil {
err = fmt.Errorf("failed to prepare, error: %v", err)
return
}

defer func() {
if testcase.Clean.CleanPrepare {
err = r.doCleanPrepare(testcase)
}

if err == nil {
err = runJob(testcase.After)
}
}()

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

if err = runJob(testcase.Before); err != nil {
return
}

r.log.Info("start to send request to %s\n", testcase.Request.API)

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

func (r *simpleTestCaseRunner) doPrepare(testcase *testing.TestCase) (err error) {
for i := range testcase.Prepare.Kubernetes {
item := testcase.Prepare.Kubernetes[i]

if err = r.execer.RunCommand("kubectl", "apply", "-f", item); err != nil {
return
}
}
return
}

func (r *simpleTestCaseRunner) doCleanPrepare(testcase *testing.TestCase) (err error) {
count := len(testcase.Prepare.Kubernetes)
count := len(testcase.Before.Items)
for i := count - 1; i >= 0; i-- {
item := testcase.Prepare.Kubernetes[i]
item := testcase.Before.Items[i]

if err = r.execer.RunCommand("kubectl", "delete", "-f", item); err != nil {
return
Expand Down Expand Up @@ -413,3 +405,22 @@ func verifyResponseBodyData(caseName string, expect testing.Response, responseBo
}
return
}

func runJob(job testing.Job) (err error) {
var program *vm.Program
env := struct{}{}

for _, item := range job.Items {
if program, err = expr.Compile(item, expr.Env(env),
expr.Function("sleep", ExprFuncSleep)); err != nil {
fmt.Printf("failed to compile: %s, %v\n", item, err)
return
}

if _, err = expr.Run(program, env); err != nil {
fmt.Printf("failed to Run: %s, %v\n", item, err)
return
}
}
return
}
34 changes: 30 additions & 4 deletions pkg/runner/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func TestTestCase(t *testing.T) {
}{{
name: "failed during the prepare stage",
testCase: &atest.TestCase{
Prepare: atest.Prepare{
Kubernetes: []string{"demo.yaml"},
Before: atest.Job{
Items: []string{"demo.yaml"},
},
},
execer: fakeruntime.FakeExecer{ExpectError: errors.New("fake")},
Expand All @@ -69,8 +69,8 @@ func TestTestCase(t *testing.T) {
`data.name == "linuxsuren"`,
},
},
Prepare: atest.Prepare{
Kubernetes: []string{"demo.yaml"},
Before: atest.Job{
Items: []string{"sleep(1)"},
},
Clean: atest.Clean{
CleanPrepare: true,
Expand Down Expand Up @@ -411,6 +411,32 @@ func TestJSONSchemaValidation(t *testing.T) {
}
}

func TestRunJob(t *testing.T) {
tests := []struct {
name string
job atest.Job
hasErr bool
}{{
name: "sleep 1s",
job: atest.Job{
Items: []string{"sleep(1)"},
},
hasErr: false,
}, {
name: "no params",
job: atest.Job{
Items: []string{"sleep()"},
},
hasErr: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := runJob(tt.job)
assert.Equal(t, tt.hasErr, err != nil, err)
})
}
}

const defaultSchemaForTest = `{"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
Expand Down
9 changes: 5 additions & 4 deletions pkg/testing/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ type TestSuite struct {
type TestCase struct {
Name string `yaml:"name" json:"name"`
Group string
Prepare Prepare `yaml:"prepare" json:"-"`
Before Job `yaml:"before" json:"before"`
After Job `yaml:"after" json:"after"`
Request Request `yaml:"request" json:"request"`
Expect Response `yaml:"expect" json:"expect"`
Clean Clean `yaml:"clean" json:"-"`
Expand All @@ -31,9 +32,9 @@ func (c *TestCase) InScope(items []string) bool {
return false
}

// Prepare does the prepare work
type Prepare struct {
Kubernetes []string `yaml:"kubernetes"`
// Job contains a list of jobs
type Job struct {
Items []string `yaml:"items"`
}

// Request represents a HTTP request
Expand Down
6 changes: 6 additions & 0 deletions pkg/testing/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ func TestParse(t *testing.T) {
}
`,
},
Before: Job{
Items: []string{"sleep(1)"},
},
After: Job{
Items: []string{"sleep(1)"},
},
}, suite.Items[0])
}

Expand Down
22 changes: 22 additions & 0 deletions sample/api-testing-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
},
"expect": {
"$ref": "#/definitions/Expect"
},
"before": {
"$ref": "#/definitions/Job"
},
"after": {
"$ref": "#/definitions/Job"
}
},
"required": [
Expand Down Expand Up @@ -121,6 +127,22 @@
"api"
],
"title": "Request"
},
"Job": {
"type": "object",
"additionalProperties": false,
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"items"
],
"title": "Job"
}
}
}
3 changes: 3 additions & 0 deletions sample/testsuite-gitee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ name: Gitee
api: https://gitee.com/api/v5
items:
- name: stargazers
before:
items:
- sleep(1)
request:
api: /repos/linuxsuren/api-testing/stargazers
expect:
Expand Down
6 changes: 6 additions & 0 deletions sample/testsuite-gitlab.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ items:
{
"type": "array"
}
before:
items:
- "sleep(1)"
after:
items:
- "sleep(1)"
- name: project
request:
api: https://gitlab.com/api/v4/projects/{{int64 (index .projects 0).id}}
Expand Down