diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e06dce6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +## Description + + + +## Checklist + + + +- [ ] the pull request title describes what this PR does (not a vague title like Update index.md) +- [ ] the pull request targets the default branch of the repository (main) +- [ ] no unintentional fmt.Print left behind after debugging +- [ ] did I name variables, methods and classes according to the naming rules? (https://go.dev/doc/effective_go#names) +- [ ] caught exceptions or throw them to the upper level for processing, not ignored (https://go.dev/doc/effective_go#errors) +- [ ] did I explain all possible solutions and why I chose the one I did? +- [ ] added any comments to make new functions clearer +- [ ] tests are added for the changes I made (if any source code was modified) +- [ ] documentation added or updated +- [ ] I have run the project locally and verified that there are no errors +- [ ] instructions for how reviewers can test the code locally +- [ ] screenshot of the feature/bug fix (if applicable) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea2c9b4..05b9641 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@v3 - name: install deps run: | + go get -u golang.org/x/tools/cmd/goimports go install golang.org/x/tools/cmd/goimports - name: golangci-lint diff --git a/go.mod b/go.mod index f79111e..a84dfc0 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/kamva/mgm/v3 v3.5.0 github.com/prometheus/client_golang v1.12.1 github.com/robfig/cron/v3 v3.0.1 + github.com/sanity-io/litter v1.5.5 github.com/sirupsen/logrus v1.8.1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/smartystreets/goconvey v1.7.2 @@ -43,8 +44,6 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/tools v0.13.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 4d09da0..c9e43fe 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -419,6 +420,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -455,6 +457,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -481,6 +485,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -600,8 +605,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -800,8 +803,6 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testdata/test.yml b/testdata/test.yml new file mode 100644 index 0000000..f4eafe1 --- /dev/null +++ b/testdata/test.yml @@ -0,0 +1,18 @@ +tableName: test +fields: + - name: id + type: string + primaryKey: true + size: 64 + creatable: true + updatable: false + readable: true + Comment: ID + - name: name + type: string + size: 63 + creatable: true + updatable: true + readable: true + Comment: 名称 + index: name_index \ No newline at end of file diff --git a/virtual/model/field.go b/virtual/model/field.go new file mode 100644 index 0000000..ccbca07 --- /dev/null +++ b/virtual/model/field.go @@ -0,0 +1,93 @@ +package model + +import ( + "fmt" + "reflect" + "strings" + "time" + + "github.com/google/uuid" + "gorm.io/gorm/schema" +) + +/* + * @Author: lwnmengjing + * @Date: 2023/9/10 16:11:55 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2023/9/10 16:11:55 + */ + +type Field struct { + Name string `json:"name" yaml:"name" binding:"required"` + JsonTag string `json:"jsonTag" yaml:"jsonTag"` + DataType schema.DataType `json:"type" yaml:"type" binding:"required"` + PrimaryKey bool `json:"primaryKey" yaml:"primaryKey"` + AutoIncrement bool `json:"autoIncrement" yaml:"autoIncrement"` + AutoIncrementIncrement int64 `json:"autoIncrementIncrement" yaml:"autoIncrementIncrement"` + Creatable bool `json:"creatable" yaml:"creatable"` + Updatable bool `json:"updatable" yaml:"updatable"` + Readable bool `json:"readable" yaml:"readable"` + DefaultValue string `json:"defaultValue" yaml:"defaultValue"` + DefaultValueFN func() string `json:"-" yaml:"-"` + NotNull bool `json:"notNull" yaml:"notNull"` + Unique bool `json:"unique" yaml:"unique"` + Index string `json:"index" yaml:"index"` + Comment string `json:"comment" yaml:"comment"` + Size int `json:"size" yaml:"size"` + Precision int `json:"precision" yaml:"precision"` + Scale int `json:"scale" yaml:"scale"` + Search string `json:"search" yaml:"search"` +} + +type DefaultFN string + +const ( + UUID DefaultFN = "uuid" + Now DefaultFN = "now" +) + +var UUIDFN = func() string { + return strings.ReplaceAll(uuid.New().String(), "-", "") +} + +var NowFN = func() string { + return time.Now().String() +} + +func (f *Field) Init() { + if f.JsonTag == "" { + f.JsonTag = f.Name + } + if f.PrimaryKey { + f.DefaultValueFN = UUIDFN + } + if f.DataType == schema.Time && f.NotNull { + f.DefaultValueFN = NowFN + } +} + +func (f *Field) GetName() string { + return strings.ToUpper(f.Name[:1]) + f.Name[1:] +} + +func (f *Field) MakeField() reflect.StructField { + field := reflect.StructField{ + Name: f.GetName(), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s" gorm:"column:%s"`, f.JsonTag, f.Name)), + } + switch f.DataType { + case schema.Bool: + field.Type = reflect.TypeOf(false) + case schema.Float: + field.Type = reflect.TypeOf(float64(0)) + case schema.Int: + field.Type = reflect.TypeOf(int(0)) + case schema.Uint: + field.Type = reflect.TypeOf(uint(0)) + case schema.Time: + field.Type = reflect.TypeOf(time.Time{}) + default: + field.Type = reflect.TypeOf("") + } + return field +} diff --git a/virtual/model/model.go b/virtual/model/model.go new file mode 100644 index 0000000..a1ad983 --- /dev/null +++ b/virtual/model/model.go @@ -0,0 +1,155 @@ +package model + +import ( + "fmt" + "reflect" + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +/* + * @Author: lwnmengjing + * @Date: 2023/9/10 15:29:38 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2023/9/10 15:29:38 + */ + +type Model struct { + Table string `json:"tableName" yaml:"tableName" binding:"required"` + AutoCreateTime schema.TimeType `json:"autoCreateTime" yaml:"autoCreateTime"` + AutoUpdateTime schema.TimeType `json:"autoUpdateTime" yaml:"autoUpdateTime"` + HardDeleted bool `json:"hardDeleted" yaml:"hardDeleted"` + Fields []*Field `json:"fields" yaml:"fields" binding:"required"` +} + +// TableName get table name +func (m *Model) TableName() string { + return m.Table +} + +// PrimaryKeys get primary keys +func (m *Model) PrimaryKeys() []string { + var keys []string + for i := range m.Fields { + if m.Fields[i].PrimaryKey { + keys = append(keys, m.Fields[i].Name) + } + } + return keys +} + +func (m *Model) Init() { + if m.AutoCreateTime == 0 { + m.AutoCreateTime = schema.UnixSecond + } + if m.AutoUpdateTime == 0 { + m.AutoUpdateTime = schema.UnixSecond + } + for i := range m.Fields { + m.Fields[i].Init() + } +} + +func (m *Model) Default(data any) { + for i := range m.Fields { + df := m.Fields[i].DefaultValue + if m.Fields[i].DefaultValueFN != nil { + df = m.Fields[i].DefaultValueFN() + } + if df == "" { + continue + } + reflect.ValueOf(data).Elem().FieldByName(m.Fields[i].GetName()).Set(reflect.ValueOf(df)) + } +} + +// MakeModel make virtual model +func (m *Model) MakeModel() any { + fieldTypes := make([]reflect.StructField, 0) + for i := range m.Fields { + fieldTypes = append(fieldTypes, m.Fields[i].MakeField()) + } + return reflect.New(reflect.StructOf(fieldTypes)).Interface() +} + +func (m *Model) MakeList() any { + fieldTypes := make([]reflect.StructField, 0) + for i := range m.Fields { + fieldTypes = append(fieldTypes, m.Fields[i].MakeField()) + } + return reflect.New(reflect.SliceOf(reflect.StructOf(fieldTypes))).Interface() +} + +func (m *Model) TableScope(db *gorm.DB) *gorm.DB { + return db.Table(m.TableName()) +} + +func (m *Model) URI(ctx *gin.Context) (f func(*gorm.DB) *gorm.DB) { + return func(db *gorm.DB) *gorm.DB { + db = db.Table(m.TableName()) + for _, key := range m.PrimaryKeys() { + db = db.Where(fmt.Sprintf("%s in (?)", key), strings.Split(ctx.Param(key), ",")) + } + return db + } +} + +func (m *Model) Pagination(ctx *gin.Context, p PaginationImp) (f func(*gorm.DB) *gorm.DB) { + err := ctx.ShouldBindQuery(p) + return func(db *gorm.DB) *gorm.DB { + if err != nil { + _ = db.AddError(err) + return db + } + offset := (p.GetCurrent() - 1) * p.GetPageSize() + return db.Offset(offset).Limit(p.GetPageSize()) + } +} + +func (m *Model) Search(ctx *gin.Context) (f func(*gorm.DB) *gorm.DB) { + return func(db *gorm.DB) *gorm.DB { + for i := range m.Fields { + v, ok := ctx.GetQuery(m.Fields[i].JsonTag) + if !ok { + continue + } + switch m.Fields[i].Search { + case "exact", "iexact": + db = db.Where(fmt.Sprintf("`%s`.`%s` = ?", m.Table, m.Fields[i].Name), v) + case "contains", "icontains": + db = db.Where(fmt.Sprintf("`%s`.`%s` like ?", m.Table, m.Fields[i].Name), "%"+v+"%") + case "gt": + db = db.Where(fmt.Sprintf("`%s`.`%s` > ?", m.Table, m.Fields[i].Name), v) + case "gte": + db = db.Where(fmt.Sprintf("`%s`.`%s` >= ?", m.Table, m.Fields[i].Name), v) + case "lt": + db = db.Where(fmt.Sprintf("`%s`.`%s` < ?", m.Table, m.Fields[i].Name), v) + case "lte": + db = db.Where(fmt.Sprintf("`%s`.`%s` <= ?", m.Table, m.Fields[i].Name), v) + case "startWith", "istartWith": + db = db.Where(fmt.Sprintf("`%s`.`%s` like ?", m.Table, m.Fields[i].Name), v+"%") + case "endWith", "iendWith": + db = db.Where(fmt.Sprintf("`%s`.`%s` like ?", m.Table, m.Fields[i].Name), "%"+v) + case "in": + arr, ok := ctx.GetQueryArray(m.Fields[i].JsonTag) + if !ok { + continue + } + db = db.Where(fmt.Sprintf("`%s`.`%s` in (?)", m.Table, m.Fields[i].JsonTag), arr) + case "isnull": + db = db.Where(fmt.Sprintf("`%s`.`%s` isnull", m.Table, m.Fields[i].JsonTag)) + case "order": + switch v { + case "desc": + db = db.Order(fmt.Sprintf("`%s`.`%s` desc", m.Table, m.Fields[i].JsonTag)) + case "asc": + db = db.Order(fmt.Sprintf("`%s`.`%s` asc", m.Table, m.Fields[i].JsonTag)) + } + } + } + return db + } +} diff --git a/virtual/model/model_test.go b/virtual/model/model_test.go new file mode 100644 index 0000000..b62a409 --- /dev/null +++ b/virtual/model/model_test.go @@ -0,0 +1,332 @@ +package model + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/sanity-io/litter" + "gorm.io/driver/mysql" + "gorm.io/gorm" + + "gopkg.in/yaml.v3" +) + +/* + * @Author: lwnmengjing + * @Date: 2023/9/10 15:32:51 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2023/9/10 15:32:51 + */ + +var id = strings.ReplaceAll(uuid.New().String(), "-", "") + +type PaginationTest struct { + PageSize int `query:"pageSize" form:"pageSize"` + Total int64 `query:"total" form:"total"` + Current int `query:"current" form:"current"` +} + +func (p *PaginationTest) SetPageSize(size int) { + p.PageSize = size +} + +func (p *PaginationTest) GetPageSize() int { + return p.PageSize +} + +func (p *PaginationTest) SetTotal(total int64) { + p.Total = total +} + +func (p *PaginationTest) GetTotal() int64 { + return p.Total +} + +func (p *PaginationTest) SetCurrent(current int) { + p.Current = current +} + +func (p *PaginationTest) GetCurrent() int { + return p.Current +} + +func TestModel_TableName(t *testing.T) { + tests := []struct { + name string + path string + want string + }{ + { + name: "test", + path: "../../testdata/test.yml", + want: "test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := os.ReadFile(tt.path) + if err != nil { + t.Fatalf("ReadFile() error = %m", err) + } + m := &Model{} + err = yaml.Unmarshal(rb, m) + if err != nil { + t.Fatalf("Unmarshal() error = %m", err) + } + if got := m.TableName(); got != tt.want { + t.Errorf("TableName() = %s, want %v", got, tt.want) + } + }) + } +} + +func TestModel_Create(t *testing.T) { + tests := []struct { + name string + path string + body string + dsn string + want bool + }{ + { + name: "test0", + path: "../../testdata/test.yml", + body: fmt.Sprintf(`{"id": "%s","name": "test0"}`, id), + dsn: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local", + want: false, + }, + { + name: "test1", + path: "../../testdata/test.yml", + body: `{"name": "test1"}`, + dsn: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := os.ReadFile(tt.path) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + m := &Model{} + err = yaml.Unmarshal(rb, m) + if err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + m.Init() + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: tt.dsn, + })) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + // 创建一个虚拟的 HTTP 请求和响应 + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodPost, "/test", bytes.NewBuffer([]byte(tt.body))) + + // 创建一个 Gin 引擎并绑定路由 + r := gin.Default() + r.POST("/test", func(ctx *gin.Context) { + item := m.MakeModel() + m.Default(item) + if err = ctx.ShouldBindJSON(item); err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("ShouldBindJSON() error = %v", err) + } + if err = db.Scopes(m.TableScope).Create(item).Error; err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("Create() error = %v", err) + } + ctx.Status(http.StatusOK) + }) + // 使用虚拟请求进行请求处理 + r.ServeHTTP(w, req) + // 检查响应 + if w.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, w.Code) + } + }) + } +} + +func TestModel_Update(t *testing.T) { + tests := []struct { + name string + path string + body string + dsn string + want bool + }{ + { + name: "test", + path: "../../testdata/test.yml", + body: `{"name":"testn"}`, + dsn: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := os.ReadFile(tt.path) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + m := &Model{} + err = yaml.Unmarshal(rb, m) + if err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: tt.dsn, + })) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + // 创建一个虚拟的 HTTP 请求和响应 + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodPut, "/test/"+id, bytes.NewBuffer([]byte(tt.body))) + + // 创建一个 Gin 引擎并绑定路由 + r := gin.Default() + r.PUT("/test/:id", func(ctx *gin.Context) { + item := m.MakeModel() + if err = ctx.ShouldBindJSON(item); err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("ShouldBindJSON() error = %v", err) + } + if err = db.Scopes(m.TableScope, m.URI(ctx)).Updates(item).Error; err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("Update() error = %v", err) + } + ctx.Status(http.StatusOK) + }) + // 使用虚拟请求进行请求处理 + r.ServeHTTP(w, req) + // 检查响应 + if w.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, w.Code) + } + }) + } +} + +func TestModel_Delete(t *testing.T) { + tests := []struct { + name string + path string + dsn string + wantError bool + }{ + { + name: "test", + path: "../../testdata/test.yml", + dsn: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local", + wantError: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := os.ReadFile(tt.path) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + m := &Model{} + err = yaml.Unmarshal(rb, m) + if err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: tt.dsn, + })) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + // 创建一个虚拟的 HTTP 请求和响应 + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodDelete, "/test/"+id, nil) + r := gin.Default() + r.DELETE("/test/:id", func(ctx *gin.Context) { + if err = db.Scopes(m.URI(ctx)).Delete(nil).Error; err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("Delete() error = %v", err) + } + ctx.Status(http.StatusOK) + }) + r.ServeHTTP(w, req) + if w.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, w.Code) + } + if tt.wantError != (err != nil) { + t.Errorf("Delete() error = %v, wantError %v", err, tt.wantError) + } + }) + } +} + +func TestModel_List(t *testing.T) { + tests := []struct { + name string + path string + dsn string + wantError bool + }{ + { + name: "test", + path: "../../testdata/test.yml", + dsn: "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local", + wantError: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb, err := os.ReadFile(tt.path) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + m := &Model{} + err = yaml.Unmarshal(rb, m) + if err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: tt.dsn, + })) + if err != nil { + t.Fatalf("Open() error = %v", err) + } + // 创建一个虚拟的 HTTP 请求和响应 + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test?pageSize=10¤t=1", nil) + r := gin.Default() + r.GET("/test", func(ctx *gin.Context) { + items := m.MakeList() + litter.Dump(items) + page := &PaginationTest{} + var count int64 + if err = db.Scopes(m.TableScope, m.Search(ctx), m.Pagination(ctx, page)).Find(items).Limit(-1).Count(&count).Error; err != nil { + ctx.Status(http.StatusInternalServerError) + t.Fatalf("Find() error = %v", err) + } + page.SetTotal(count) + litter.Dump(items) + litter.Dump(page) + ctx.Status(http.StatusOK) + }) + r.ServeHTTP(w, req) + if w.Code != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, w.Code) + } + if tt.wantError != (err != nil) { + t.Errorf("List() error = %v, wantError %v", err, tt.wantError) + } + }) + } +} diff --git a/virtual/model/pagination.go b/virtual/model/pagination.go new file mode 100644 index 0000000..b37651c --- /dev/null +++ b/virtual/model/pagination.go @@ -0,0 +1,17 @@ +/* + * @Author: lwnmengjing + * @Date: 2023/9/13 10:49:06 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2023/9/13 10:49:06 + */ + +package model + +type PaginationImp interface { + SetPageSize(int) + SetCurrent(int) + SetTotal(int64) + GetCurrent() int + GetPageSize() int + GetTotal() int64 +}