From dc0adbfadd1fd0ada09de2f6c196e297ce36e7c2 Mon Sep 17 00:00:00 2001 From: rick <1450685+LinuxSuRen@users.noreply.github.com> Date: Mon, 17 Apr 2023 21:35:20 +0800 Subject: [PATCH 1/2] fix the duplicates --- cmd/run_test.go | 15 ++++++--------- cmd/server.go | 1 + cmd/service_test.go | 6 ++++-- pkg/runner/kubernetes/client_test.go | 6 +++--- pkg/runner/kubernetes/verify_test.go | 8 ++------ pkg/server/remote_server_test.go | 12 +++++++----- pkg/util/default.go | 21 +++++++++++++++++++++ pkg/util/default_test.go | 18 ++++++++++++++++++ 8 files changed, 62 insertions(+), 25 deletions(-) create mode 100644 pkg/util/default.go create mode 100644 pkg/util/default_test.go diff --git a/cmd/run_test.go b/cmd/run_test.go index a65b4759..a9fd68e9 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -8,6 +8,7 @@ import ( "github.com/h2non/gock" "github.com/linuxsuren/api-testing/pkg/limit" + "github.com/linuxsuren/api-testing/pkg/util" fakeruntime "github.com/linuxsuren/go-fake-runtime" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -41,14 +42,12 @@ func TestRunSuite(t *testing.T) { }, { name: "not found file", suiteFile: "testdata/fake.yaml", - prepare: func() {}, hasError: true, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer gock.Clean() - - tt.prepare() + util.MakeSureNotNil(tt.prepare)() ctx := getDefaultContext() opt := newDiskCardRunOption() opt.requestTimeout = 30 * time.Second @@ -75,10 +74,9 @@ func TestRunCommand(t *testing.T) { }, hasErr: true, }, { - name: "file not found", - args: []string{"--pattern", "fake"}, - prepare: func() {}, - hasErr: false, + name: "file not found", + args: []string{"--pattern", "fake"}, + hasErr: false, }, { name: "normal case", args: []string{"-p", simpleSuite}, @@ -90,8 +88,7 @@ func TestRunCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer gock.Clean() - tt.prepare() - + util.MakeSureNotNil(tt.prepare)() root := &cobra.Command{Use: "root"} root.AddCommand(createRunCommand()) diff --git a/cmd/server.go b/cmd/server.go index ef04ddcd..c8d34f30 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -71,4 +71,5 @@ func (s *fakeGRPCServer) Serve(net.Listener) error { // RegisterService is a fake method func (s *fakeGRPCServer) RegisterService(desc *grpc.ServiceDesc, impl interface{}) { + // Do nothing due to this is a fake method } diff --git a/cmd/service_test.go b/cmd/service_test.go index 5fb124b6..83d1430a 100644 --- a/cmd/service_test.go +++ b/cmd/service_test.go @@ -16,7 +16,7 @@ func TestService(t *testing.T) { assert.NotNil(t, err) notLinux := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "fake"}, NewFakeGRPCServer()) - notLinux.SetArgs([]string{"service", "--action", "install"}) + notLinux.SetArgs([]string{"service", paramAction, "install"}) err = notLinux.Execute() assert.NotNil(t, err) @@ -27,7 +27,7 @@ func TestService(t *testing.T) { }() targetScript := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, NewFakeGRPCServer()) - targetScript.SetArgs([]string{"service", "--action", "install", "--script-path", tmpFile.Name()}) + targetScript.SetArgs([]string{"service", paramAction, "install", "--script-path", tmpFile.Name()}) err = targetScript.Execute() assert.Nil(t, err) data, err := os.ReadFile(tmpFile.Name()) @@ -67,3 +67,5 @@ func TestService(t *testing.T) { }) } } + +const paramAction = "--action" diff --git a/pkg/runner/kubernetes/client_test.go b/pkg/runner/kubernetes/client_test.go index 422e3cc4..aa180f9d 100644 --- a/pkg/runner/kubernetes/client_test.go +++ b/pkg/runner/kubernetes/client_test.go @@ -27,7 +27,7 @@ func TestGetPod(t *testing.T) { name: "fake", }, prepare: func() { - gock.New("http://foo"). + gock.New(urlFoo). Get("/api/v1/namespaces/ns/pods/fake"). Reply(http.StatusOK). JSON(`{"kind":"pod"}`) @@ -46,7 +46,7 @@ func TestGetPod(t *testing.T) { name: "fake", }, prepare: func() { - gock.New("http://foo"). + gock.New(urlFoo). Get("/apis/apps/v1/namespaces/ns/deployments/fake"). Reply(http.StatusOK). JSON(`{"kind":"deployment"}`) @@ -60,7 +60,7 @@ func TestGetPod(t *testing.T) { t.Run(tt.name, func(t *testing.T) { defer gock.Clean() tt.prepare() - reader := kubernetes.NewDefaultReader("http://foo", "") + reader := kubernetes.NewDefaultReader(urlFoo, "") result, err := reader.GetResource(tt.group, tt.kind, tt.version, tt.namespacedName.namespace, tt.namespacedName.name) assert.Equal(t, tt.expect, result) assert.Nil(t, err) diff --git a/pkg/runner/kubernetes/verify_test.go b/pkg/runner/kubernetes/verify_test.go index 5ebbb4bf..fa324078 100644 --- a/pkg/runner/kubernetes/verify_test.go +++ b/pkg/runner/kubernetes/verify_test.go @@ -8,6 +8,7 @@ import ( "github.com/antonmedv/expr" "github.com/h2non/gock" "github.com/linuxsuren/api-testing/pkg/runner/kubernetes" + "github.com/linuxsuren/api-testing/pkg/util" "github.com/stretchr/testify/assert" ) @@ -47,7 +48,6 @@ func TestKubernetesValidatorFunc(t *testing.T) { }, { name: "no enough params", expression: `k8s('crd')`, - prepare: emptyPrepare, expectBool: false, expectErr: true, }, { @@ -73,11 +73,11 @@ func TestKubernetesValidatorFunc(t *testing.T) { }, { name: "no kind", expression: `k8s({"foo": "bar"}, "ns", "foo").Exist()`, - prepare: emptyPrepare, expectErr: true, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt.prepare = util.MakeSureNotNil(tt.prepare) tt.prepare() vm, err := expr.Compile(tt.expression, kubernetes.KubernetesValidatorFunc(), kubernetes.PodValidatorFunc()) @@ -92,10 +92,6 @@ func TestKubernetesValidatorFunc(t *testing.T) { } } -func emptyPrepare() { - // only for testing -} - func preparePod() { gock.New(urlFoo). Get("/api/v1/namespaces/ns/pods/foo"). diff --git a/pkg/server/remote_server_test.go b/pkg/server/remote_server_test.go index cdf58696..50a8e7aa 100644 --- a/pkg/server/remote_server_test.go +++ b/pkg/server/remote_server_test.go @@ -19,22 +19,22 @@ func TestRemoteServer(t *testing.T) { }) assert.NotNil(t, err) - gock.New("http://foo").Get("/").Reply(http.StatusOK).JSON(&server) - gock.New("http://foo").Get("/").Reply(http.StatusOK).JSON(&server) + gock.New(urlFoo).Get("/").Reply(http.StatusOK).JSON(&server) + gock.New(urlFoo).Get("/").Reply(http.StatusOK).JSON(&server) _, err = server.Run(context.TODO(), &TestTask{ Kind: "suite", Data: simpleSuite, }) assert.Nil(t, err) - gock.New("http://bar").Get("/").Reply(http.StatusOK).JSON(&server) + gock.New(urlFoo).Get("/").Reply(http.StatusOK).JSON(&server) _, err = server.Run(context.TODO(), &TestTask{ Kind: "testcase", Data: simpleTestCase, }) assert.Nil(t, err) - gock.New("http://foo").Get("/").Reply(http.StatusOK).JSON(&server) + gock.New(urlFoo).Get("/").Reply(http.StatusOK).JSON(&server) _, err = server.Run(context.TODO(), &TestTask{ Kind: "testcaseInSuite", Data: simpleSuite, @@ -42,7 +42,7 @@ func TestRemoteServer(t *testing.T) { }) assert.Nil(t, err) - gock.New("http://foo").Get("/").Reply(http.StatusOK).JSON(&server) + gock.New(urlFoo).Get("/").Reply(http.StatusOK).JSON(&server) _, err = server.Run(context.TODO(), &TestTask{ Kind: "testcaseInSuite", Data: simpleSuite, @@ -171,3 +171,5 @@ var simpleSuite string //go:embed testdata/simple_testcase.yaml var simpleTestCase string + +const urlFoo = "http://foo" diff --git a/pkg/util/default.go b/pkg/util/default.go new file mode 100644 index 00000000..f90cfced --- /dev/null +++ b/pkg/util/default.go @@ -0,0 +1,21 @@ +// Package util provides a set of common functions +package util + +// MakeSureNotNil makes sure the parameter is not nil +func MakeSureNotNil[T any](inter T) T { + switch val := any(inter).(type) { + case func(): + if val == nil { + val = func() { + // only making sure this is not nil + } + return any(val).(T) + } + case map[string]string: + if val == nil { + val = map[string]string{} + return any(val).(T) + } + } + return inter +} diff --git a/pkg/util/default_test.go b/pkg/util/default_test.go new file mode 100644 index 00000000..a8c0319c --- /dev/null +++ b/pkg/util/default_test.go @@ -0,0 +1,18 @@ +package util_test + +import ( + "testing" + + "github.com/linuxsuren/api-testing/pkg/util" + "github.com/stretchr/testify/assert" +) + +func TestMakeSureNotNil(t *testing.T) { + var fun func() + var mapStruct map[string]string + + assert.NotNil(t, util.MakeSureNotNil(fun)) + assert.NotNil(t, util.MakeSureNotNil(TestMakeSureNotNil)) + assert.NotNil(t, util.MakeSureNotNil(mapStruct)) + assert.NotNil(t, util.MakeSureNotNil(map[string]string{})) +} From 4f72b44bdfd51626c81d04238f9e532f18e1d993 Mon Sep 17 00:00:00 2001 From: rick <1450685+LinuxSuRen@users.noreply.github.com> Date: Tue, 18 Apr 2023 18:42:52 +0800 Subject: [PATCH 2/2] improve the function RunTestCase --- pkg/runner/simple.go | 36 ++-------------------- pkg/runner/simple_test.go | 5 ++-- pkg/testing/parser.go | 36 ++++++++++++++++++++++ pkg/testing/parser_test.go | 61 ++++++++++++++++++++++++++++++++++++++ pkg/util/default.go | 7 +++++ 5 files changed, 109 insertions(+), 36 deletions(-) diff --git a/pkg/runner/simple.go b/pkg/runner/simple.go index 1c04e5be..d9aa28d9 100644 --- a/pkg/runner/simple.go +++ b/pkg/runner/simple.go @@ -1,16 +1,12 @@ package runner import ( - "bytes" "context" "crypto/tls" "encoding/json" "fmt" "io" - "mime/multipart" "net/http" - "net/url" - "os" "reflect" "strings" "time" @@ -202,40 +198,14 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte } var requestBody io.Reader - if testcase.Request.Body != "" { - requestBody = bytes.NewBufferString(testcase.Request.Body) - } else if testcase.Request.BodyFromFile != "" { - var data []byte - if data, err = os.ReadFile(testcase.Request.BodyFromFile); err != nil { - return - } - requestBody = bytes.NewBufferString(string(data)) + if requestBody, err = testcase.Request.GetBody(); err != nil { + return } if err = testcase.Request.Render(dataContext); err != nil { return } - if len(testcase.Request.Form) > 0 { - if testcase.Request.Header[contentType] == "multipart/form-data" { - multiBody := &bytes.Buffer{} - writer := multipart.NewWriter(multiBody) - for key, val := range testcase.Request.Form { - writer.WriteField(key, val) - } - - _ = writer.Close() - requestBody = multiBody - testcase.Request.Header[contentType] = writer.FormDataContentType() - } else if testcase.Request.Header[contentType] == "application/x-www-form-urlencoded" { - data := url.Values{} - for key, val := range testcase.Request.Form { - data.Set(key, val) - } - requestBody = strings.NewReader(data.Encode()) - } - } - var request *http.Request if request, err = http.NewRequestWithContext(ctx, testcase.Request.Method, testcase.Request.API, requestBody); err != nil { return @@ -436,5 +406,3 @@ func jsonSchemaValidation(schema string, body []byte) (err error) { } return } - -const contentType = "Content-Type" diff --git a/pkg/runner/simple_test.go b/pkg/runner/simple_test.go index 6e8e7f43..b4dbc8af 100644 --- a/pkg/runner/simple_test.go +++ b/pkg/runner/simple_test.go @@ -12,6 +12,7 @@ import ( "github.com/h2non/gock" atest "github.com/linuxsuren/api-testing/pkg/testing" + "github.com/linuxsuren/api-testing/pkg/util" fakeruntime "github.com/linuxsuren/go-fake-runtime" "github.com/stretchr/testify/assert" ) @@ -300,7 +301,7 @@ func TestTestCase(t *testing.T) { API: urlFoo, Method: http.MethodPost, Header: map[string]string{ - contentType: "multipart/form-data", + util.ContentType: "multipart/form-data", }, Form: map[string]string{ "key": "value", @@ -319,7 +320,7 @@ func TestTestCase(t *testing.T) { API: urlFoo, Method: http.MethodPost, Header: map[string]string{ - contentType: "application/x-www-form-urlencoded", + util.ContentType: "application/x-www-form-urlencoded", }, Form: map[string]string{ "key": "value", diff --git a/pkg/testing/parser.go b/pkg/testing/parser.go index b6beb1b5..a6b59564 100644 --- a/pkg/testing/parser.go +++ b/pkg/testing/parser.go @@ -1,12 +1,17 @@ package testing import ( + "bytes" "fmt" + "io" + "mime/multipart" "net/http" + "net/url" "os" "strings" "github.com/linuxsuren/api-testing/pkg/render" + "github.com/linuxsuren/api-testing/pkg/util" "gopkg.in/yaml.v2" ) @@ -83,6 +88,37 @@ func (r *Request) Render(ctx interface{}) (err error) { return } +// GetBody returns the request body +func (r *Request) GetBody() (reader io.Reader, err error) { + if len(r.Form) > 0 { + if r.Header[util.ContentType] == util.MultiPartFormData { + multiBody := &bytes.Buffer{} + writer := multipart.NewWriter(multiBody) + for key, val := range r.Form { + writer.WriteField(key, val) + } + + _ = writer.Close() + reader = multiBody + r.Header[util.ContentType] = writer.FormDataContentType() + } else if r.Header[util.ContentType] == util.Form { + data := url.Values{} + for key, val := range r.Form { + data.Set(key, val) + } + reader = strings.NewReader(data.Encode()) + } + } else if r.Body != "" { + reader = bytes.NewBufferString(r.Body) + } else if r.BodyFromFile != "" { + var data []byte + if data, err = os.ReadFile(r.BodyFromFile); err == nil { + reader = bytes.NewBufferString(string(data)) + } + } + return +} + // Render renders the response func (r *Response) Render(ctx interface{}) (err error) { r.StatusCode = zeroThenDefault(r.StatusCode, http.StatusOK) diff --git a/pkg/testing/parser_test.go b/pkg/testing/parser_test.go index 13d0128e..a1808a99 100644 --- a/pkg/testing/parser_test.go +++ b/pkg/testing/parser_test.go @@ -1,11 +1,13 @@ package testing import ( + "io" "net/http" "testing" _ "embed" + "github.com/linuxsuren/api-testing/pkg/util" "github.com/stretchr/testify/assert" ) @@ -212,5 +214,64 @@ func TestTestCase(t *testing.T) { }, testCase) } +func TestGetBody(t *testing.T) { + defaultBody := "fake body" + + tests := []struct { + name string + req *Request + expectBody string + containBody string + expectErr bool + }{{ + name: "normal body", + req: &Request{Body: defaultBody}, + expectBody: defaultBody, + }, { + name: "body from file", + req: &Request{BodyFromFile: "testdata/testcase.yaml"}, + expectBody: testCaseContent, + }, { + name: "multipart form data", + req: &Request{ + Header: map[string]string{ + util.ContentType: util.MultiPartFormData, + }, + Form: map[string]string{ + "key": "value", + }, + }, + containBody: "name=\"key\"\r\n\r\nvalue\r\n", + }, { + name: "normal form", + req: &Request{ + Header: map[string]string{ + util.ContentType: util.Form, + }, + Form: map[string]string{ + "name": "linuxsuren", + }, + }, + expectBody: "name=linuxsuren", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader, err := tt.req.GetBody() + if tt.expectErr { + assert.NotNil(t, err) + } else { + assert.NotNil(t, reader) + data, err := io.ReadAll(reader) + assert.Nil(t, err) + if tt.expectBody != "" { + assert.Equal(t, tt.expectBody, string(data)) + } else { + assert.Contains(t, string(data), tt.containBody) + } + } + }) + } +} + //go:embed testdata/testcase.yaml var testCaseContent string diff --git a/pkg/util/default.go b/pkg/util/default.go index f90cfced..5f6cfa28 100644 --- a/pkg/util/default.go +++ b/pkg/util/default.go @@ -19,3 +19,10 @@ func MakeSureNotNil[T any](inter T) T { } return inter } + +// ContentType is the HTTP header key +const ( + ContentType = "Content-Type" + MultiPartFormData = "multipart/form-data" + Form = "application/x-www-form-urlencoded" +)