Skip to content
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

chore: introduce ginkgo framework for backend E2E testing #1319

Merged
merged 28 commits into from
Feb 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d80d8de
test
johzchen Jan 16, 2021
39ac403
Revert "test"
johzchen Jan 16, 2021
adf1d59
chore: introduce ginkgo framework for E2E testing
johzchen Jan 17, 2021
e34ad75
chore: introduce ginkgo framework for E2E testing
johzchen Jan 17, 2021
b24088f
test: common test
johzchen Jan 17, 2021
d2e59d2
test: online debug
johzchen Jan 17, 2021
6eac4a5
test: route test
johzchen Jan 17, 2021
40b11ec
test: route test cases
johzchen Jan 17, 2021
6fa39ee
ci: add ci
johzchen Jan 17, 2021
f41a617
fix: yaml format
johzchen Jan 17, 2021
23218e4
fix: typo
johzchen Jan 17, 2021
4bb4205
fix: error
johzchen Jan 17, 2021
05b4e95
fix CI failed
johzchen Jan 18, 2021
76fd5e9
Merge branch 'master' into e2e-test-framework
johzchen Jan 18, 2021
9b058ff
chore: code format
johzchen Jan 18, 2021
38669f3
chore: merge master
johzchen Feb 2, 2021
800aae5
chore: remove irrelevant files
johzchen Feb 3, 2021
2e6ce1f
Merge branch 'master' into e2e-test-framework
johzchen Feb 3, 2021
c59a456
chore: minimal test cases
johzchen Feb 4, 2021
f570651
fix review
johzchen Feb 4, 2021
89ee750
Merge branch 'master' into e2e-test-framework
johzchen Feb 5, 2021
de421b7
fix: download APISIX dockerfile for e2e test
johzchen Feb 5, 2021
51de62b
Merge branch 'master' into e2e-test-framework
johzchen Feb 5, 2021
d5ed119
Merge branch 'master' into e2e-test-framework
johzchen Feb 5, 2021
14ad3e8
fix: config
johzchen Feb 5, 2021
05640ff
Merge branch 'master' into e2e-test-framework
johzchen Feb 5, 2021
4494434
test: debug
johzchen Feb 5, 2021
0a47e3a
fix docker compose down
johzchen Feb 5, 2021
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
55 changes: 55 additions & 0 deletions .github/workflows/backend-e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,58 @@ jobs:
working-directory: ./api/test/testdata
run: |
bash <(curl -s https://codecov.io/bash) -f ./integrationcover.out


backend-e2e-test-ginkgo:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: setup go
uses: actions/setup-go@v2.1.3
with:
go-version: '1.13'

- name: Modify conf.yaml Configure for use by the manage-api cluster
run: |
sed -i 's/127.0.0.1:2379/172.16.238.10:2379/' ./api/conf/conf.yaml
sed -i 's/127.0.0.1/0.0.0.0/' ./api/conf/conf.yaml
sed -i '/172.16.238.10:2379/a\ - 172.16.238.11:2379' ./api/conf/conf.yaml
sed -i '/172.16.238.10:2379/a\ - 172.16.238.12:2379' ./api/conf/conf.yaml
sed -i 's@127.0.0.0/24@0.0.0.0/0@' ./api/conf/conf.yaml

- name: download file Dockerfile-apisix
working-directory: ./api/test/docker
run: |
curl -o Dockerfile-apisix https://raw.githubusercontent.com/apache/apisix-docker/master/alpine/Dockerfile

- name: run docker compose
working-directory: ./api/test/docker
run: |
docker-compose up -d
sleep 5
docker logs docker_managerapi_1

- name: install ginkgo cli
run: go get github.com/onsi/ginkgo/ginkgo

- name: run test
working-directory: ./api/test/e2enew
run: ginkgo -r

- name: stop docker compose
working-directory: ./api/test/docker
run: |
docker-compose down
sleep 10

- name: output test coverage
working-directory: ./api/test/testdata
run: |
go tool cover -func=./integrationcover.out

- name: upload coverage profile
working-directory: ./api/test/testdata
run: |
bash <(curl -s https://codecov.io/bash) -f ./integrationcover.out
298 changes: 298 additions & 0 deletions api/test/e2enew/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package base

import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"os/exec"
"strings"
"time"

"github.com/gavv/httpexpect/v2"
"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
)

var (
token string

UpstreamIp = "172.16.238.20"
APISIXHost = "http://127.0.0.1:9080"
APISIXInternalUrl = "http://172.16.238.30:9080"
APISIXSingleWorkerHost = "http://127.0.0.1:9081"
ManagerAPIHost = "http://127.0.0.1:9000"
)

func GetToken() string {
nic-chen marked this conversation as resolved.
Show resolved Hide resolved
if token != "" {
return token
}

requestBody := `{
"username": "admin",
"password": "admin"
}`

url := ManagerAPIHost + "/apisix/admin/user/login"
body, _, err := HttpPost(url, nil, requestBody)
if err != nil {
panic(err)
}

respond := gjson.ParseBytes(body)
token = respond.Get("data.token").String()

return token
}

func getTestingHandle() httpexpect.LoggerReporter {
return ginkgo.GinkgoT()
}

func ManagerApiExpect() *httpexpect.Expect {
t := getTestingHandle()
return httpexpect.New(t, ManagerAPIHost)
}

func APISIXExpect() *httpexpect.Expect {
t := getTestingHandle()
return httpexpect.New(t, APISIXHost)
}

func APISIXHTTPSExpect() *httpexpect.Expect {
t := getTestingHandle()
e := httpexpect.WithConfig(httpexpect.Config{
BaseURL: "https://www.test2.com:9443",
Reporter: httpexpect.NewAssertReporter(t),
Client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
// accept any certificate; for testing only!
InsecureSkipVerify: true,
},
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if addr == "www.test2.com:9443" {
addr = "127.0.0.1:9443"
}
dialer := &net.Dialer{}
return dialer.DialContext(ctx, network, addr)
},
},
},
})

return e
}

var SleepTime = time.Duration(300) * time.Millisecond

type HttpTestCase struct {
Desc string
Object *httpexpect.Expect
Method string
Path string
Query string
Body string
Headers map[string]string
Headers_test map[string]interface{}
ExpectStatus int
ExpectCode int
ExpectMessage string
ExpectBody interface{}
UnexpectBody interface{}
ExpectHeaders map[string]string
Sleep time.Duration //ms
}

func RunTestCase(tc HttpTestCase) {
//init
expectObj := tc.Object
var req *httpexpect.Request
switch tc.Method {
case http.MethodGet:
req = expectObj.GET(tc.Path)
case http.MethodPut:
req = expectObj.PUT(tc.Path)
case http.MethodPost:
req = expectObj.POST(tc.Path)
case http.MethodDelete:
req = expectObj.DELETE(tc.Path)
case http.MethodPatch:
req = expectObj.PATCH(tc.Path)
case http.MethodOptions:
req = expectObj.OPTIONS(tc.Path)
default:
}

if req == nil {
panic("fail to init request")
}

if tc.Sleep != 0 {
time.Sleep(tc.Sleep)
} else {
time.Sleep(time.Duration(50) * time.Millisecond)
}

if tc.Query != "" {
req.WithQueryString(tc.Query)
}

// set header
setContentType := false
for key, val := range tc.Headers {
req.WithHeader(key, val)
if strings.ToLower(key) == "content-type" {
setContentType = true
}
}

// set default content-type
if !setContentType {
req.WithHeader("Content-Type", "application/json")
}

// set body
if tc.Body != "" {
req.WithText(tc.Body)
}

// respond check
resp := req.Expect()

// match http status
if tc.ExpectStatus != 0 {
resp.Status(tc.ExpectStatus)
}

// match headers
if tc.ExpectHeaders != nil {
for key, val := range tc.ExpectHeaders {
resp.Header(key).Equal(val)
}
}

// match body
if tc.ExpectBody != nil {
//assert.Contains(t, []string{"string", "[]string"}, reflect.TypeOf(tc.ExpectBody).String())
if body, ok := tc.ExpectBody.(string); ok {
if body == "" {
// "" indicates the body is expected to be empty
resp.Body().Empty()
} else {
resp.Body().Contains(body)
}
} else if bodies, ok := tc.ExpectBody.([]string); ok && len(bodies) != 0 {
for _, b := range bodies {
resp.Body().Contains(b)
}
}
}

// match UnexpectBody
if tc.UnexpectBody != nil {
//assert.Contains(t, []string{"string", "[]string"}, reflect.TypeOf(tc.UnexpectBody).String())
if body, ok := tc.UnexpectBody.(string); ok {
// "" indicates the body is expected to be non empty
if body == "" {
resp.Body().NotEmpty()
} else {
resp.Body().NotContains(body)
}
} else if bodies, ok := tc.UnexpectBody.([]string); ok && len(bodies) != 0 {
for _, b := range bodies {
resp.Body().NotContains(b)
}
}
}
}

func ReadAPISIXErrorLog() string {
t := getTestingHandle()
cmd := exec.Command("pwd")
pwdByte, err := cmd.CombinedOutput()
pwd := string(pwdByte)
pwd = strings.Replace(pwd, "\n", "", 1)
pwd = pwd[:strings.Index(pwd, "/e2e")]
bytes, err := ioutil.ReadFile(pwd + "/docker/apisix_logs/error.log")
assert.Nil(t, err)
logContent := string(bytes)

return logContent
}

func CleanAPISIXErrorLog() {
t := getTestingHandle()
cmd := exec.Command("pwd")
pwdByte, err := cmd.CombinedOutput()
pwd := string(pwdByte)
pwd = strings.Replace(pwd, "\n", "", 1)
pwd = pwd[:strings.Index(pwd, "/e2e")]
cmd = exec.Command("sudo", "echo", " > ", pwd+"/docker/apisix_logs/error.log")
_, err = cmd.CombinedOutput()
if err != nil {
fmt.Println("cmd error:", err.Error())
}
assert.Nil(t, err)
}

func GetResourceList(resource string) string {
t := getTestingHandle()
body, _, err := HttpGet(ManagerAPIHost+"/apisix/admin/"+resource, map[string]string{"Authorization": GetToken()})
assert.Nil(t, err)
return string(body)
}

func CleanResource(resource string) {
resources := GetResourceList(resource)
list := gjson.Get(resources, "data.rows").Value().([]interface{})
for _, item := range list {
resourceObj := item.(map[string]interface{})
tc := HttpTestCase{
Desc: "delete " + resource + "/" + resourceObj["id"].(string),
Object: ManagerApiExpect(),
Method: http.MethodDelete,
Path: "/apisix/admin/" + resource + "/" + resourceObj["id"].(string),
Headers: map[string]string{"Authorization": GetToken()},
ExpectStatus: http.StatusOK,
}
RunTestCase(tc)
}
time.Sleep(SleepTime)
}

var jwtToken string

func GetJwtToken(userKey string) string {
if jwtToken != "" {
return jwtToken
}
time.Sleep(SleepTime)

body, status, err := HttpGet(APISIXHost+"/apisix/plugin/jwt/sign?key="+userKey, nil)
assert.Nil(ginkgo.GinkgoT(), err)
assert.Equal(ginkgo.GinkgoT(), http.StatusOK, status)
jwtToken = string(body)

return jwtToken
}
Loading