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

liujun pushed a commit to branch refactor-with-go
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git


The following commit(s) were added to refs/heads/refactor-with-go by this push:
     new 5bc791aa feat: refactor service mock with go (#1060)
5bc791aa is described below

commit 5bc791aab97dff442807f2e3460388e96a3f4182
Author: WangGuan <[email protected]>
AuthorDate: Tue Mar 28 19:06:31 2023 +0800

    feat: refactor service mock with go (#1060)
---
 .gitignore                                        |   3 +
 conf/dubboadmin.yml                               |   3 +-
 go.mod                                            |  10 ++
 go.sum                                            |  20 +++
 pkg/admin/config/config.go                        |  30 ++++
 pkg/admin/handlers/mock_rule.go                   |  96 ++++++++++++
 pkg/admin/mapper/mock_rule_mapper.go              |  59 ++++++++
 pkg/admin/mapper/mock_rule_mapper_mock.go         | 125 ++++++++++++++++
 pkg/admin/model/mock_rule.go                      |  64 ++++++++
 pkg/admin/router/router.go                        |   7 +
 pkg/admin/services/mock_rule_service.go           |  33 +++++
 pkg/admin/services/mock_rule_service_impl.go      |  85 +++++++++++
 pkg/admin/services/mock_rule_service_impl_test.go | 172 ++++++++++++++++++++++
 13 files changed, 706 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 0aae576a..9ba98974 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,9 @@ target/
 *.iml
 *.iws
 
+# vscode ignore
+.vscode/
+
 # temp ignore
 *.log*
 *.cache
diff --git a/conf/dubboadmin.yml b/conf/dubboadmin.yml
index b8d0fdd7..8df11f61 100644
--- a/conf/dubboadmin.yml
+++ b/conf/dubboadmin.yml
@@ -18,4 +18,5 @@ admin:
     address: zookeeper://127.0.0.1:2181
   config-center: zookeeper://127.0.0.1:2181
   metadata-report:
-    address: zookeeper://127.0.0.1:2181
\ No newline at end of file
+    address: zookeeper://127.0.0.1:2181
+  # mysql-dsn: 
root:password@tcp(127.0.0.1:3306)/dubbo-admin?charset=utf8&parseTime=true
\ No newline at end of file
diff --git a/go.mod b/go.mod
index a3d7d09f..02f12f3b 100644
--- a/go.mod
+++ b/go.mod
@@ -45,6 +45,13 @@ require (
        sigs.k8s.io/structured-merge-diff/v4 v4.2.3
 )
 
+require (
+       github.com/go-sql-driver/mysql v1.7.0 // indirect
+       github.com/jinzhu/inflection v1.0.0 // indirect
+       github.com/jinzhu/now v1.1.5 // indirect
+       github.com/mattn/go-sqlite3 v1.14.16 // indirect
+)
+
 require (
        contrib.go.opencensus.io/exporter/prometheus v0.4.1 // indirect
        github.com/RoaringBitmap/roaring v1.1.0 // indirect
@@ -144,6 +151,9 @@ require (
        gopkg.in/ini.v1 v1.51.0 // indirect
        gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
+       gorm.io/driver/mysql v1.4.7
+       gorm.io/driver/sqlite v1.4.4
+       gorm.io/gorm v1.24.6
        k8s.io/klog/v2 v2.80.1 // indirect
        k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
        sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
diff --git a/go.sum b/go.sum
index e3273fbe..86c96788 100644
--- a/go.sum
+++ b/go.sum
@@ -261,6 +261,9 @@ github.com/go-playground/validator/v10 v10.11.1 
h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJ
 github.com/go-playground/validator/v10 v10.11.1/go.mod 
h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 github.com/go-resty/resty/v2 v2.7.0/go.mod 
h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
 github.com/go-sql-driver/mysql v1.4.0/go.mod 
h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.7.0 
h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod 
h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0 
h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod 
h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod 
h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
 github.com/go-test/deep v1.0.2/go.mod 
h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@@ -452,6 +455,11 @@ github.com/jessevdk/go-flags v1.4.0/go.mod 
h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
 github.com/jhump/protoreflect v1.6.0/go.mod 
h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
 github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
 github.com/jinzhu/copier v0.3.5/go.mod 
h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
+github.com/jinzhu/inflection v1.0.0 
h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod 
h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.4/go.mod 
h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod 
h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod 
h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0 
h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod 
h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -535,6 +543,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod 
h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-isatty v0.0.16 
h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
 github.com/mattn/go-isatty v0.0.16/go.mod 
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-runewidth v0.0.2/go.mod 
h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod 
h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.16 
h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod 
h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 
h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod 
h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2 
h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
 github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod 
h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -1280,6 +1292,14 @@ gopkg.in/yaml.v3 
v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
+gorm.io/driver/mysql v1.4.7/go.mod 
h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
+gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
+gorm.io/driver/sqlite v1.4.4/go.mod 
h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
+gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
+gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
+gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
 gotest.tools v2.2.0+incompatible/go.mod 
h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod 
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod 
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/admin/config/config.go b/pkg/admin/config/config.go
index 72f1c34e..def8c4a8 100644
--- a/pkg/admin/config/config.go
+++ b/pkg/admin/config/config.go
@@ -29,9 +29,13 @@ import (
        "dubbo.apache.org/dubbo-go/v3/config_center"
        "dubbo.apache.org/dubbo-go/v3/metadata/report"
        "dubbo.apache.org/dubbo-go/v3/registry"
+       "gorm.io/driver/mysql"
+       "gorm.io/driver/sqlite"
+       "gorm.io/gorm"
 
        "github.com/apache/dubbo-admin/pkg/admin/constant"
        _ "github.com/apache/dubbo-admin/pkg/admin/imports"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
        "gopkg.in/yaml.v2"
 )
 
@@ -51,6 +55,7 @@ type Admin struct {
        ConfigCenter   string        `yaml:"config-center"`
        MetadataReport AddressConfig `yaml:"metadata-report"`
        Registry       AddressConfig `yaml:"registry"`
+       MysqlDsn       string        `yaml:"mysql-dsn"`
 }
 
 var (
@@ -58,6 +63,8 @@ var (
        RegistryCenter       registry.Registry
        MetadataReportCenter report.MetadataReport
 
+       DataBase *gorm.DB // for service mock
+
        Group string
 )
 
@@ -77,9 +84,13 @@ func LoadConfig() {
        }
        var config Config
        yaml.Unmarshal(content, &config)
+
        address := config.Admin.ConfigCenter
        registryAddress := config.Admin.Registry.Address
        metadataReportAddress := config.Admin.MetadataReport.Address
+
+       loadDatabaseConfig(config.Admin.MysqlDsn)
+
        PrometheusIp = config.Prometheus.Ip
        PrometheusPort = config.Prometheus.Port
        if PrometheusIp == "" {
@@ -88,6 +99,7 @@ func LoadConfig() {
        if PrometheusPort == "" {
                PrometheusPort = "9090"
        }
+
        if len(address) > 0 {
                c := newAddressConfig(address)
                factory, err := 
extension.GetConfigCenterFactory(c.getProtocol())
@@ -170,3 +182,21 @@ func newAddressConfig(address string) AddressConfig {
        }
        return config
 }
+
+// load database for mock rule storage, if dsn is empty, use sqlite in memory
+func loadDatabaseConfig(dsn string) {
+       var db *gorm.DB
+       var err error
+       if dsn == "" {
+               db, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), 
&gorm.Config{})
+       } else {
+               db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
+       }
+       if err != nil {
+               panic(err)
+       } else {
+               DataBase = db
+               // init table
+               DataBase.AutoMigrate(&model.MockRuleEntity{})
+       }
+}
diff --git a/pkg/admin/handlers/mock_rule.go b/pkg/admin/handlers/mock_rule.go
new file mode 100644
index 00000000..27ad3a35
--- /dev/null
+++ b/pkg/admin/handlers/mock_rule.go
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package handlers
+
+import (
+       "net/http"
+       "strconv"
+
+       "github.com/apache/dubbo-admin/pkg/admin/mapper"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "github.com/apache/dubbo-admin/pkg/admin/services"
+       "github.com/apache/dubbo-admin/pkg/logger"
+       "github.com/gin-gonic/gin"
+)
+
+var mockRuleService services.MockRuleService = &services.MockRuleServiceImpl{
+       MockRuleMapper: &mapper.MockRuleMapperImpl{},
+       Logger:         logger.Logger(),
+}
+
+func CreateOrUpdateMockRule(c *gin.Context) {
+       var mockRule *model.MockRule
+       if err := c.ShouldBindJSON(&mockRule); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       if err := mockRuleService.CreateOrUpdateMockRule(mockRule); err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+               return
+       }
+
+       c.JSON(http.StatusCreated, true)
+}
+
+func DeleteMockRuleById(c *gin.Context) {
+       // TODO use c.Param("id") instead of http body
+       // id, err := strconv.ParseInt(c.Param("id"), 10, 64)
+
+       var mockRule *model.MockRule
+       if err := c.ShouldBindJSON(&mockRule); err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       if mockRule.ID == 0 {
+               c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
+               return
+       }
+       if err := mockRuleService.DeleteMockRuleById(int64(mockRule.ID)); err 
!= nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+       c.JSON(http.StatusOK, true)
+}
+
+func ListMockRulesByPage(c *gin.Context) {
+       filter := c.Query("filter")
+       offset, err := strconv.Atoi(c.DefaultQuery("offset", "0"))
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       limit, err := strconv.Atoi(c.DefaultQuery("limit", "-1"))
+       if err != nil {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               return
+       }
+
+       mockRules, total, err := mockRuleService.ListMockRulesByPage(filter, 
offset, limit)
+       if err != nil {
+               c.JSON(http.StatusInternalServerError, gin.H{"error": 
err.Error()})
+               return
+       }
+
+       // FIXME: the response data is not compatible with the frontend
+       c.JSON(http.StatusOK, gin.H{
+               "total":   total,
+               "content": mockRules,
+       })
+}
diff --git a/pkg/admin/mapper/mock_rule_mapper.go 
b/pkg/admin/mapper/mock_rule_mapper.go
new file mode 100644
index 00000000..5123c547
--- /dev/null
+++ b/pkg/admin/mapper/mock_rule_mapper.go
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package mapper
+
+import (
+       "github.com/apache/dubbo-admin/pkg/admin/config"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+)
+
+type MockRuleMapper interface {
+       Create(mockRule *model.MockRuleEntity) error
+       Update(mockRule *model.MockRuleEntity) error
+       DeleteById(id int64) error
+       FindByServiceNameAndMethodName(serviceName, methodName string) 
(*model.MockRuleEntity, error)
+       FindByPage(filter string, offset, limit int) ([]*model.MockRuleEntity, 
int64, error)
+}
+
+type MockRuleMapperImpl struct{}
+
+func (m *MockRuleMapperImpl) Create(mockRule *model.MockRuleEntity) error {
+       return config.DataBase.Create(mockRule).Error
+}
+
+func (m *MockRuleMapperImpl) Update(mockRule *model.MockRuleEntity) error {
+       return config.DataBase.Updates(mockRule).Error
+}
+
+func (m *MockRuleMapperImpl) DeleteById(id int64) error {
+       return config.DataBase.Delete(&model.MockRuleEntity{}, id).Error
+}
+
+func (m *MockRuleMapperImpl) FindByServiceNameAndMethodName(serviceName, 
methodName string) (*model.MockRuleEntity, error) {
+       var mockRule model.MockRuleEntity
+       err := config.DataBase.Where("service_name = ? and method_name = ?", 
serviceName, methodName).Limit(1).Find(&mockRule).Error
+       return &mockRule, err
+}
+
+func (m *MockRuleMapperImpl) FindByPage(filter string, offset, limit int) 
([]*model.MockRuleEntity, int64, error) {
+       var mockRules []*model.MockRuleEntity
+       var total int64
+       err := config.DataBase.Where("service_name like ?", 
"%"+filter+"%").Offset(offset).Limit(limit).Find(&mockRules).Error
+       config.DataBase.Model(&model.MockRuleEntity{}).Where("service_name like 
?", "%"+filter+"%").Count(&total)
+       return mockRules, total, err
+}
diff --git a/pkg/admin/mapper/mock_rule_mapper_mock.go 
b/pkg/admin/mapper/mock_rule_mapper_mock.go
new file mode 100644
index 00000000..949133c8
--- /dev/null
+++ b/pkg/admin/mapper/mock_rule_mapper_mock.go
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/apache/dubbo-admin/pkg/admin/mapper (interfaces: 
MockRuleMapper)
+
+// Package mapper is a generated GoMock package.
+package mapper
+
+import (
+       reflect "reflect"
+
+       model "github.com/apache/dubbo-admin/pkg/admin/model"
+       gomock "github.com/golang/mock/gomock"
+)
+
+// MockMockRuleMapper is a mock of MockRuleMapper interface.
+type MockMockRuleMapper struct {
+       ctrl     *gomock.Controller
+       recorder *MockMockRuleMapperMockRecorder
+}
+
+// MockMockRuleMapperMockRecorder is the mock recorder for MockMockRuleMapper.
+type MockMockRuleMapperMockRecorder struct {
+       mock *MockMockRuleMapper
+}
+
+// NewMockMockRuleMapper creates a new mock instance.
+func NewMockMockRuleMapper(ctrl *gomock.Controller) *MockMockRuleMapper {
+       mock := &MockMockRuleMapper{ctrl: ctrl}
+       mock.recorder = &MockMockRuleMapperMockRecorder{mock}
+       return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockMockRuleMapper) EXPECT() *MockMockRuleMapperMockRecorder {
+       return m.recorder
+}
+
+// Create mocks base method.
+func (m *MockMockRuleMapper) Create(arg0 *model.MockRuleEntity) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "Create", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// Create indicates an expected call of Create.
+func (mr *MockMockRuleMapperMockRecorder) Create(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", 
reflect.TypeOf((*MockMockRuleMapper)(nil).Create), arg0)
+}
+
+// DeleteById mocks base method.
+func (m *MockMockRuleMapper) DeleteById(arg0 int64) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "DeleteById", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// DeleteById indicates an expected call of DeleteById.
+func (mr *MockMockRuleMapperMockRecorder) DeleteById(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteById", 
reflect.TypeOf((*MockMockRuleMapper)(nil).DeleteById), arg0)
+}
+
+// FindByPage mocks base method.
+func (m *MockMockRuleMapper) FindByPage(arg0 string, arg1, arg2 int) 
([]*model.MockRuleEntity, int64, error) {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "FindByPage", arg0, arg1, arg2)
+       ret0, _ := ret[0].([]*model.MockRuleEntity)
+       ret1, _ := ret[1].(int64)
+       ret2, _ := ret[2].(error)
+       return ret0, ret1, ret2
+}
+
+// FindByPage indicates an expected call of FindByPage.
+func (mr *MockMockRuleMapperMockRecorder) FindByPage(arg0, arg1, arg2 
interface{}) *gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByPage", 
reflect.TypeOf((*MockMockRuleMapper)(nil).FindByPage), arg0, arg1, arg2)
+}
+
+// FindByServiceNameAndMethodName mocks base method.
+func (m *MockMockRuleMapper) FindByServiceNameAndMethodName(arg0, arg1 string) 
(*model.MockRuleEntity, error) {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "FindByServiceNameAndMethodName", arg0, arg1)
+       ret0, _ := ret[0].(*model.MockRuleEntity)
+       ret1, _ := ret[1].(error)
+       return ret0, ret1
+}
+
+// FindByServiceNameAndMethodName indicates an expected call of 
FindByServiceNameAndMethodName.
+func (mr *MockMockRuleMapperMockRecorder) FindByServiceNameAndMethodName(arg0, 
arg1 interface{}) *gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, 
"FindByServiceNameAndMethodName", 
reflect.TypeOf((*MockMockRuleMapper)(nil).FindByServiceNameAndMethodName), 
arg0, arg1)
+}
+
+// Update mocks base method.
+func (m *MockMockRuleMapper) Update(arg0 *model.MockRuleEntity) error {
+       m.ctrl.T.Helper()
+       ret := m.ctrl.Call(m, "Update", arg0)
+       ret0, _ := ret[0].(error)
+       return ret0
+}
+
+// Update indicates an expected call of Update.
+func (mr *MockMockRuleMapperMockRecorder) Update(arg0 interface{}) 
*gomock.Call {
+       mr.mock.ctrl.T.Helper()
+       return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", 
reflect.TypeOf((*MockMockRuleMapper)(nil).Update), arg0)
+}
diff --git a/pkg/admin/model/mock_rule.go b/pkg/admin/model/mock_rule.go
new file mode 100644
index 00000000..eaab05db
--- /dev/null
+++ b/pkg/admin/model/mock_rule.go
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+       "gorm.io/gorm"
+)
+
+type MockRule struct {
+       ID          uint   `json:"id"`
+       ServiceName string `json:"serviceName"`
+       MethodName  string `json:"methodName"`
+       Rule        string `json:"rule"`
+       Enable      bool   `json:"enable"`
+}
+
+func (m *MockRule) ToMockRuleEntity() *MockRuleEntity {
+       return &MockRuleEntity{
+               Model: gorm.Model{
+                       ID: m.ID,
+               },
+               ServiceName: m.ServiceName,
+               MethodName:  m.MethodName,
+               Rule:        m.Rule,
+               Enable:      m.Enable,
+       }
+}
+
+type MockRuleEntity struct {
+       gorm.Model
+       ServiceName string `gorm:"type:varchar(255)"`
+       MethodName  string `gorm:"type:varchar(255)"`
+       Rule        string `gorm:"type:text"`
+       Enable      bool
+}
+
+func (m *MockRuleEntity) ToMockRule() *MockRule {
+       return &MockRule{
+               ID:          uint(m.ID),
+               ServiceName: m.ServiceName,
+               MethodName:  m.MethodName,
+               Rule:        m.Rule,
+               Enable:      m.Enable,
+       }
+}
+
+func (m *MockRuleEntity) TableName() string {
+       return "mock_rule"
+}
diff --git a/pkg/admin/router/router.go b/pkg/admin/router/router.go
index ebe157c4..b24ead20 100644
--- a/pkg/admin/router/router.go
+++ b/pkg/admin/router/router.go
@@ -67,5 +67,12 @@ func InitRouter() *gin.Engine {
                conditionRoute.PUT("/disable/:id", 
handlers.DisableConditionRoute)
        }
 
+       mockRoute := router.Group("/api/:env/mock/rule")
+       {
+               mockRoute.POST("/", handlers.CreateOrUpdateMockRule)
+               mockRoute.DELETE("/", handlers.DeleteMockRuleById)
+               mockRoute.GET("/list", handlers.ListMockRulesByPage)
+       }
+
        return router
 }
diff --git a/pkg/admin/services/mock_rule_service.go 
b/pkg/admin/services/mock_rule_service.go
new file mode 100644
index 00000000..3ae5ce4b
--- /dev/null
+++ b/pkg/admin/services/mock_rule_service.go
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package services
+
+import "github.com/apache/dubbo-admin/pkg/admin/model"
+
+type MockRuleService interface {
+       // create or update mock rule. if the request contains id, then will be 
an update operation.
+       CreateOrUpdateMockRule(mockRule *model.MockRule) error
+
+       // delete the mock rule data by mock rule id.
+       DeleteMockRuleById(id int64) error
+
+       // list the mock rules by filter and return data by page.
+       ListMockRulesByPage(filter string, offset, limit int) 
([]*model.MockRule, int64, error)
+
+       // TODO getMockData method, which depends on the implementation 
corresponding to mock of dubbo-go.
+}
diff --git a/pkg/admin/services/mock_rule_service_impl.go 
b/pkg/admin/services/mock_rule_service_impl.go
new file mode 100644
index 00000000..f6889961
--- /dev/null
+++ b/pkg/admin/services/mock_rule_service_impl.go
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package services
+
+import (
+       "errors"
+
+       "github.com/apache/dubbo-admin/pkg/admin/mapper"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "go.uber.org/zap"
+)
+
+type MockRuleServiceImpl struct {
+       MockRuleMapper mapper.MockRuleMapper
+       Logger         *zap.Logger
+}
+
+func (m *MockRuleServiceImpl) CreateOrUpdateMockRule(mockRule *model.MockRule) 
error {
+       if mockRule.ServiceName == "" || mockRule.MethodName == "" || 
mockRule.Rule == "" {
+               return nil
+       }
+
+       existRule, err := 
m.MockRuleMapper.FindByServiceNameAndMethodName(mockRule.ServiceName, 
mockRule.MethodName)
+       if err != nil {
+               m.Logger.Error(err.Error())
+               return err
+       }
+
+       isExist := existRule.ID != 0
+       // check if we can save or update the rule, we need keep the 
serviceName + methodName is unique.
+       if isExist {
+               if mockRule.ID != existRule.ID {
+                       err := errors.New("service name and method name must be 
unique")
+                       m.Logger.Error(err.Error())
+                       return err
+               }
+               if err := m.MockRuleMapper.Update(mockRule.ToMockRuleEntity()); 
err != nil {
+                       m.Logger.Error(err.Error())
+                       return err
+               }
+       } else {
+               if err := m.MockRuleMapper.Create(mockRule.ToMockRuleEntity()); 
err != nil {
+                       m.Logger.Error(err.Error())
+                       return err
+               }
+       }
+       return nil
+}
+
+func (m *MockRuleServiceImpl) DeleteMockRuleById(id int64) error {
+       if err := m.MockRuleMapper.DeleteById(id); err != nil {
+               m.Logger.Error(err.Error())
+               return err
+       }
+       return nil
+}
+
+func (m *MockRuleServiceImpl) ListMockRulesByPage(filter string, offset, limit 
int) ([]*model.MockRule, int64, error) {
+       result, total, err := m.MockRuleMapper.FindByPage(filter, offset, limit)
+       if err != nil {
+               m.Logger.Error(err.Error())
+               return nil, 0, err
+       }
+
+       morkRules := make([]*model.MockRule, 0)
+       for _, mockRuleEntity := range result {
+               morkRules = append(morkRules, mockRuleEntity.ToMockRule())
+       }
+       return morkRules, total, nil
+}
diff --git a/pkg/admin/services/mock_rule_service_impl_test.go 
b/pkg/admin/services/mock_rule_service_impl_test.go
new file mode 100644
index 00000000..b98be6ad
--- /dev/null
+++ b/pkg/admin/services/mock_rule_service_impl_test.go
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package services
+
+import (
+       "reflect"
+       "testing"
+
+       "github.com/apache/dubbo-admin/pkg/admin/mapper"
+       "github.com/apache/dubbo-admin/pkg/admin/model"
+       "github.com/apache/dubbo-admin/pkg/logger"
+       "github.com/golang/mock/gomock"
+)
+
+func TestMockRuleServiceImpl_CreateOrUpdateMockRule(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockMockRuleMapper := mapper.NewMockMockRuleMapper(ctrl)
+
+       mockUnExistData := &model.MockRule{}
+       createData := &model.MockRule{ID: 1, ServiceName: "testService1", 
MethodName: "testMethod1", Rule: "exampleRule", Enable: true}
+       mockExistData := &model.MockRule{ID: 1, ServiceName: "testService2", 
MethodName: "testMethod2", Rule: "exampleRule", Enable: true}
+       updateData := &model.MockRule{ID: 1, ServiceName: "testService2", 
MethodName: "testMethod2", Rule: "exampleRuleAfterUpdate", Enable: true}
+
+       
mockMockRuleMapper.EXPECT().FindByServiceNameAndMethodName(createData.ServiceName,
 createData.MethodName).Return(mockUnExistData.ToMockRuleEntity(), nil)
+       
mockMockRuleMapper.EXPECT().Create(createData.ToMockRuleEntity()).Return(nil)
+       
mockMockRuleMapper.EXPECT().FindByServiceNameAndMethodName(mockExistData.ServiceName,
 mockExistData.MethodName).Return(mockExistData.ToMockRuleEntity(), nil)
+       
mockMockRuleMapper.EXPECT().Update(updateData.ToMockRuleEntity()).Return(nil)
+
+       type args struct {
+               mockRule *model.MockRule
+       }
+       tests := []struct {
+               name    string
+               m       *MockRuleServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "test create mock rule",
+                       m: &MockRuleServiceImpl{
+                               MockRuleMapper: mockMockRuleMapper,
+                               Logger:         logger.Logger(),
+                       },
+                       args: args{
+                               mockRule: createData,
+                       },
+                       wantErr: false,
+               },
+               {
+                       name: "test update mock rule",
+                       m: &MockRuleServiceImpl{
+                               MockRuleMapper: mockMockRuleMapper,
+                               Logger:         logger.Logger(),
+                       },
+                       args: args{
+                               mockRule: updateData,
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := 
tt.m.CreateOrUpdateMockRule(tt.args.mockRule); (err != nil) != tt.wantErr {
+                               
t.Errorf("MockRuleServiceImpl.CreateOrUpdateMockRule() error = %v, wantErr %v", 
err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestMockRuleServiceImpl_DeleteMockRuleById(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockMockRuleMapper := mapper.NewMockMockRuleMapper(ctrl)
+
+       mockMockRuleMapper.EXPECT().DeleteById(int64(1)).Return(nil)
+
+       type args struct {
+               id int64
+       }
+       tests := []struct {
+               name    string
+               m       *MockRuleServiceImpl
+               args    args
+               wantErr bool
+       }{
+               {
+                       name: "test delete mock rule",
+                       m: &MockRuleServiceImpl{
+                               MockRuleMapper: mockMockRuleMapper,
+                               Logger:         logger.Logger(),
+                       },
+                       args: args{
+                               id: 1,
+                       },
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if err := tt.m.DeleteMockRuleById(tt.args.id); (err != 
nil) != tt.wantErr {
+                               
t.Errorf("MockRuleServiceImpl.DeleteMockRuleById() error = %v, wantErr %v", 
err, tt.wantErr)
+                       }
+               })
+       }
+}
+
+func TestMockRuleServiceImpl_ListMockRulesByPage(t *testing.T) {
+       ctrl := gomock.NewController(t)
+       mockMockRuleMapper := mapper.NewMockMockRuleMapper(ctrl)
+
+       findResult := &model.MockRule{ID: 1, ServiceName: "testService2", 
MethodName: "testMethod2", Rule: "exampleRule", Enable: true}
+       mockMockRuleMapper.EXPECT().FindByPage("", 0, 
-1).Return([]*model.MockRuleEntity{findResult.ToMockRuleEntity()}, int64(1), 
nil)
+
+       type args struct {
+               filter string
+               offset int
+               limit  int
+       }
+       tests := []struct {
+               name    string
+               m       *MockRuleServiceImpl
+               args    args
+               want    []*model.MockRule
+               want1   int64
+               wantErr bool
+       }{
+               {
+                       name: "test list mock rule",
+                       m: &MockRuleServiceImpl{
+                               MockRuleMapper: mockMockRuleMapper,
+                               Logger:         logger.Logger(),
+                       },
+                       args: args{
+                               filter: "",
+                               offset: 0,
+                               limit:  -1,
+                       },
+                       want:    []*model.MockRule{findResult},
+                       want1:   1,
+                       wantErr: false,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, got1, err := 
tt.m.ListMockRulesByPage(tt.args.filter, tt.args.offset, tt.args.limit)
+                       if (err != nil) != tt.wantErr {
+                               
t.Errorf("MockRuleServiceImpl.ListMockRulesByPage() error = %v, wantErr %v", 
err, tt.wantErr)
+                               return
+                       }
+                       if !reflect.DeepEqual(got, tt.want) {
+                               
t.Errorf("MockRuleServiceImpl.ListMockRulesByPage() got = %v, want %v", got, 
tt.want)
+                       }
+                       if got1 != tt.want1 {
+                               
t.Errorf("MockRuleServiceImpl.ListMockRulesByPage() got1 = %v, want %v", got1, 
tt.want1)
+                       }
+               })
+       }
+}

Reply via email to