diff --git a/api/internal/core/entity/entity.go b/api/internal/core/entity/entity.go index 0e68078bd8..32ed6e0ce1 100644 --- a/api/internal/core/entity/entity.go +++ b/api/internal/core/entity/entity.go @@ -235,6 +235,12 @@ type Script struct { Script interface{} `json:"script,omitempty"` } +// swagger:model GlobalPlugins +type GlobalPlugins struct { + ID interface{} `json:"id"` + Plugins map[string]interface{} `json:"plugins,omitempty"` +} + type ServerInfo struct { BaseInfo LastReportTime int64 `json:"last_report_time,omitempty"` diff --git a/api/internal/core/store/storehub.go b/api/internal/core/store/storehub.go index fe6195352d..b1e1acf143 100644 --- a/api/internal/core/store/storehub.go +++ b/api/internal/core/store/storehub.go @@ -34,6 +34,7 @@ const ( HubKeySsl HubKey = "ssl" HubKeyUpstream HubKey = "upstream" HubKeyScript HubKey = "script" + HubKeyGlobalRule HubKey = "global_rule" HubKeyServerInfo HubKey = `server_info` ) @@ -42,8 +43,15 @@ var ( ) func InitStore(key HubKey, opt GenericStoreOption) error { - if key == HubKeyConsumer || key == HubKeyRoute || - key == HubKeyService || key == HubKeySsl || key == HubKeyUpstream { + hubsNeedCheck := map[HubKey]bool{ + HubKeyConsumer: true, + HubKeyRoute: true, + HubKeySsl: true, + HubKeyService: true, + HubKeyUpstream: true, + HubKeyGlobalRule: true, + } + if _, ok := hubsNeedCheck[key]; ok { validator, err := NewAPISIXJsonSchemaValidator("main." + string(key)) if err != nil { return err @@ -145,6 +153,18 @@ func InitStores() error { return err } + err = InitStore(HubKeyGlobalRule, GenericStoreOption{ + BasePath: "/apisix/global_rules", + ObjType: reflect.TypeOf(entity.GlobalPlugins{}), + KeyFunc: func(obj interface{}) string { + r := obj.(*entity.GlobalPlugins) + return utils.InterfaceToString(r.ID) + }, + }) + if err != nil { + return err + } + err = InitStore(HubKeyServerInfo, GenericStoreOption{ BasePath: "/apisix/data_plane/server_info", ObjType: reflect.TypeOf(entity.ServerInfo{}), diff --git a/api/internal/filter/schema.go b/api/internal/filter/schema.go index a2a99ee124..e9d50f2a94 100644 --- a/api/internal/filter/schema.go +++ b/api/internal/filter/schema.go @@ -37,11 +37,12 @@ import ( ) var resources = map[string]string{ - "routes": "route", - "upstreams": "upstream", - "services": "service", - "consumers": "consumer", - "ssl": "ssl", + "routes": "route", + "upstreams": "upstream", + "services": "service", + "consumers": "consumer", + "ssl": "ssl", + "global_rules": "global_rule", } const ( diff --git a/api/internal/handler/global_rule/global_rule.go b/api/internal/handler/global_rule/global_rule.go new file mode 100644 index 0000000000..81b1877c75 --- /dev/null +++ b/api/internal/handler/global_rule/global_rule.go @@ -0,0 +1,175 @@ +/* + * 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 global_rule + +import ( + "encoding/json" + "reflect" + + "github.com/gin-gonic/gin" + "github.com/shiningrush/droplet" + "github.com/shiningrush/droplet/wrapper" + wgin "github.com/shiningrush/droplet/wrapper/gin" + + "github.com/apisix/manager-api/internal/core/entity" + "github.com/apisix/manager-api/internal/core/store" + "github.com/apisix/manager-api/internal/handler" + "github.com/apisix/manager-api/internal/utils" + "github.com/apisix/manager-api/internal/utils/consts" +) + +type Handler struct { + globalRuleStore store.Interface +} + +func NewHandler() (handler.RouteRegister, error) { + return &Handler{ + globalRuleStore: store.GetStore(store.HubKeyGlobalRule), + }, nil +} + +func (h *Handler) ApplyRoute(r *gin.Engine) { + // global plugins + r.GET("/apisix/admin/global_rules/:id", wgin.Wraps(h.Get, + wrapper.InputType(reflect.TypeOf(GetInput{})))) + r.GET("/apisix/admin/global_rules", wgin.Wraps(h.List, + wrapper.InputType(reflect.TypeOf(ListInput{})))) + r.PUT("/apisix/admin/global_rules/:id", wgin.Wraps(h.Set, + wrapper.InputType(reflect.TypeOf(entity.GlobalPlugins{})))) + r.PUT("/apisix/admin/global_rules", wgin.Wraps(h.Set, + wrapper.InputType(reflect.TypeOf(entity.GlobalPlugins{})))) + + r.PATCH("/apisix/admin/global_rules/:id", consts.ErrorWrapper(Patch)) + r.PATCH("/apisix/admin/global_rules/:id/*path", consts.ErrorWrapper(Patch)) + + r.DELETE("/apisix/admin/global_rules/:id", wgin.Wraps(h.BatchDelete, + wrapper.InputType(reflect.TypeOf(BatchDeleteInput{})))) +} + +type GetInput struct { + ID string `auto_read:"id,path" validate:"required"` +} + +func (h *Handler) Get(c droplet.Context) (interface{}, error) { + input := c.Input().(*GetInput) + + r, err := h.globalRuleStore.Get(input.ID) + if err != nil { + return handler.SpecCodeResponse(err), err + } + return r, nil +} + +type ListInput struct { + store.Pagination +} + +// swagger:operation GET /apisix/admin/global_rules getGlobalRuleList +// +// Return the global rule list according to the specified page number and page size. +// +// --- +// produces: +// - application/json +// parameters: +// - name: page +// in: query +// description: page number +// required: false +// type: integer +// - name: page_size +// in: query +// description: page size +// required: false +// type: integer +// responses: +// '0': +// description: list response +// schema: +// type: array +// items: +// "$ref": "#/definitions/GlobalPlugins" +// default: +// description: unexpected error +// schema: +// "$ref": "#/definitions/ApiError" +func (h *Handler) List(c droplet.Context) (interface{}, error) { + input := c.Input().(*ListInput) + + ret, err := h.globalRuleStore.List(store.ListInput{ + PageSize: input.PageSize, + PageNumber: input.PageNumber, + }) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (h *Handler) Set(c droplet.Context) (interface{}, error) { + input := c.Input().(*entity.GlobalPlugins) + + if err := h.globalRuleStore.Create(c.Context(), input); err != nil { + return handler.SpecCodeResponse(err), err + } + + return nil, nil +} + +func Patch(c *gin.Context) (interface{}, error) { + reqBody, _ := c.GetRawData() + ID := c.Param("id") + subPath := c.Param("path") + + routeStore := store.GetStore(store.HubKeyGlobalRule) + stored, err := routeStore.Get(ID) + if err != nil { + return handler.SpecCodeResponse(err), err + } + + res, err := utils.MergePatch(stored, subPath, reqBody) + if err != nil { + return handler.SpecCodeResponse(err), err + } + + var globalRule entity.GlobalPlugins + err = json.Unmarshal(res, &globalRule) + if err != nil { + return handler.SpecCodeResponse(err), err + } + + if err := routeStore.Update(c, &globalRule, false); err != nil { + return handler.SpecCodeResponse(err), err + } + + return nil, nil +} + +type BatchDeleteInput struct { + ID string `auto_read:"id,path"` +} + +func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) { + input := c.Input().(*BatchDeleteInput) + + if err := h.globalRuleStore.BatchDelete(c.Context(), []string{input.ID}); err != nil { + return handler.SpecCodeResponse(err), err + } + + return nil, nil +} diff --git a/api/internal/handler/global_rule/global_rule_test.go b/api/internal/handler/global_rule/global_rule_test.go new file mode 100644 index 0000000000..62a719d8fb --- /dev/null +++ b/api/internal/handler/global_rule/global_rule_test.go @@ -0,0 +1,336 @@ +/* + * 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 global_rule + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/shiningrush/droplet" + "github.com/shiningrush/droplet/data" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/apisix/manager-api/internal/core/entity" + "github.com/apisix/manager-api/internal/core/store" +) + +func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { + req := httptest.NewRequest(method, path, nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + return w +} + +func TestHandler_ApplyRoute(t *testing.T) { + mStore := &store.MockInterface{} + giveRet := `{ + "id": "test", + "plugins": { + "jwt-auth": {} + } + }` + mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { + assert.Equal(t, "test", args.Get(0)) + }).Return(giveRet, nil) + + h := Handler{globalRuleStore: mStore} + r := gin.New() + h.ApplyRoute(r) + + w := performRequest(r, "GET", "/apisix/admin/global_rules/test") + assert.Equal(t, 200, w.Code) +} + +func TestHandler_Get(t *testing.T) { + tests := []struct { + caseDesc string + giveInput *GetInput + giveRet interface{} + giveErr error + wantErr error + wantGetKey string + wantRet interface{} + }{ + { + caseDesc: "normal", + giveInput: &GetInput{ID: "test"}, + wantGetKey: "test", + giveRet: `{ + "id": "test", + "plugins": { + "jwt-auth": {} + } + }`, + wantRet: `{ + "id": "test", + "plugins": { + "jwt-auth": {} + } + }`, + }, + { + caseDesc: "store get failed", + giveInput: &GetInput{ID: "non-existent-key"}, + wantGetKey: "non-existent-key", + giveErr: fmt.Errorf("data not found"), + wantErr: fmt.Errorf("data not found"), + wantRet: &data.SpecCodeResponse{ + StatusCode: http.StatusNotFound, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.caseDesc, func(t *testing.T) { + getCalled := true + mStore := &store.MockInterface{} + mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) { + getCalled = true + assert.Equal(t, tc.wantGetKey, args.Get(0)) + }).Return(tc.giveRet, tc.giveErr) + + h := Handler{globalRuleStore: mStore} + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ret, err := h.Get(ctx) + assert.True(t, getCalled) + assert.Equal(t, tc.wantRet, ret) + assert.Equal(t, tc.wantErr, err) + }) + } +} + +func TestHandler_List(t *testing.T) { + tests := []struct { + caseDesc string + giveInput *ListInput + giveData []*entity.GlobalPlugins + giveErr error + wantErr error + wantInput store.ListInput + wantRet interface{} + }{ + { + caseDesc: "list all condition", + giveInput: &ListInput{ + Pagination: store.Pagination{ + PageSize: 10, + PageNumber: 1, + }, + }, + wantInput: store.ListInput{ + PageSize: 10, + PageNumber: 1, + }, + giveData: []*entity.GlobalPlugins{ + {ID: "global-rules-1"}, + {ID: "global-rules-2"}, + {ID: "global-rules-3"}, + }, + wantRet: &store.ListOutput{ + Rows: []interface{}{ + &entity.GlobalPlugins{ID: "global-rules-1"}, + &entity.GlobalPlugins{ID: "global-rules-2"}, + &entity.GlobalPlugins{ID: "global-rules-3"}, + }, + TotalSize: 3, + }, + }, + { + caseDesc: "store list failed", + giveInput: &ListInput{ + Pagination: store.Pagination{ + PageSize: 10, + PageNumber: 10, + }, + }, + wantInput: store.ListInput{ + PageSize: 10, + PageNumber: 10, + }, + giveData: []*entity.GlobalPlugins{}, + giveErr: fmt.Errorf("list failed"), + wantErr: fmt.Errorf("list failed"), + }, + } + + for _, tc := range tests { + t.Run(tc.caseDesc, func(t *testing.T) { + getCalled := true + mStore := &store.MockInterface{} + mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { + getCalled = true + input := args.Get(0).(store.ListInput) + assert.Equal(t, tc.wantInput.PageSize, input.PageSize) + assert.Equal(t, tc.wantInput.PageNumber, input.PageNumber) + }).Return(func(input store.ListInput) *store.ListOutput { + var returnData []interface{} + for _, c := range tc.giveData { + if input.Predicate == nil || input.Predicate(c) { + returnData = append(returnData, c) + } + } + return &store.ListOutput{ + Rows: returnData, + TotalSize: len(returnData), + } + }, tc.giveErr) + + h := Handler{globalRuleStore: mStore} + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ret, err := h.List(ctx) + assert.True(t, getCalled) + assert.Equal(t, tc.wantRet, ret) + assert.Equal(t, tc.wantErr, err) + }) + } +} + +func TestHandler_Set(t *testing.T) { + tests := []struct { + caseDesc string + giveInput *entity.GlobalPlugins + giveCtx context.Context + giveErr error + wantErr error + wantInput *entity.GlobalPlugins + wantRet interface{} + wantCalled bool + }{ + { + caseDesc: "normal", + giveInput: &entity.GlobalPlugins{ + ID: "name", + Plugins: map[string]interface{}{ + "jwt-auth": map[string]interface{}{}, + }, + }, + giveCtx: context.WithValue(context.Background(), "test", "value"), + wantInput: &entity.GlobalPlugins{ + ID: "name", + Plugins: map[string]interface{}{ + "jwt-auth": map[string]interface{}{}, + }, + }, + wantRet: nil, + wantCalled: true, + }, + { + caseDesc: "store create failed", + giveInput: &entity.GlobalPlugins{ + ID: "name", + Plugins: nil, + }, + giveErr: fmt.Errorf("create failed"), + wantInput: &entity.GlobalPlugins{ + ID: "name", + Plugins: map[string]interface{}(nil), + }, + wantErr: fmt.Errorf("create failed"), + wantRet: &data.SpecCodeResponse{ + StatusCode: http.StatusInternalServerError, + }, + wantCalled: true, + }, + } + + for _, tc := range tests { + t.Run(tc.caseDesc, func(t *testing.T) { + methodCalled := true + mStore := &store.MockInterface{} + mStore.On("Create", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + methodCalled = true + assert.Equal(t, tc.giveCtx, args.Get(0)) + assert.Equal(t, tc.wantInput, args.Get(1)) + }).Return(tc.giveErr) + + h := Handler{globalRuleStore: mStore} + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ctx.SetContext(tc.giveCtx) + ret, err := h.Set(ctx) + assert.Equal(t, tc.wantCalled, methodCalled) + assert.Equal(t, tc.wantRet, ret) + assert.Equal(t, tc.wantErr, err) + }) + } +} + +func TestHandler_BatchDelete(t *testing.T) { + tests := []struct { + caseDesc string + giveInput *BatchDeleteInput + giveCtx context.Context + giveErr error + wantErr error + wantInput []string + wantRet interface{} + }{ + { + caseDesc: "normal", + giveInput: &BatchDeleteInput{ + ID: "user1", + }, + giveCtx: context.WithValue(context.Background(), "test", "value"), + wantInput: []string{ + "user1", + }, + }, + { + caseDesc: "store delete failed", + giveInput: &BatchDeleteInput{ + ID: "user2", + }, + giveCtx: context.WithValue(context.Background(), "test", "value"), + giveErr: fmt.Errorf("delete failed"), + wantInput: []string{ + "user2", + }, + wantErr: fmt.Errorf("delete failed"), + wantRet: &data.SpecCodeResponse{ + StatusCode: http.StatusInternalServerError, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.caseDesc, func(t *testing.T) { + methodCalled := true + mStore := &store.MockInterface{} + mStore.On("BatchDelete", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + methodCalled = true + assert.Equal(t, tc.giveCtx, args.Get(0)) + assert.Equal(t, tc.wantInput, args.Get(1)) + }).Return(tc.giveErr) + + h := Handler{globalRuleStore: mStore} + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ctx.SetContext(tc.giveCtx) + ret, err := h.BatchDelete(ctx) + assert.True(t, methodCalled) + assert.Equal(t, tc.wantErr, err) + assert.Equal(t, tc.wantRet, ret) + }) + } +} diff --git a/api/internal/route.go b/api/internal/route.go index f36e819b5f..02e1650e47 100644 --- a/api/internal/route.go +++ b/api/internal/route.go @@ -31,6 +31,7 @@ import ( "github.com/apisix/manager-api/internal/handler" "github.com/apisix/manager-api/internal/handler/authentication" "github.com/apisix/manager-api/internal/handler/consumer" + "github.com/apisix/manager-api/internal/handler/global_rule" "github.com/apisix/manager-api/internal/handler/healthz" "github.com/apisix/manager-api/internal/handler/label" "github.com/apisix/manager-api/internal/handler/plugin" @@ -68,6 +69,7 @@ func SetUpRouter() *gin.Engine { plugin.NewHandler, healthz.NewHandler, authentication.NewHandler, + global_rule.NewHandler, route_online_debug.NewHandler, server_info.NewHandler, label.NewHandler, diff --git a/api/test/e2e/base.go b/api/test/e2e/base.go index 5c79f857c1..4bed9cb1d7 100644 --- a/api/test/e2e/base.go +++ b/api/test/e2e/base.go @@ -238,5 +238,4 @@ func testCaseCheck(tc HttpTestCase, t *testing.T) { resp.Body().Contains(tc.ExpectBody) } }) - } diff --git a/api/test/e2e/global_rule_test.go b/api/test/e2e/global_rule_test.go new file mode 100644 index 0000000000..19a59b3961 --- /dev/null +++ b/api/test/e2e/global_rule_test.go @@ -0,0 +1,234 @@ +/* + * 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 e2e + +import ( + "net/http" + "testing" +) + +func TestGlobalRule(t *testing.T) { + tests := []HttpTestCase{ + { + Desc: "make sure the route doesn't exist", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + }, + { + Desc: "create route", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1981, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "create global rule", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/global_rules/1", + Method: http.MethodPut, + Body: `{ + "id": "1", + "plugins": { + "response-rewrite": { + "headers": { + "X-VERSION":"1.0" + } + }, + "uri-blocker": { + "block_rules": ["select.+(from|limit)", "(?:(union(.*?)select))"] + } + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "verify route with header", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusOK, + ExpectBody: "hello world", + ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, + Sleep: sleepTime, + }, + { + Desc: "verify route that should be blocked", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Query: "name=;select%20from%20sys", + ExpectStatus: http.StatusForbidden, + ExpectHeaders: map[string]string{"X-VERSION": "1.0"}, + }, + { + Desc: "update route with same plugin response-rewrite", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "plugins": { + "response-rewrite": { + "headers": { + "X-VERSION":"2.0" + } + } + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1981, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "verify route that header should be the same as the route config", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusOK, + ExpectBody: "hello world", + ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, + Sleep: sleepTime, + }, + { + Desc: "the uncovered global plugin should works", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Query: "name=;select%20from%20sys", + ExpectStatus: http.StatusForbidden, + //ExpectHeaders: map[string]string{"X-VERSION":"2.0"}, + }, + { + Desc: "route patch to enable key-auth", + Object: ManagerApiExpect(t), + Method: http.MethodPatch, + Path: "/apisix/admin/global_rules/1/plugins", + Body: `{ + "response-rewrite": { + "headers": { + "X-VERSION":"1.0" + } + }, + "key-auth": {} + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "make sure that patch succeeded", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusUnauthorized, + Sleep: sleepTime, + }, + { + Desc: "route patch to disable key-auth", + Object: ManagerApiExpect(t), + Method: http.MethodPatch, + Path: "/apisix/admin/global_rules/1", + Body: `{ + "plugins": { + "key-auth": null + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "make sure that patch succeeded", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusOK, + ExpectBody: "hello world", + ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, + Sleep: sleepTime, + }, + { + Desc: "delete global rule", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/global_rules/1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "make sure the global rule has been deleted", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Path: "/apisix/admin/global_rules/1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"code":10001,"message":"data not found"`, + Sleep: sleepTime, + }, + { + Desc: "verify route that should not be blocked", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Query: "name=;select%20from%20sys", + ExpectStatus: http.StatusOK, + ExpectHeaders: map[string]string{"X-VERSION": "2.0"}, + }, + { + Desc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + Desc: "make sure the route has been deleted", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc, t) + } +}