/* * 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" "strconv" "strings" "time" "github.com/gavv/httpexpect/v2" . "github.com/onsi/ginkgo/v2" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" ) var ( token string UpstreamIp = "upstream" UpstreamGrpcIp = "upstream_grpc" UpstreamHTTPBinIp = "upstream_httpbin" APISIXHost = "http://127.0.0.1:9080" APISIXAdminAPIHost = "http://127.0.0.1:9180" APISIXInternalUrl = "http://apisix:9080" APISIXSingleWorkerHost = "http://127.0.0.1:9081" ManagerAPIHost = "http://127.0.0.1:9000" PrometheusExporter = "http://127.0.0.1:9091" ) func GetToken() string { 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 GinkgoT() } func ManagerApiExpect() *httpexpect.Expect { return httpexpect.New(GinkgoT(), ManagerAPIHost) } func APISIXExpect() *httpexpect.Expect { return httpexpect.New(GinkgoT(), APISIXHost) } func APISIXAdminAPIExpect() *httpexpect.Expect { return httpexpect.New(GinkgoT(), APISIXAdminAPIHost) } func APISIXStreamProxyExpect(port uint16, sni string) *httpexpect.Expect { if port == 0 { port = 10090 } if sni != "" { addr := net.JoinHostPort(sni, strconv.Itoa(int(port))) return httpexpect.WithConfig(httpexpect.Config{ BaseURL: "https://" + addr, Reporter: httpexpect.NewAssertReporter(GinkgoT()), 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) { addr = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))) dialer := &net.Dialer{} return dialer.DialContext(ctx, network, addr) }, }, }, }) } else { return httpexpect.New(GinkgoT(), "http://"+net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))) } } func PrometheusExporterExpect() *httpexpect.Expect { return httpexpect.New(GinkgoT(), PrometheusExporter) } func APISIXHTTPSExpect() *httpexpect.Expect { e := httpexpect.WithConfig(httpexpect.Config{ BaseURL: "https://www.test2.com:9443", Reporter: httpexpect.NewAssertReporter(GinkgoT()), 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 = 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 { 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(GinkgoT(), err) logContent := string(bytes) return logContent } func CleanAPISIXErrorLog() { cmd := exec.Command("pwd") pwdByte, err := cmd.CombinedOutput() pwd := string(pwdByte) pwd = strings.Replace(pwd, "\n", "", 1) pwd = pwd[:strings.Index(pwd, "/e2e")] cmdStr := "echo | sudo tee " + pwd + "/docker/apisix_logs/error.log" cmd = exec.Command("bash", "-c", cmdStr) _, err = cmd.Output() if err != nil { fmt.Println("cmd error:", err.Error()) } assert.Nil(GinkgoT(), err) } func GetResourceList(resource string) string { body, _, err := HttpGet(ManagerAPIHost+"/apisix/admin/"+resource, map[string]string{"Authorization": GetToken()}) assert.Nil(GinkgoT(), 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()}, } RunTestCase(tc) } time.Sleep(SleepTime) } func CleanAllResource() { CleanResource("routes") CleanResource("upstreams") CleanResource("consumers") CleanResource("services") CleanResource("global_rules") CleanResource("plugin_configs") CleanResource("proto") CleanResource("ssl") CleanResource("stream_routes") } func RestartManagerAPI() { e := exec.Command("docker", "restart", "docker_managerapi") e.Run() } 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(GinkgoT(), err) assert.Equal(GinkgoT(), http.StatusOK, status) jwtToken = string(body) return jwtToken }