This is an automated email from the ASF dual-hosted git repository.

starsz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 352a64d  feat: refactor route unit test (#1315)
352a64d is described below

commit 352a64d810b5ff5fe2cb110498c5c8449621468a
Author: Peter Zhu <[email protected]>
AuthorDate: Fri Feb 5 16:54:32 2021 +0800

    feat: refactor route unit test (#1315)
---
 api/internal/handler/route/route.go      |   45 +-
 api/internal/handler/route/route_test.go | 3256 +++++++++++++++---------------
 2 files changed, 1588 insertions(+), 1713 deletions(-)

diff --git a/api/internal/handler/route/route.go 
b/api/internal/handler/route/route.go
index 7b710c2..52336f0 100644
--- a/api/internal/handler/route/route.go
+++ b/api/internal/handler/route/route.go
@@ -94,8 +94,7 @@ func (h *Handler) Patch(c droplet.Context) (interface{}, 
error) {
        ID := input.ID
        subPath := input.SubPath
 
-       routeStore := store.GetStore(store.HubKeyRoute)
-       stored, err := routeStore.Get(c.Context(), ID)
+       stored, err := h.routeStore.Get(c.Context(), ID)
        if err != nil {
                return handler.SpecCodeResponse(err), err
        }
@@ -111,7 +110,7 @@ func (h *Handler) Patch(c droplet.Context) (interface{}, 
error) {
                return handler.SpecCodeResponse(err), err
        }
 
-       ret, err := routeStore.Update(c.Context(), &route, false)
+       ret, err := h.routeStore.Update(c.Context(), &route, false)
        if err != nil {
                return handler.SpecCodeResponse(err), err
        }
@@ -522,18 +521,6 @@ func (h *Handler) BatchDelete(c droplet.Context) 
(interface{}, error) {
        return nil, nil
 }
 
-type ExistInput struct {
-       Name string `auto_read:"name,query"`
-}
-
-func toRows(list *store.ListOutput) []store.Row {
-       rows := make([]store.Row, list.TotalSize)
-       for i := range list.Rows {
-               rows[i] = list.Rows[i].(*entity.Route)
-       }
-       return rows
-}
-
 type ExistCheckInput struct {
        Name    string `auto_read:"name,query"`
        Exclude string `auto_read:"exclude,query"`
@@ -570,30 +557,26 @@ func (h *Handler) Exist(c droplet.Context) (interface{}, 
error) {
        input := c.Input().(*ExistCheckInput)
        name := input.Name
        exclude := input.Exclude
-       routeStore := store.GetStore(store.HubKeyRoute)
 
-       ret, err := routeStore.List(c.Context(), store.ListInput{
-               Predicate:  nil,
+       ret, err := h.routeStore.List(c.Context(), store.ListInput{
+               Predicate: func(obj interface{}) bool {
+                       r := obj.(*entity.Route)
+                       if r.Name == name && r.ID != exclude {
+                               return true
+                       }
+
+                       return false
+               },
                PageSize:   0,
                PageNumber: 0,
        })
-
        if err != nil {
                return nil, err
        }
 
-       sort := store.NewSort(nil)
-       filter := store.NewFilter([]string{"name", name})
-       pagination := store.NewPagination(0, 0)
-       query := store.NewQuery(sort, filter, pagination)
-       rows := store.NewFilterSelector(toRows(ret), query)
-
-       if len(rows) > 0 {
-               r := rows[0].(*entity.Route)
-               if r.ID != exclude {
-                       return &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
-                               consts.InvalidParam("Route name is reduplicate")
-               }
+       if ret.TotalSize > 0 {
+               return &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       consts.InvalidParam("Route name is reduplicate")
        }
 
        return nil, nil
diff --git a/api/internal/handler/route/route_test.go 
b/api/internal/handler/route/route_test.go
index c95dba2..0226f76 100644
--- a/api/internal/handler/route/route_test.go
+++ b/api/internal/handler/route/route_test.go
@@ -14,1743 +14,1635 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package route
 
 import (
        "encoding/json"
+       "errors"
+       "fmt"
        "net/http"
        "testing"
-       "time"
 
        "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/conf"
        "github.com/apisix/manager-api/internal/core/entity"
-       "github.com/apisix/manager-api/internal/core/storage"
        "github.com/apisix/manager-api/internal/core/store"
+       "github.com/apisix/manager-api/internal/handler"
+       "github.com/apisix/manager-api/internal/utils/consts"
 )
 
-func TestRoute(t *testing.T) {
-       // init
-       err := storage.InitETCDClient(conf.ETCDConfig)
-       assert.Nil(t, err)
-       err = store.InitStores()
-       assert.Nil(t, err)
+type testCase struct {
+       caseDesc     string
+       giveInput    interface{}
+       mockInput    interface{}
+       mockRet      interface{}
+       mockErr      interface{}
+       wantRet      interface{}
+       wantErr      interface{}
+       called       bool
+       serviceInput string
+       scriptRet    interface{}
+       scriptErr    error
+       serviceRet   interface{}
+       serviceErr   error
+       upstreamRet  interface{}
+       upstreamErr  error
+}
 
-       handler := &Handler{
-               routeStore:    store.GetStore(store.HubKeyRoute),
-               svcStore:      store.GetStore(store.HubKeyService),
-               upstreamStore: store.GetStore(store.HubKeyUpstream),
-               scriptStore:   store.GetStore(store.HubKeyScript),
+var DagScript = `
+{
+    "rule":{
+        "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
+        "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[
+            [
+                "code == 403",
+                "b93d622c-92ef-48b4-b6bb-57e1ce893ee3"
+            ],
+            [
+                "",
+                "988ef5c2-c896-4606-a666-3d4cbe24a731"
+            ]
+        ]
+    },
+    "conf":{
+        "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
+            "name":"uri-blocker",
+            "conf":{
+                "block_rules":[
+                    "root.exe",
+                    "root.m+"
+                ],
+                "rejected_code":403
+            }
+        },
+        "988ef5c2-c896-4606-a666-3d4cbe24a731":{
+            "name":"kafka-logger",
+            "conf":{
+                "batch_max_size":1000,
+                "broker_list":{
+
+                },
+                "buffer_duration":60,
+                "inactive_timeout":5,
+                "include_req_body":false,
+                "kafka_topic":"1",
+                "key":"2",
+                "max_retry_count":0,
+                "name":"kafka logger",
+                "retry_delay":1,
+                "timeout":3
+            }
+        },
+        "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
+            "name":"fault-injection",
+            "conf":{
+                "abort":{
+                    "body":"200",
+                    "http_status":300
+                },
+                "delay":{
+                    "duration":500
+                }
+            }
+        }
+    },
+    "chart":{
+        "hovered":{
+
+        },
+        "links":{
+            "3a110c30-d6f3-40b1-a8ac-b828cfaa2489":{
+                "from":{
+                    "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
+                    "portId":"port3"
+                },
+                "id":"3a110c30-d6f3-40b1-a8ac-b828cfaa2489",
+                "to":{
+                    "nodeId":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
+                    "portId":"port1"
+                }
+            },
+            "c1958993-c1ef-44b1-bb32-7fc6f34870c2":{
+                "from":{
+                    "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
+                    "portId":"port2"
+                },
+                "id":"c1958993-c1ef-44b1-bb32-7fc6f34870c2",
+                "to":{
+                    "nodeId":"988ef5c2-c896-4606-a666-3d4cbe24a731",
+                    "portId":"port1"
+                }
+            },
+            "f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23":{
+                "from":{
+                    "nodeId":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
+                    "portId":"port2"
+                },
+                "id":"f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23",
+                "to":{
+                    "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
+                    "portId":"port1"
+                }
+            }
+        },
+        "nodes":{
+            "3365eca3-4bc8-4769-bab3-1485dfd6a43c":{
+                "id":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
+                "orientation":0,
+                "ports":{
+                    "port1":{
+                        "id":"port1",
+                        "position":{
+                            "x":107,
+                            "y":0
+                        },
+                        "type":"input"
+                    },
+                    "port2":{
+                        "id":"port2",
+                        "position":{
+                            "x":92,
+                            "y":96
+                        },
+                        "properties":{
+                            "value":"no"
+                        },
+                        "type":"output"
+                    },
+                    "port3":{
+                        "id":"port3",
+                        "position":{
+                            "x":122,
+                            "y":96
+                        },
+                        "properties":{
+                            "value":"yes"
+                        },
+                        "type":"output"
+                    }
+                },
+                "position":{
+                    "x":750.2627969928922,
+                    "y":301.0370335799397
+                },
+                "properties":{
+                    "customData":{
+                        "name":"code == 403",
+                        "type":1
+                    }
+                },
+                "size":{
+                    "height":96,
+                    "width":214
+                },
+                "type":"判断条件"
+            },
+            "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
+                "id":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
+                "orientation":0,
+                "ports":{
+                    "port1":{
+                        "id":"port1",
+                        "position":{
+                            "x":100,
+                            "y":0
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"input"
+                    },
+                    "port2":{
+                        "id":"port2",
+                        "position":{
+                            "x":100,
+                            "y":96
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"output"
+                    }
+                },
+                "position":{
+                    "x":741.5684544145346,
+                    "y":126.75879247285502
+                },
+                "properties":{
+                    "customData":{
+                        "data":{
+                            "block_rules":[
+                                "root.exe",
+                                "root.m+"
+                            ],
+                            "rejected_code":403
+                        },
+                        "name":"uri-blocker",
+                        "type":0
+                    }
+                },
+                "size":{
+                    "height":96,
+                    "width":201
+                },
+                "type":"uri-blocker"
+            },
+            "988ef5c2-c896-4606-a666-3d4cbe24a731":{
+                "id":"988ef5c2-c896-4606-a666-3d4cbe24a731",
+                "orientation":0,
+                "ports":{
+                    "port1":{
+                        "id":"port1",
+                        "position":{
+                            "x":106,
+                            "y":0
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"input"
+                    },
+                    "port2":{
+                        "id":"port2",
+                        "position":{
+                            "x":106,
+                            "y":96
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"output"
+                    }
+                },
+                "position":{
+                    "x":607.9687500000001,
+                    "y":471.17788461538447
+                },
+                "properties":{
+                    "customData":{
+                        "data":{
+                            "batch_max_size":1000,
+                            "broker_list":{
+
+                            },
+                            "buffer_duration":60,
+                            "inactive_timeout":5,
+                            "include_req_body":false,
+                            "kafka_topic":"1",
+                            "key":"2",
+                            "max_retry_count":0,
+                            "name":"kafka logger",
+                            "retry_delay":1,
+                            "timeout":3
+                        },
+                        "name":"kafka-logger",
+                        "type":0
+                    }
+                },
+                "size":{
+                    "height":96,
+                    "width":212
+                },
+                "type":"kafka-logger"
+            },
+            "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
+                "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
+                "orientation":0,
+                "ports":{
+                    "port1":{
+                        "id":"port1",
+                        "position":{
+                            "x":110,
+                            "y":0
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"input"
+                    },
+                    "port2":{
+                        "id":"port2",
+                        "position":{
+                            "x":110,
+                            "y":96
+                        },
+                        "properties":{
+                            "custom":"property"
+                        },
+                        "type":"output"
+                    }
+                },
+                "position":{
+                    "x":988.9074986362261,
+                    "y":478.62041800736495
+                },
+                "properties":{
+                    "customData":{
+                        "data":{
+                            "abort":{
+                                "body":"200",
+                                "http_status":300
+                            },
+                            "delay":{
+                                "duration":500
+                            }
+                        },
+                        "name":"fault-injection",
+                        "type":0
+                    }
+                },
+                "size":{
+                    "height":96,
+                    "width":219
+                },
+                "type":"fault-injection"
+            }
+        },
+        "offset":{
+            "x":-376.83,
+            "y":87.98
+        },
+        "scale":0.832,
+        "selected":{
+            "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
+            "type":"node"
+        }
+    }
+}`
+
+func TestRoute_Get(t *testing.T) {
+       tests := []testCase{
+               {
+                       caseDesc:  "route: get success",
+                       giveInput: &GetInput{ID: "r1"},
+                       mockInput: "r1",
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               URI: "/test",
+                       },
+                       mockErr: nil,
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Script: "script",
+                               URI:    "/test",
+                       },
+                       wantErr:   nil,
+                       called:    true,
+                       scriptRet: &entity.Script{ID: "r1", Script: "script"},
+                       scriptErr: nil,
+               },
+               {
+                       caseDesc:  "route: store get failed",
+                       giveInput: &GetInput{ID: "failed_key"},
+                       mockInput: "failed_key",
+                       mockRet:   nil,
+                       mockErr:   fmt.Errorf("get failed"),
+                       wantRet: &data.SpecCodeResponse{
+                               StatusCode: http.StatusNotFound,
+                       },
+                       wantErr: fmt.Errorf("get failed"),
+                       called:  true,
+               },
+               {
+                       caseDesc:  "script: store get failed",
+                       giveInput: &GetInput{ID: "failed_key"},
+                       mockInput: "failed_key",
+                       mockRet:   nil,
+                       mockErr:   fmt.Errorf("get failed"),
+                       wantRet: &data.SpecCodeResponse{
+                               StatusCode: http.StatusNotFound,
+                       },
+                       wantErr:   fmt.Errorf("get failed"),
+                       scriptErr: errors.New("get failed"),
+                       called:    true,
+               },
        }
-       assert.NotNil(t, handler)
-
-       //create Note: depends on lib `dag-to-lua` if script exists
-       ctx := droplet.NewContext()
-       route := &entity.Route{}
-       reqBody := `{
-         "id": "1",
-         "name": "aaaa",
-         "uri": "/index.html",
-         "hosts": ["foo.com", "*.bar.com"],
-         "vars": [],
-         "remote_addrs": ["127.0.0.0/8"],
-         "methods": ["PUT", "GET"],
-         "status": 1,
-         "upstream": {
-             "type": "roundrobin",
-             "nodes": [{
-                 "host": "www.a.com",
-                 "port": 80,
-                 "weight": 1
-             }]
-         },
-         "script":{
-             "rule":{
-                 "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                 "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[
-                     [
-                         "code == 403",
-                         "b93d622c-92ef-48b4-b6bb-57e1ce893ee3"
-                     ],
-                     [
-                         "",
-                         "988ef5c2-c896-4606-a666-3d4cbe24a731"
-                     ]
-                 ]
-             },
-             "conf":{
-                 "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
-                     "name":"uri-blocker",
-                     "conf":{
-                         "block_rules":[
-                             "root.exe",
-                             "root.m+"
-                         ],
-                         "rejected_code":403
-                     }
-                 },
-                 "988ef5c2-c896-4606-a666-3d4cbe24a731":{
-                     "name":"kafka-logger",
-                     "conf":{
-                         "batch_max_size":1000,
-                         "broker_list":{
-       
-                         },
-                         "buffer_duration":60,
-                         "inactive_timeout":5,
-                         "include_req_body":false,
-                         "kafka_topic":"1",
-                         "key":"2",
-                         "max_retry_count":0,
-                         "name":"kafka logger",
-                         "retry_delay":1,
-                         "timeout":3
-                     }
-                 },
-                 "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
-                     "name":"fault-injection",
-                     "conf":{
-                         "abort":{
-                             "body":"200",
-                             "http_status":300
-                         },
-                         "delay":{
-                             "duration":500
-                         }
-                     }
-                 }
-             },
-             "chart":{
-                 "hovered":{
-       
-                 },
-                 "links":{
-                     "3a110c30-d6f3-40b1-a8ac-b828cfaa2489":{
-                         "from":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port3"
-                         },
-                         "id":"3a110c30-d6f3-40b1-a8ac-b828cfaa2489",
-                         "to":{
-                             "nodeId":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                             "portId":"port1"
-                         }
-                     },
-                     "c1958993-c1ef-44b1-bb32-7fc6f34870c2":{
-                         "from":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port2"
-                         },
-                         "id":"c1958993-c1ef-44b1-bb32-7fc6f34870c2",
-                         "to":{
-                             "nodeId":"988ef5c2-c896-4606-a666-3d4cbe24a731",
-                             "portId":"port1"
-                         }
-                     },
-                     "f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23":{
-                         "from":{
-                             "nodeId":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                             "portId":"port2"
-                         },
-                         "id":"f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23",
-                         "to":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port1"
-                         }
-                     }
-                 },
-                 "nodes":{
-                     "3365eca3-4bc8-4769-bab3-1485dfd6a43c":{
-                         "id":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":107,
-                                     "y":0
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":92,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "value":"no"
-                                 },
-                                 "type":"output"
-                             },
-                             "port3":{
-                                 "id":"port3",
-                                 "position":{
-                                     "x":122,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "value":"yes"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":750.2627969928922,
-                             "y":301.0370335799397
-                         },
-                         "properties":{
-                             "customData":{
-                                 "name":"code == 403",
-                                 "type":1
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":214
-                         },
-                         "type":"判断条件"
-                     },
-                     "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
-                         "id":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":100,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":100,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":741.5684544145346,
-                             "y":126.75879247285502
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "block_rules":[
-                                         "root.exe",
-                                         "root.m+"
-                                     ],
-                                     "rejected_code":403
-                                 },
-                                 "name":"uri-blocker",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":201
-                         },
-                         "type":"uri-blocker"
-                     },
-                     "988ef5c2-c896-4606-a666-3d4cbe24a731":{
-                         "id":"988ef5c2-c896-4606-a666-3d4cbe24a731",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":106,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":106,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":607.9687500000001,
-                             "y":471.17788461538447
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "batch_max_size":1000,
-                                     "broker_list":{
-       
-                                     },
-                                     "buffer_duration":60,
-                                     "inactive_timeout":5,
-                                     "include_req_body":false,
-                                     "kafka_topic":"1",
-                                     "key":"2",
-                                     "max_retry_count":0,
-                                     "name":"kafka logger",
-                                     "retry_delay":1,
-                                     "timeout":3
-                                 },
-                                 "name":"kafka-logger",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":212
-                         },
-                         "type":"kafka-logger"
-                     },
-                     "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
-                         "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":110,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":110,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":988.9074986362261,
-                             "y":478.62041800736495
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "abort":{
-                                         "body":"200",
-                                         "http_status":300
-                                     },
-                                     "delay":{
-                                         "duration":500
-                                     }
-                                 },
-                                 "name":"fault-injection",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":219
-                         },
-                         "type":"fault-injection"
-                     }
-                 },
-                 "offset":{
-                     "x":-376.83,
-                     "y":87.98
-                 },
-                 "scale":0.832,
-                 "selected":{
-                     "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                     "type":"node"
-                 }
-             }
-         }
-       }`
-       err = json.Unmarshal([]byte(reqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err := handler.Create(ctx)
-       assert.Nil(t, err)
-       // check the returned valued
-       objRet, ok := ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, "1", objRet.ID)
-
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       //get
-       input := &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored := ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ID, route.ID)
-
-       //update
-       route2 := &UpdateInput{}
-       route2.ID = "1"
-       reqBody = `{
-         "id": "1",
-         "name": "aaaa",
-         "uri": "/index.html",
-         "hosts": ["foo.com", "*.bar.com"],
-         "remote_addrs": ["127.0.0.0/8"],
-         "methods": ["PUT", "GET"],
-         "status": 1,
-         "labels": {
-             "l1": "v1",
-             "l2": "v2"
-      },
-         "upstream": {
-             "type": "roundrobin",
-             "nodes": [{
-                 "host": "www.a.com",
-                 "port": 80,
-                 "weight": 1
-             }]
-         },
-         "script":{
-             "rule":{
-                 "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                 "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[
-                     [
-                         "code == 403",
-                         "b93d622c-92ef-48b4-b6bb-57e1ce893ee3"
-                     ],
-                     [
-                         "",
-                         "988ef5c2-c896-4606-a666-3d4cbe24a731"
-                     ]
-                 ]
-             },
-             "conf":{
-                 "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
-                     "name":"uri-blocker",
-                     "conf":{
-                         "block_rules":[
-                             "root.exe",
-                             "root.m+"
-                         ],
-                         "rejected_code":403
-                     }
-                 },
-                 "988ef5c2-c896-4606-a666-3d4cbe24a731":{
-                     "name":"kafka-logger",
-                     "conf":{
-                         "batch_max_size":1000,
-                         "broker_list":{
-       
-                         },
-                         "buffer_duration":60,
-                         "inactive_timeout":5,
-                         "include_req_body":false,
-                         "kafka_topic":"1",
-                         "key":"2",
-                         "max_retry_count":0,
-                         "name":"kafka logger",
-                         "retry_delay":1,
-                         "timeout":3
-                     }
-                 },
-                 "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
-                     "name":"fault-injection",
-                     "conf":{
-                         "abort":{
-                             "body":"200",
-                             "http_status":300
-                         },
-                         "delay":{
-                             "duration":500
-                         }
-                     }
-                 }
-             },
-             "chart":{
-                 "hovered":{
-       
-                 },
-                 "links":{
-                     "3a110c30-d6f3-40b1-a8ac-b828cfaa2489":{
-                         "from":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port3"
-                         },
-                         "id":"3a110c30-d6f3-40b1-a8ac-b828cfaa2489",
-                         "to":{
-                             "nodeId":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                             "portId":"port1"
-                         }
-                     },
-                     "c1958993-c1ef-44b1-bb32-7fc6f34870c2":{
-                         "from":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port2"
-                         },
-                         "id":"c1958993-c1ef-44b1-bb32-7fc6f34870c2",
-                         "to":{
-                             "nodeId":"988ef5c2-c896-4606-a666-3d4cbe24a731",
-                             "portId":"port1"
-                         }
-                     },
-                     "f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23":{
-                         "from":{
-                             "nodeId":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                             "portId":"port2"
-                         },
-                         "id":"f9c42bf6-c8aa-4e86-8498-8dfbc5c53c23",
-                         "to":{
-                             "nodeId":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                             "portId":"port1"
-                         }
-                     }
-                 },
-                 "nodes":{
-                     "3365eca3-4bc8-4769-bab3-1485dfd6a43c":{
-                         "id":"3365eca3-4bc8-4769-bab3-1485dfd6a43c",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":107,
-                                     "y":0
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":92,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "value":"no"
-                                 },
-                                 "type":"output"
-                             },
-                             "port3":{
-                                 "id":"port3",
-                                 "position":{
-                                     "x":122,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "value":"yes"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":750.2627969928922,
-                             "y":301.0370335799397
-                         },
-                         "properties":{
-                             "customData":{
-                                 "name":"code == 403",
-                                 "type":1
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":214
-                         },
-                         "type":"判断条件"
-                     },
-                     "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
-                         "id":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":100,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":100,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":741.5684544145346,
-                             "y":126.75879247285502
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "block_rules":[
-                                         "root.exe",
-                                         "root.m+"
-                                     ],
-                                     "rejected_code":403
-                                 },
-                                 "name":"uri-blocker",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":201
-                         },
-                         "type":"uri-blocker"
-                     },
-                     "988ef5c2-c896-4606-a666-3d4cbe24a731":{
-                         "id":"988ef5c2-c896-4606-a666-3d4cbe24a731",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":106,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":106,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":607.9687500000001,
-                             "y":471.17788461538447
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "batch_max_size":1000,
-                                     "broker_list":{
-       
-                                     },
-                                     "buffer_duration":60,
-                                     "inactive_timeout":5,
-                                     "include_req_body":false,
-                                     "kafka_topic":"1",
-                                     "key":"2",
-                                     "max_retry_count":0,
-                                     "name":"kafka logger",
-                                     "retry_delay":1,
-                                     "timeout":3
-                                 },
-                                 "name":"kafka-logger",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":212
-                         },
-                         "type":"kafka-logger"
-                     },
-                     "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
-                         "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                         "orientation":0,
-                         "ports":{
-                             "port1":{
-                                 "id":"port1",
-                                 "position":{
-                                     "x":110,
-                                     "y":0
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"input"
-                             },
-                             "port2":{
-                                 "id":"port2",
-                                 "position":{
-                                     "x":110,
-                                     "y":96
-                                 },
-                                 "properties":{
-                                     "custom":"property"
-                                 },
-                                 "type":"output"
-                             }
-                         },
-                         "position":{
-                             "x":988.9074986362261,
-                             "y":478.62041800736495
-                         },
-                         "properties":{
-                             "customData":{
-                                 "data":{
-                                     "abort":{
-                                         "body":"200",
-                                         "http_status":300
-                                     },
-                                     "delay":{
-                                         "duration":500
-                                     }
-                                 },
-                                 "name":"fault-injection",
-                                 "type":0
-                             }
-                         },
-                         "size":{
-                             "height":96,
-                             "width":219
-                         },
-                         "type":"fault-injection"
-                     }
-                 },
-                 "offset":{
-                     "x":-376.83,
-                     "y":87.98
-                 },
-                 "scale":0.832,
-                 "selected":{
-                     "id":"b93d622c-92ef-48b4-b6bb-57e1ce893ee3",
-                     "type":"node"
-                 }
-             }
-         }
-       }`
-
-       err = json.Unmarshal([]byte(reqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       ret, err = handler.Update(ctx)
-       assert.Nil(t, err)
-       // check the returned value
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, route2.ID, objRet.ID)
-       assert.Equal(t, route2.Labels, objRet.Labels)
-
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       // check ID discrepancy on Update
-
-       // Fail: test the string body id value != string route id value
-       errRoute := &UpdateInput{}
-       errRoute.ID = "2"
-       err = json.Unmarshal([]byte(reqBody), errRoute)
-       assert.Nil(t, err)
-       ctx.SetInput(errRoute)
-       ret, err = handler.Update(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "ID on path (2) doesn't match ID on body (1)")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // Fail: tests the float body id value != string route id value
-       reqBodyErr := `{
-               "id": 1,
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
-               }
-       }`
-       errRoute = &UpdateInput{}
-       errRoute.ID = "2"
-       err = json.Unmarshal([]byte(reqBodyErr), errRoute)
-       assert.Nil(t, err)
-       ctx.SetInput(errRoute)
-       ret, err = handler.Update(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "ID on path (2) doesn't match ID on body (1)")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // Success: tests the float body id value is == string route id value
-       reqBodyErr = `{
-               "id": 10,
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
-               }
-       }`
-       errRoute = &UpdateInput{}
-       errRoute.ID = "10"
-       err = json.Unmarshal([]byte(reqBodyErr), errRoute)
-       assert.Nil(t, err)
-       ctx.SetInput(errRoute)
-       ret, err = handler.Update(ctx)
-       assert.Nil(t, err)
-       // Check the returned value
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, errRoute.ID, objRet.ID)
-
-       // Success: tests the Body ID can be nil
-       reqBodyErr = `{
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
-               }
-       }`
-       errRoute = &UpdateInput{}
-       errRoute.ID = "r1"
-       err = json.Unmarshal([]byte(reqBodyErr), errRoute)
-       assert.Nil(t, err)
-       ctx.SetInput(errRoute)
-       ret, err = handler.Update(ctx)
-       assert.Nil(t, err)
-       // Check the returned value
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, errRoute.ID, objRet.ID)
-
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       //list
-       listInput := &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err := handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage := retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "uri": "index"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search name not match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "not-exists", "uri": 
"index"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 0)
-
-       //list search uri not match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "uri": 
"not-exists"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 0)
-
-       //list search label not match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "label":"l3"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 0)
-
-       //list search label match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "label":"l1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search label match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "label":"l1:v1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search and label not match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "label":"l1:v2"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 0)
-
-       // list search and status match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "status": "1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       // sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       // list search and status not match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "0"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 0)
-
-       //list search with name and status
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search with name and label
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "label":"l1:v1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search with uri and label
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "uri": "index", "label":"l1:v1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //list search with uri,name and label
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "uri": "index", 
"label":"l1:v1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
 
-       //list search with uri,name, status and label
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "1", 
"uri": "index", "label":"l1:v1"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
-
-       //create route using uris
-       route3 := &entity.Route{}
-       reqBody = `{
-         "id": "2",
-         "name": "bbbbb",
-         "uris": ["/aa", "/bb"],
-         "hosts": ["foo.com", "*.bar.com"],
-         "remote_addrs": ["127.0.0.0/8"],
-         "methods": ["PUT", "GET"],
-         "upstream": {
-             "type": "roundrobin",
-             "nodes": {"www.a.com:80": 1}
-         }
-       }`
-       err = json.Unmarshal([]byte(reqBody), route3)
-       assert.Nil(t, err)
-       ctx.SetInput(route3)
-       ret, err = handler.Create(ctx)
-       assert.Nil(t, err)
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, "2", objRet.ID)
+       for _, tc := range tests {
+               getCalled := false
+               mStore := &store.MockInterface{}
+
+               mStore.On("Get", mock.Anything, mock.Anything).Run(func(args 
mock.Arguments) {
+                       getCalled = true
+                       assert.Equal(t, tc.mockInput, args.Get(0))
+               }).Return(tc.mockRet, tc.mockErr)
+
+               sStore := &store.MockInterface{}
+               sStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.scriptRet, tc.scriptErr)
+
+               h := Handler{routeStore: mStore, scriptStore: sStore}
+               ctx := droplet.NewContext()
+               ctx.SetInput(tc.giveInput)
+               ret, err := h.Get(ctx)
+               assert.True(t, tc.called, getCalled)
+               assert.Equal(t, tc.wantRet, ret)
+               assert.Equal(t, tc.wantErr, err)
+       }
+}
 
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
+func TestRoute_List(t *testing.T) {
+       mockData := []*entity.Route{
+               {
+                       BaseInfo: entity.BaseInfo{CreateTime: 1609742634},
+                       Name:     "r1",
+                       URI:      "/test_r1",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "build":   "16",
+                       },
+                       Upstream: &entity.UpstreamDef{
+                               Nodes: []interface{}{
+                                       map[string]interface{}{
+                                               "host":   "39.97.63.215",
+                                               "port":   float64(80),
+                                               "weight": float64(1),
+                                       },
+                               },
+                       },
+               },
+               {
+                       BaseInfo: entity.BaseInfo{CreateTime: 1609742635},
+                       Name:     "r2",
+                       URI:      "/test_r2",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "build":   "16",
+                       },
+               },
+               {
+                       BaseInfo: entity.BaseInfo{CreateTime: 1609742636},
+                       Name:     "route_test",
+                       URI:      "/test_route_test",
+                       Labels: map[string]string{
+                               "version": "v2",
+                               "build":   "17",
+                       },
+               },
+               {
+                       BaseInfo: entity.BaseInfo{CreateTime: 1609742636},
+                       Name:     "test_route",
+                       URI:      "/test_test_route",
+                       Labels: map[string]string{
+                               "version": "v2",
+                               "build":   "17",
+                               "extra":   "test",
+                       },
+               },
+       }
 
-       //list search match uris
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1, "name": "bbb", "uri": "bb"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
+       tests := []testCase{
+               {
+                       caseDesc: "list all route",
+                       giveInput: &ListInput{
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       mockData[0],
+                                       mockData[1],
+                                       mockData[2],
+                                       mockData[3],
+                               },
+                               TotalSize: 4,
+                       },
+                       scriptRet: &entity.Script{ID: "r1", Script: "script"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "list routes with name",
+                       giveInput: &ListInput{
+                               Name: "route",
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       mockData[2],
+                                       mockData[3],
+                               },
+                               TotalSize: 2,
+                       },
+                       scriptRet: &entity.Script{ID: "r1", Script: "script"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "list routes with uri",
+                       giveInput: &ListInput{
+                               URI: "test_r2",
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       mockData[1],
+                               },
+                               TotalSize: 1,
+                       },
+                       scriptRet: &entity.Script{ID: "r1", Script: "script"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "list routes with label",
+                       giveInput: &ListInput{
+                               Label: "version:v1",
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       mockData[0],
+                                       mockData[1],
+                               },
+                               TotalSize: 2,
+                       },
+                       scriptRet: &entity.Script{ID: "s1", Script: "script"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "list routes with label",
+                       giveInput: &ListInput{
+                               Label: "extra",
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       mockData[3],
+                               },
+                               TotalSize: 1,
+                       },
+                       scriptRet: &entity.Script{ID: "s1", Script: "script"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "list routes and test format",
+                       giveInput: &ListInput{
+                               Name: "r1",
+                               Pagination: store.Pagination{
+                                       PageSize:   10,
+                                       PageNumber: 10,
+                               },
+                       },
+                       mockInput: store.ListInput{
+                               PageSize:   10,
+                               PageNumber: 10,
+                       },
+                       wantRet: &store.ListOutput{
+                               Rows: []interface{}{
+                                       &entity.Route{
+                                               BaseInfo: 
entity.BaseInfo{CreateTime: 1609742634},
+                                               Name:     "r1",
+                                               URI:      "/test_r1",
+                                               Labels: map[string]string{
+                                                       "version": "v1",
+                                                       "build":   "16",
+                                               },
+                                               Script: "script",
+                                               Upstream: &entity.UpstreamDef{
+                                                       Nodes: []*entity.Node{
+                                                               {
+                                                                       Host:   
"39.97.63.215",
+                                                                       Port:   
80,
+                                                                       Weight: 
1,
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
 
-       //delete test data
-       inputDel := &BatchDelete{}
-       reqBody = `{"ids": "1,2"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
-       assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       _, err = handler.BatchDelete(ctx)
-       assert.Nil(t, err)
+                               TotalSize: 1,
+                       },
+                       called: true,
+               },
+       }
 
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
+                       mStore := &store.MockInterface{}
+                       mStore.On("List", mock.Anything).Run(func(args 
mock.Arguments) {
+                               getCalled = true
+                               input := args.Get(0).(store.ListInput)
+                               mockInput := tc.mockInput.(store.ListInput)
+                               assert.Equal(t, mockInput.PageSize, 
input.PageSize)
+                               assert.Equal(t, mockInput.PageNumber, 
input.PageNumber)
+                       }).Return(func(input store.ListInput) *store.ListOutput 
{
+                               var returnData []interface{}
+                               for _, c := range mockData {
+                                       if input.Predicate(c) {
+                                               if input.Format == nil {
+                                                       returnData = 
append(returnData, c)
+                                                       continue
+                                               }
+
+                                               returnData = append(returnData, 
input.Format(c))
+                                       }
+                               }
+
+                               return &store.ListOutput{
+                                       Rows:      returnData,
+                                       TotalSize: len(returnData),
+                               }
+                       }, tc.mockErr)
+
+                       sStore := &store.MockInterface{}
+                       sStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.scriptRet, tc.scriptErr)
+
+                       h := Handler{routeStore: mStore, scriptStore: sStore}
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+
+                       ret, err := h.List(ctx)
+                       assert.Equal(t, tc.called, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       assert.Equal(t, tc.wantErr, err)
+               })
+       }
+}
 
-       //get route -- deleted, not found
-       getInput := &GetInput{}
-       reqBody = `{"id": "1"}`
-       err = json.Unmarshal([]byte(reqBody), getInput)
-       assert.Nil(t, err)
-       ctx.SetInput(getInput)
-       ret, err = handler.Get(ctx)
-       assert.EqualError(t, err, "data not found")
-       assert.Equal(t, http.StatusNotFound, 
ret.(*data.SpecCodeResponse).StatusCode)
+func TestRoute_Create(t *testing.T) {
+       scriptMap := make(map[string]interface{})
 
-       //delete test data
-       reqBody = `{"ids": "not-exists"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
+       err := json.Unmarshal([]byte(DagScript), &scriptMap)
        assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       ret, err = handler.BatchDelete(ctx)
-       assert.NotNil(t, err)
-       assert.Equal(t, http.StatusNotFound, 
ret.(*data.SpecCodeResponse).StatusCode)
 
-       //create route with not exist upstream id
-       route4 := &entity.Route{}
-       reqBody = `{
-         "id": "2222",
-         "name": "r222",
-         "uris": ["/aa", "/bb"],
-         "upstream_id": "not-exists"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route4)
+       luaCode, err := generateLuaCode(scriptMap)
        assert.Nil(t, err)
-       ctx.SetInput(route4)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
 
-       //type:chash, hash_on: vars, wrong key
-       route5 := &entity.Route{}
-       reqBody = `{
-         "id": "1",
-         "methods": ["GET"],
-         "upstream": {
-             "nodes": {
-                 "127.0.0.1:8080": 1
-             },
-             "type": "chash",
-             "hash_on":"vars",
-             "key": "not_support"
-         },
-         "desc": "new route",
-         "uri": "/index.html"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route5)
-       assert.Nil(t, err)
-       ctx.SetInput(route5)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
+       tests := []testCase{
+               {
+                       caseDesc: "create route success",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               ScriptID:   "s1",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               ScriptID:   "s1",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       serviceRet:  "service",
+                       serviceErr:  nil,
+                       upstreamRet: "upstream",
+                       upstreamErr: nil,
+                       scriptRet:   "script",
+                       scriptErr:   nil,
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               ScriptID:   "s1",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantErr: nil,
+                       called:  true,
+               },
+               {
+                       caseDesc: "create route failed, service not found",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s2",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "not_found",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantRet:    &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:    fmt.Errorf("service id: not_found not 
found"),
+                       serviceRet: nil,
+                       serviceErr: data.ErrNotFound,
+               },
+               {
+                       caseDesc: "create route failed, service store get 
error",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "r1",
+                               Desc:       "test route",
+                               UpstreamID: "r1",
+                               // mock store will return err if service is s3
+                               ServiceID: "error",
+                               Script:    "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantRet:    &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:    errors.New("service error"),
+                       serviceRet: nil,
+                       serviceErr: errors.New("service error"),
+                       called:     false,
+               },
+               {
+                       caseDesc: "create route failed, upstream not found",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s2",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "not_found",
+                               ServiceID:  "s2",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantRet:     &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:     fmt.Errorf("upstream id: not_found not 
found"),
+                       upstreamErr: data.ErrNotFound,
+                       called:      false,
+               },
+               {
+                       caseDesc: "create route failed, upstream store get 
error",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s2",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "error",
+                               ServiceID:  "s2",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantRet:     &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:     errors.New("upstream error"),
+                       upstreamErr: errors.New("upstream error"),
+                       called:      false,
+               },
+               {
+                       caseDesc: "create route failed, script create error",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s2",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s2",
+                               Script:     "",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantRet:    &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:    fmt.Errorf("upstream error"),
+                       serviceErr: fmt.Errorf("upstream error"),
+                       called:     false,
+               },
+               {
+                       caseDesc: "create route success with script",
+                       giveInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               Script:     scriptMap,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               Script:     luaCode,
+                               ScriptID:   "s1",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               Script:     luaCode,
+                               ScriptID:   "s1",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       serviceRet:  "service",
+                       serviceErr:  nil,
+                       upstreamRet: "upstream",
+                       upstreamErr: nil,
+                       scriptRet:   "script",
+                       scriptErr:   nil,
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "s1",
+                                       CreateTime: 1609746531,
+                               },
+                               Name:       "s1",
+                               Desc:       "test_route",
+                               UpstreamID: "u1",
+                               ServiceID:  "s1",
+                               Script:     luaCode,
+                               ScriptID:   "s1",
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                       },
+                       wantErr: nil,
+                       called:  true,
+               },
+       }
 
-       //type:chash, hash_on: cookie, missing key
-       route6 := &entity.Route{}
-       reqBody = `{
-         "id": "1",
-         "methods": ["GET"],
-         "upstream": {
-             "nodes": {
-                 "127.0.0.1:8080": 1
-             },
-             "type": "chash",
-             "hash_on":"cookie"
-         },
-         "desc": "new route",
-         "uri": "/index.html"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route6)
-       assert.Nil(t, err)
-       ctx.SetInput(route6)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
 
-       //create route with out upstream
-       route11 := &entity.Route{}
-       reqBody = `{
-         "id": "11",
-         "name": "bbbbb",
-         "uri": "/r11",
-         "hosts": ["foo.com", "*.bar.com"],
-         "remote_addrs": ["127.0.0.0/8"],
-         "methods": ["PUT", "GET"],
-         "plugins": {
-             "limit-count": {
-                 "count": 2,
-                 "time_window": 60,
-                 "rejected_code": 503,
-                 "key": "remote_addr"
-             }
-         }
-       }`
-       err = json.Unmarshal([]byte(reqBody), route11)
-       assert.Nil(t, err)
-       ctx.SetInput(route11)
-       ret, err = handler.Create(ctx)
-       assert.Nil(t, err)
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, "11", objRet.ID)
+                       mStore := &store.MockInterface{}
+                       mStore.On("Create", mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
+                               getCalled = true
+                               route := args.Get(1).(*entity.Route)
+                               assert.Equal(t, tc.mockInput, route)
+                       }).Return(tc.mockRet, tc.mockErr)
 
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
+                       svcStore := &store.MockInterface{}
+                       svcStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.serviceRet, tc.serviceErr)
 
-       //get
-       input11 := &GetInput{}
-       input11.ID = "11"
-       ctx.SetInput(input11)
-       ret, err = handler.Get(ctx)
-       assert.Nil(t, err)
-       stored = ret.(*entity.Route)
-       assert.Equal(t, "11", stored.ID)
+                       uStore := &store.MockInterface{}
+                       uStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.upstreamRet, tc.upstreamErr)
 
-       //list
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 10, "page": 1}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       _, err = handler.List(ctx)
-       assert.Nil(t, err)
+                       scriptStore := &store.MockInterface{}
+                       scriptStore.On("Create", mock.Anything, 
mock.Anything).Return(tc.scriptRet, tc.serviceErr)
 
-       //list search match
-       listInput = &ListInput{}
-       reqBody = `{"page_size": 1, "page": 1,  "uri": "r11"}`
-       err = json.Unmarshal([]byte(reqBody), listInput)
-       assert.Nil(t, err)
-       ctx.SetInput(listInput)
-       retPage, err = handler.List(ctx)
-       assert.Nil(t, err)
-       dataPage = retPage.(*store.ListOutput)
-       assert.Equal(t, len(dataPage.Rows), 1)
+                       h := Handler{routeStore: mStore, svcStore: svcStore, 
upstreamStore: uStore, scriptStore: scriptStore}
 
-       //delete test data
-       reqBody = `{"ids": "11"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
-       assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       _, err = handler.BatchDelete(ctx)
-       assert.Nil(t, err)
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+                       ret, err := h.Create(ctx)
+                       assert.Equal(t, tc.called, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       assert.Equal(t, tc.wantErr, err)
+               })
+       }
 }
 
-func Test_Route_With_Script_Dag2lua(t *testing.T) {
-       // init
-       err := storage.InitETCDClient(conf.ETCDConfig)
-       assert.Nil(t, err)
-       err = store.InitStores()
-       assert.Nil(t, err)
-
-       handler := &Handler{
-               routeStore:    store.GetStore(store.HubKeyRoute),
-               svcStore:      store.GetStore(store.HubKeyService),
-               upstreamStore: store.GetStore(store.HubKeyUpstream),
-               scriptStore:   store.GetStore(store.HubKeyScript),
+func TestRoute_Update(t *testing.T) {
+       luaScript := "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M"
+       scriptMap := make(map[string]interface{})
+       err := json.Unmarshal([]byte(DagScript), &scriptMap)
+       assert.Nil(t, err)
+
+       luaCode, err := generateLuaCode(scriptMap)
+       assert.Nil(t, err)
+       tests := []testCase{
+               {
+                       caseDesc: "update success with script",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       Name:       "r1",
+                                       Desc:       "updated route",
+                                       UpstreamID: "u2",
+                                       Script:     luaScript,
+                                       ServiceID:  "s1",
+                                       Labels: map[string]string{
+                                               "version": "v2",
+                                       },
+                               },
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaScript,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaScript,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaScript,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       mockErr:      nil,
+                       serviceInput: "s2",
+                       called:       true,
+               },
+               {
+                       caseDesc: "update success with script map",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       Name:       "r1",
+                                       Desc:       "updated route",
+                                       UpstreamID: "u2",
+                                       Script:     scriptMap,
+                                       ServiceID:  "s1",
+                                       Labels: map[string]string{
+                                               "version": "v2",
+                                       },
+                               },
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaCode,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaCode,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       mockErr: nil,
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name:       "r1",
+                               Desc:       "updated route",
+                               UpstreamID: "u2",
+                               Script:     luaCode,
+                               ScriptID:   "r1",
+                               ServiceID:  "s1",
+                               Labels: map[string]string{
+                                       "version": "v2",
+                               },
+                       },
+                       serviceInput: "s2",
+                       called:       true,
+               },
+               {
+                       caseDesc: "update failed, different id",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       BaseInfo: entity.BaseInfo{
+                                               ID: "r2",
+                                       },
+                                       Name:       "test_route",
+                                       UpstreamID: "u1",
+                                       Desc:       "test service",
+                               },
+                       },
+                       wantRet: &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr: fmt.Errorf("ID on path (r1) doesn't match ID 
on body (r2)"),
+                       called:  false,
+               },
+               {
+                       caseDesc: "update failed, service not found",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       BaseInfo: entity.BaseInfo{
+                                               ID: "r1",
+                                       },
+                                       Name:       "test route",
+                                       ServiceID:  "not_found",
+                                       UpstreamID: "u1",
+                               },
+                       },
+                       wantRet:    &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:    fmt.Errorf("service id: not_found not 
found"),
+                       serviceErr: data.ErrNotFound,
+                       called:     false,
+               },
+               {
+                       caseDesc: "update failed, upstream not found",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       Name:       "test_route",
+                                       UpstreamID: "not_found",
+                                       ServiceID:  "s1",
+                                       Desc:       "test route",
+                               },
+                       },
+                       wantRet:     &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr:     fmt.Errorf("upstream id: not_found not 
found"),
+                       serviceRet:  "service",
+                       upstreamErr: data.ErrNotFound,
+                       called:      false,
+               },
+               {
+                       caseDesc: "update failed, route return error",
+                       giveInput: &UpdateInput{
+                               ID: "r1",
+                               Route: entity.Route{
+                                       Name: "r1",
+                                       Desc: "test route",
+                               },
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID: "r1",
+                               },
+                               Name: "r1",
+                               Desc: "test route",
+                       },
+                       mockErr:      fmt.Errorf("route update error"),
+                       wantErr:      fmt.Errorf("route update error"),
+                       wantRet:      &data.SpecCodeResponse{StatusCode: 
http.StatusInternalServerError},
+                       serviceRet:   "service",
+                       upstreamRet:  "upstream",
+                       serviceInput: "s1",
+                       called:       true,
+               },
        }
-       assert.NotNil(t, handler)
-
-       //create Note: depends on lib `dag-to-lua` if script exists
-       ctx := droplet.NewContext()
-       route := &entity.Route{}
-       reqBody := `{
-                 "id": "1",
-                 "uri": "/index.html",
-                 "upstream": {
-                     "type": "roundrobin",
-                     "nodes": [{
-                         "host": "www.a.com",
-                         "port": 80,
-                         "weight": 1
-                     }]
-                 },
-                 "script":{
-                     "rule":{
-                         "root":"451106f8-560c-43a4-acf2-2a6ed0ea57b8",
-                         "451106f8-560c-43a4-acf2-2a6ed0ea57b8":[
-                             [
-                                 "code == 403",
-                                 "b93d622c-92ef-48b4-b6bb-57e1ce893ee3"
-                             ],
-                             [
-                                 "",
-                                 "988ef5c2-c896-4606-a666-3d4cbe24a731"
-                             ]
-                         ]
-                     },
-                     "conf":{
-                         "451106f8-560c-43a4-acf2-2a6ed0ea57b8":{
-                             "name":"uri-blocker",
-                             "conf":{
-                                 "block_rules":[
-                                     "root.exe",
-                                     "root.m+"
-                                 ],
-                                 "rejected_code":403
-                             }
-                         },
-                         "988ef5c2-c896-4606-a666-3d4cbe24a731":{
-                             "name":"kafka-logger",
-                             "conf":{
-                                 "batch_max_size":1000,
-                                 "broker_list":{
-                                 },
-                                 "buffer_duration":60,
-                                 "inactive_timeout":5,
-                                 "include_req_body":false,
-                                 "kafka_topic":"1",
-                                 "key":"2",
-                                 "max_retry_count":0,
-                                 "name":"kafka logger",
-                                 "retry_delay":1,
-                                 "timeout":3
-                             }
-                         },
-                         "b93d622c-92ef-48b4-b6bb-57e1ce893ee3":{
-                             "name":"fault-injection",
-                             "conf":{
-                                 "abort":{
-                                     "body":"200",
-                                     "http_status":300
-                                 },
-                                 "delay":{
-                                     "duration":500
-                                 }
-                             }
-                         }
-                     },
-                     "chart":{
-                     }
-                 }
-               }`
-       err = json.Unmarshal([]byte(reqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err := handler.Create(ctx)
-       assert.Nil(t, err)
-       objRet, ok := ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, "1", objRet.ID)
-
-       //sleep
-       time.Sleep(time.Duration(20) * time.Millisecond)
-
-       //get
-       input := &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored := ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ID, route.ID)
-       assert.NotNil(t, stored.Script)
-
-       //update
-       route2 := &UpdateInput{}
-       route2.ID = "1"
-       reqBody = `{
-               "id": "1",
-               "uri": "/index.html",
-               "enable_websocket": true,
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
-               }
-       }`
-
-       err = json.Unmarshal([]byte(reqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       ret, err = handler.Update(ctx)
-       assert.Nil(t, err)
-       // check the returned value
-       objRet, ok = ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, route2.ID, objRet.ID)
-       // script returned should be nil
-       assert.Nil(t, objRet.Script)
-
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       //get, script should be nil
-       input = &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored = ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ID, route.ID)
-       assert.Nil(t, stored.Script)
 
-       //delete test data
-       inputDel := &BatchDelete{}
-       reqBody = `{"ids": "1"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
-       assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       _, err = handler.BatchDelete(ctx)
-       assert.Nil(t, err)
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
+                       routeStore := &store.MockInterface{}
+
+                       routeStore.On("Update", mock.Anything, mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
+                               getCalled = true
+                               input := args.Get(1).(*entity.Route)
+                               createIfNotExist := args.Get(2).(bool)
+                               assert.Equal(t, tc.mockInput, input)
+                               assert.True(t, createIfNotExist)
+                       }).Return(tc.mockRet, tc.mockErr)
+
+                       serviceStore := &store.MockInterface{}
+                       serviceStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.serviceRet, tc.serviceErr)
+
+                       upstreamStore := &store.MockInterface{}
+                       upstreamStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.upstreamRet, tc.upstreamErr)
+
+                       scriptStore := &store.MockInterface{}
+                       scriptStore.On("Get", mock.Anything, 
mock.Anything).Return(tc.scriptRet, tc.scriptErr)
+                       scriptStore.On("Update", mock.Anything, mock.Anything, 
mock.Anything).Return(luaScript, nil)
+
+                       h := Handler{svcStore: serviceStore, upstreamStore: 
upstreamStore, scriptStore: scriptStore,
+                               routeStore: routeStore}
+
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+                       ret, err := h.Update(ctx)
+                       assert.Equal(t, tc.called, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       assert.Equal(t, tc.wantErr, err)
+               })
+       }
 }
 
-func Test_Route_With_Script_Luacode(t *testing.T) {
-       // init
-       err := storage.InitETCDClient(conf.ETCDConfig)
-       assert.Nil(t, err)
-       err = store.InitStores()
-       assert.Nil(t, err)
-
-       handler := &Handler{
-               routeStore:    store.GetStore(store.HubKeyRoute),
-               svcStore:      store.GetStore(store.HubKeyService),
-               upstreamStore: store.GetStore(store.HubKeyUpstream),
-               scriptStore:   store.GetStore(store.HubKeyScript),
+func TestRoute_Patch(t *testing.T) {
+       existRoute := &entity.Route{
+               BaseInfo: entity.BaseInfo{
+                       ID:         "r1",
+                       CreateTime: 1609340491,
+                       UpdateTime: 1609340491,
+               },
+               Name:       "exist_service",
+               UpstreamID: "u1",
+               Upstream: &entity.UpstreamDef{
+                       Key: "key",
+               },
+               EnableWebsocket: false,
+               Labels: map[string]string{
+                       "version": "v1",
+               },
+               Plugins: map[string]interface{}{
+                       "limit-count": map[string]interface{}{
+                               "count":         2,
+                               "time_window":   60,
+                               "rejected_code": 503,
+                               "key":           "remote_addr",
+                       },
+               },
+               Status: 1,
        }
-       assert.NotNil(t, handler)
 
-       // create with script of valid lua syntax
-       ctx := droplet.NewContext()
-       route := &entity.Route{}
-       reqBody := `{
-               "id": "1",
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+       tests := []testCase{
+               {
+                       caseDesc: "patch success",
+                       giveInput: &PatchInput{
+                               ID:      "r1",
+                               SubPath: "",
+                               Body:    []byte("{\"status\":0}"),
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       called: true,
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       _, err = handler.Create(ctx)
-       assert.Nil(t, err)
-
-       // sleep
-       time.Sleep(time.Duration(20) * time.Millisecond)
-
-       // get
-       input := &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err := handler.Get(ctx)
-       stored := ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ID, route.ID)
-       assert.Equal(t, "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M", stored.Script)
-       // If not specify script_id in request, script_id should be equal to id
-       assert.Equal(t, stored.ScriptID, route.ID)
-
-       // update via empty script
-       route2 := &UpdateInput{}
-       route2.ID = "1"
-       reqBody = `{
-               "id": "1",
-               "uri": "/index.html",
-               "enable_websocket": true,
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
-               }
-       }`
-
-       err = json.Unmarshal([]byte(reqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       ret, err = handler.Update(ctx)
-       assert.Nil(t, err)
-       // check the returned value
-       objRet, ok := ret.(*entity.Route)
-       assert.True(t, ok)
-       assert.Equal(t, route2.ID, objRet.ID)
-       // script and script_id returned should be nil
-       assert.Nil(t, objRet.Script)
-       assert.Nil(t, objRet.ScriptID)
-
-       //sleep
-       time.Sleep(time.Duration(100) * time.Millisecond)
-
-       //get, script should be nil
-       input = &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored = ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ID, route.ID)
-       assert.Nil(t, stored.Script)
-
-       // 2nd update via invalid script
-       input3 := &UpdateInput{}
-       input3.ID = "1"
-       reqBody = `{
-               "id": "1",
-               "uri": "/index.html",
-               "enable_websocket": true,
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+               {
+                       caseDesc: "patch success by path",
+                       giveInput: &PatchInput{
+                               ID:      "r1",
+                               SubPath: "/status",
+                               Body:    []byte("0"),
+                       },
+                       mockInput: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       mockRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       wantRet: &entity.Route{
+                               BaseInfo: entity.BaseInfo{
+                                       ID:         "r1",
+                                       CreateTime: 1609340491,
+                                       UpdateTime: 1609340491,
+                               },
+                               Name:       "exist_service",
+                               UpstreamID: "u1",
+                               Upstream: &entity.UpstreamDef{
+                                       Key: "key",
+                               },
+                               EnableWebsocket: false,
+                               Labels: map[string]string{
+                                       "version": "v1",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "limit-count": map[string]interface{}{
+                                               "count":         float64(2),
+                                               "time_window":   float64(60),
+                                               "rejected_code": float64(503),
+                                               "key":           "remote_addr",
+                                       },
+                               },
+                               Status: 0,
+                       },
+                       called: true,
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\")"
-       }`
-
-       err = json.Unmarshal([]byte(reqBody), input3)
-       assert.Nil(t, err)
-       ctx.SetInput(input3)
-       _, err = handler.Update(ctx)
-       // err should NOT be nil
-       assert.NotNil(t, err)
-
-       // delete test data
-       inputDel := &BatchDelete{}
-       reqBody = `{"ids": "1"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
-       assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       _, err = handler.BatchDelete(ctx)
-       assert.Nil(t, err)
-
-       // 2nd create with script of invalid lua syntax
-       ctx = droplet.NewContext()
-       route = &entity.Route{}
-       reqBody = `{
-               "id": "1",
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+               {
+                       caseDesc: "patch failed, path error",
+                       giveInput: &PatchInput{
+                               ID:      "r1",
+                               SubPath: "error",
+                               Body:    []byte("0"),
+                       },
+                       wantRet: handler.SpecCodeResponse(
+                               errors.New("add operation does not apply: doc 
is missing path: \"error\": missing value")),
+                       wantErr: errors.New("add operation does not apply: doc 
is missing path: \"error\": missing value"),
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\")"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "<string> at EOF:   syntax error\n")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
+       }
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
+
+                       routeStore := &store.MockInterface{}
+                       routeStore.On("Get", mock.Anything, 
mock.Anything).Return(existRoute, nil)
+                       routeStore.On("Update", mock.Anything, mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
+                               getCalled = true
+                               input := args.Get(1).(*entity.Route)
+                               createIfNotExist := args.Get(2).(bool)
+                               assert.Equal(t, tc.mockInput, input)
+                               assert.False(t, createIfNotExist)
+                       }).Return(tc.mockRet, tc.mockErr)
+                       h := Handler{routeStore: routeStore}
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+                       ret, err := h.Patch(ctx)
+                       assert.Equal(t, tc.called, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       if tc.wantErr != nil && err != nil {
+                               assert.Error(t, tc.wantErr.(error), err.Error())
+                       } else {
+                               assert.Equal(t, tc.wantErr, err)
+                       }
+               })
+       }
 }
 
-func Test_Route_With_Script_ID(t *testing.T) {
-       // init
-       err := storage.InitETCDClient(conf.ETCDConfig)
-       assert.Nil(t, err)
-       err = store.InitStores()
-       assert.Nil(t, err)
+func TestRoute_Delete(t *testing.T) {
+       tests := []testCase{
+               {
+                       caseDesc: "delete success",
+                       giveInput: &BatchDelete{
+                               IDs: "r1",
+                       },
+                       mockInput: []string{"r1"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "batch delete success",
+                       giveInput: &BatchDelete{
+                               IDs: "r1,r2",
+                       },
+                       mockInput: []string{"r1", "r2"},
+                       called:    true,
+               },
+               {
+                       caseDesc: "delete failed, route delete error",
+                       giveInput: &BatchDelete{
+                               IDs: "r1",
+                       },
+                       mockInput: []string{"r1"},
+                       mockErr:   fmt.Errorf("delete error"),
+                       wantRet:   handler.SpecCodeResponse(fmt.Errorf("delete 
error")),
+                       wantErr:   fmt.Errorf("delete error"),
+                       called:    true,
+               },
+               {
+                       caseDesc: "delete failed, script delete error",
+                       giveInput: &BatchDelete{
+                               IDs: "r1",
+                       },
+                       mockInput: []string{"r1"},
+                       scriptErr: fmt.Errorf("delete error"),
+                       wantRet:   nil,
+                       wantErr:   nil,
+                       called:    true,
+               },
+       }
 
-       handler := &Handler{
-               routeStore:    store.GetStore(store.HubKeyRoute),
-               svcStore:      store.GetStore(store.HubKeyService),
-               upstreamStore: store.GetStore(store.HubKeyUpstream),
-               scriptStore:   store.GetStore(store.HubKeyScript),
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
+                       routeStore := &store.MockInterface{}
+                       routeStore.On("BatchDelete", mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
+                               getCalled = true
+                               input := args.Get(1).([]string)
+                               assert.Equal(t, tc.mockInput, input)
+                       }).Return(tc.mockErr)
+
+                       scriptStore := &store.MockInterface{}
+                       scriptStore.On("BatchDelete", mock.Anything, 
mock.Anything).Return(tc.serviceRet, tc.scriptErr)
+
+                       h := Handler{routeStore: routeStore, scriptStore: 
scriptStore}
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+                       ret, err := h.BatchDelete(ctx)
+                       assert.True(t, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       if tc.wantErr != nil && err != nil {
+                               assert.Error(t, tc.wantErr.(error), err.Error())
+                       } else {
+                               assert.Equal(t, tc.wantErr, err)
+                       }
+               })
        }
-       assert.NotNil(t, handler)
+}
 
-       // create with invalid script_id - not equal to id
-       ctx := droplet.NewContext()
-       route := &entity.Route{}
-       errReqBody := `{
-               "id": "1",
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+func TestRoute_Exist(t *testing.T) {
+       mockData := []*entity.Route{
+               {
+                       BaseInfo: entity.BaseInfo{ID: "001", CreateTime: 
1609742634},
+                       Name:     "r1",
+                       URI:      "/test_r1",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "build":   "16",
+                       },
+                       Upstream: &entity.UpstreamDef{
+                               Nodes: []interface{}{
+                                       map[string]interface{}{
+                                               "host":   "39.97.63.215",
+                                               "port":   float64(80),
+                                               "weight": float64(1),
+                                       },
+                               },
+                       },
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M",
-               "script_id": "not-1"
-       }`
-       err = json.Unmarshal([]byte(errReqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err := handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "script_id must be the same as id")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // create with invalid script_id - set script_id but without id
-       route = &entity.Route{}
-       errReqBody = `{
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+               {
+                       BaseInfo: entity.BaseInfo{ID: "002", CreateTime: 
1609742635},
+                       Name:     "r2",
+                       URI:      "/test_r2",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "build":   "16",
+                       },
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M",
-               "script_id": "1"
-       }`
-       err = json.Unmarshal([]byte(errReqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "script_id must be the same as id")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // create with invalid script_id - set script_id but without script
-       route = &entity.Route{}
-       errReqBody = `{
-               "id": "1",
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+               {
+                       BaseInfo: entity.BaseInfo{ID: "003", CreateTime: 
1609742636},
+                       Name:     "route_test",
+                       URI:      "/test_route_test",
+                       Labels: map[string]string{
+                               "version": "v2",
+                               "build":   "17",
+                       },
                },
-               "script_id": "1"
-       }`
-       err = json.Unmarshal([]byte(errReqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       ret, err = handler.Create(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "script_id cannot be set if script is unset")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
+       }
 
-       // create with valid script_id
-       route = &entity.Route{}
-       reqBody := `{
-               "id": "1",
-               "uri": "/index.html",
-               "upstream": {
-                       "type": "roundrobin",
-                       "nodes": [{
-                               "host": "www.a.com",
-                               "port": 80,
-                               "weight": 1
-                       }]
+       tests := []testCase{
+               {
+                       caseDesc: "check route exist, excluded",
+                       giveInput: &ExistCheckInput{
+                               Name:    "r1",
+                               Exclude: "001",
+                       },
+                       wantRet: nil,
+                       called:  true,
                },
-               "script": "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M",
-               "script_id": "1"
-       }`
-       err = json.Unmarshal([]byte(reqBody), route)
-       assert.Nil(t, err)
-       ctx.SetInput(route)
-       _, err = handler.Create(ctx)
-       assert.Nil(t, err)
-
-       // sleep
-       time.Sleep(time.Duration(20) * time.Millisecond)
-
-       // get
-       input := &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored := ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ScriptID, route.ID)
-
-       // update via invalid script_id - not equal to id
-       route2 := &UpdateInput{}
-       route2.ID = "1"
-       errReqBody = `{
-                       "id": "1",
-                       "uri": "/index.html",
-                       "enable_websocket": true,
-                       "upstream": {
-                               "type": "roundrobin",
-                               "nodes": [{
-                                       "host": "www.a.com",
-                                       "port": 80,
-                                       "weight": 1
-                               }]
-                       },
-                       "script": "local _M = {} \n function _M.access(api_ctx) 
\n ngx.log(ngx.WARN,\"hit access phase\") \n end \nreturn _M",
-                       "script_id": "not-1"
-               }`
-
-       err = json.Unmarshal([]byte(errReqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       ret, err = handler.Update(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "script_id must be the same as id")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // update via invalid script_id - set script_id but without script
-       route2 = &UpdateInput{}
-       route2.ID = "1"
-       errReqBody = `{
-                       "id": "1",
-                       "uri": "/index.html",
-                       "enable_websocket": true,
-                       "upstream": {
-                               "type": "roundrobin",
-                               "nodes": [{
-                                       "host": "www.a.com",
-                                       "port": 80,
-                                       "weight": 1
-                               }]
-                       },
-                       "script_id": "1"
-               }`
-       err = json.Unmarshal([]byte(errReqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       ret, err = handler.Update(ctx)
-       assert.NotNil(t, err)
-       assert.EqualError(t, err, "script_id cannot be set if script is unset")
-       assert.Equal(t, http.StatusBadRequest, 
ret.(*data.SpecCodeResponse).StatusCode)
-
-       // update via valid script_id
-       route2 = &UpdateInput{}
-       route2.ID = "1"
-       reqBody = `{
-                               "id": "1",
-                               "uri": "/index.html",
-                               "enable_websocket": true,
-                               "upstream": {
-                                       "type": "roundrobin",
-                                       "nodes": [{
-                                               "host": "www.a.com",
-                                               "port": 80,
-                                               "weight": 1
-                                       }]
-                               },
-                               "script": "local _M = {} \n function 
_M.access(api_ctx) \n ngx.log(ngx.WARN,\"hit access phase, again\") \n end 
\nreturn _M",
-                               "script_id": "1"
-                       }`
-       err = json.Unmarshal([]byte(reqBody), route2)
-       assert.Nil(t, err)
-       ctx.SetInput(route2)
-       _, err = handler.Update(ctx)
-       assert.Nil(t, err)
-
-       // sleep
-       time.Sleep(time.Duration(20) * time.Millisecond)
-
-       // get
-       input = &GetInput{}
-       input.ID = "1"
-       ctx.SetInput(input)
-       ret, err = handler.Get(ctx)
-       stored = ret.(*entity.Route)
-       assert.Nil(t, err)
-       assert.Equal(t, stored.ScriptID, route.ID)
-       assert.Equal(t, "local _M = {} \n function _M.access(api_ctx) \n 
ngx.log(ngx.WARN,\"hit access phase, again\") \n end \nreturn _M", 
stored.Script)
+               {
+                       caseDesc: "check route exist, not excluded",
+                       giveInput: &ExistCheckInput{
+                               Name:    "r1",
+                               Exclude: "002",
+                       },
+                       wantRet: &data.SpecCodeResponse{StatusCode: 
http.StatusBadRequest},
+                       wantErr: consts.InvalidParam("Route name is 
reduplicate"),
+                       called:  true,
+               },
+               {
+                       caseDesc: "check route exist, not existed",
+                       giveInput: &ExistCheckInput{
+                               Name:    "r3",
+                               Exclude: "001",
+                       },
+                       wantRet: nil,
+                       wantErr: nil,
+                       called:  true,
+               },
+       }
 
-       // delete test data
-       inputDel := &BatchDelete{}
-       reqBody = `{"ids": "1"}`
-       err = json.Unmarshal([]byte(reqBody), inputDel)
-       assert.Nil(t, err)
-       ctx.SetInput(inputDel)
-       _, err = handler.BatchDelete(ctx)
-       assert.Nil(t, err)
+       for _, tc := range tests {
+               t.Run(tc.caseDesc, func(t *testing.T) {
+                       getCalled := false
+                       routeStore := &store.MockInterface{}
+                       routeStore.On("List", mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
+                               getCalled = true
+                       }).Return(func(input store.ListInput) *store.ListOutput 
{
+                               var res []interface{}
+                               for _, c := range mockData {
+                                       if input.Predicate(c) {
+                                               if input.Format != nil {
+                                                       res = append(res, 
input.Format(c))
+                                               } else {
+                                                       res = append(res, c)
+                                               }
+                                       }
+                               }
+
+                               return &store.ListOutput{
+                                       Rows:      res,
+                                       TotalSize: len(res),
+                               }
+                       }, nil)
+
+                       h := Handler{routeStore: routeStore}
+                       ctx := droplet.NewContext()
+                       ctx.SetInput(tc.giveInput)
+                       ret, err := h.Exist(ctx)
+                       assert.True(t, getCalled)
+                       assert.Equal(t, tc.wantRet, ret)
+                       assert.Equal(t, tc.wantErr, err)
+               })
+       }
 }

Reply via email to