From e08285b2cd9de0cc9934806a889b45915654e6a1 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sat, 1 Jul 2023 20:06:03 +0530 Subject: [PATCH 01/17] add `make:factory` command --- database/console/factory_make_command.go | 98 +++++++++++++++++++ database/console/factory_make_command_test.go | 31 ++++++ database/console/stubs.go | 13 +++ database/service_provider.go | 1 + 4 files changed, 143 insertions(+) create mode 100644 database/console/factory_make_command.go create mode 100644 database/console/factory_make_command_test.go diff --git a/database/console/factory_make_command.go b/database/console/factory_make_command.go new file mode 100644 index 000000000..a650c521b --- /dev/null +++ b/database/console/factory_make_command.go @@ -0,0 +1,98 @@ +package console + +import ( + "os" + "path/filepath" + "strings" + + "github.com/gookit/color" + + "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/console/command" + "github.com/goravel/framework/support/file" + "github.com/goravel/framework/support/str" +) + +type FactoryMakeCommand struct { +} + +func NewFactoryMakeCommand() *FactoryMakeCommand { + return &FactoryMakeCommand{} +} + +// Signature The name and signature of the console command. +func (receiver *FactoryMakeCommand) Signature() string { + return "make:factory" +} + +// Description The console command description. +func (receiver *FactoryMakeCommand) Description() string { + return "Create a new factory class" +} + +// Extend The console command extend. +func (receiver *FactoryMakeCommand) Extend() command.Extend { + return command.Extend{ + Category: "make", + } +} + +// Handle Execute the console command. +func (receiver *FactoryMakeCommand) Handle(ctx console.Context) error { + name := ctx.Argument(0) + if name == "" { + color.Redln("Not enough arguments (missing: name)") + + return nil + } + + if err := file.Create(receiver.getPath(name), receiver.populateStub(receiver.getStub(), name)); err != nil { + return err + } + + color.Greenln("Factory created successfully") + + return nil +} + +func (receiver *FactoryMakeCommand) getStub() string { + return Stubs{}.Factory() +} + +// populateStub Populate the place-holders in the command stub. +func (receiver *FactoryMakeCommand) populateStub(stub string, name string) string { + modelName, packageName, _ := receiver.parseName(name) + + stub = strings.ReplaceAll(stub, "DummyFactory", str.Case2Camel(modelName)) + stub = strings.ReplaceAll(stub, "DummyPackage", packageName) + + return stub +} + +// getPath Get the full path to the command. +func (receiver *FactoryMakeCommand) getPath(name string) string { + pwd, _ := os.Getwd() + + modelName, _, folderPath := receiver.parseName(name) + + return filepath.Join(pwd, "database", "factories", folderPath, str.Camel2Case(modelName)+".go") +} + +// parseName Parse the name to get the model name, package name and folder path. +func (receiver *FactoryMakeCommand) parseName(name string) (string, string, string) { + name = strings.TrimSuffix(name, ".go") + + segments := strings.Split(name, "/") + + modelName := segments[len(segments)-1] + + packageName := "factories" + folderPath := "" + + if len(segments) > 1 { + folderPath = filepath.Join(segments[:len(segments)-1]...) + packageName = segments[len(segments)-2] + } + + return modelName, packageName, folderPath +} diff --git a/database/console/factory_make_command_test.go b/database/console/factory_make_command_test.go new file mode 100644 index 000000000..510a663a6 --- /dev/null +++ b/database/console/factory_make_command_test.go @@ -0,0 +1,31 @@ +package console + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + consolemocks "github.com/goravel/framework/contracts/console/mocks" + "github.com/goravel/framework/support/file" +) + +func TestFactoryMakeCommand(t *testing.T) { + factoryMakeCommand := &FactoryMakeCommand{} + mockContext := &consolemocks.Context{} + mockContext.On("Argument", 0).Return("").Once() + assert.Nil(t, factoryMakeCommand.Handle(mockContext)) + + mockContext.On("Argument", 0).Return("UserFactory").Once() + assert.Nil(t, factoryMakeCommand.Handle(mockContext)) + assert.True(t, file.Exists("database/factories/user_factory.go")) + assert.True(t, file.Contain("database/factories/user_factory.go", "package factories")) + assert.True(t, file.Contain("database/factories/user_factory.go", "type UserFactory struct")) + assert.Nil(t, file.Remove("database")) + + mockContext.On("Argument", 0).Return("subdir/DemoFactory").Once() + assert.Nil(t, factoryMakeCommand.Handle(mockContext)) + assert.True(t, file.Exists("database/factories/subdir/demo_factory.go")) + assert.True(t, file.Contain("database/factories/subdir/demo_factory.go", "package subdir")) + assert.True(t, file.Contain("database/factories/subdir/demo_factory.go", "type DemoFactory struct")) + assert.Nil(t, file.Remove("database")) +} diff --git a/database/console/stubs.go b/database/console/stubs.go index 78c9077e8..9bc62a38d 100644 --- a/database/console/stubs.go +++ b/database/console/stubs.go @@ -89,3 +89,16 @@ func (s *DummySeeder) Run() error { } ` } + +func (r Stubs) Factory() string { + return `package DummyPackage + +type DummyFactory struct { +} + +// Defination Define the model's default state. +func (f *DummyFactory) Defination() { + +} +` +} \ No newline at end of file diff --git a/database/service_provider.go b/database/service_provider.go index 943ba78ab..315f57f41 100644 --- a/database/service_provider.go +++ b/database/service_provider.go @@ -52,5 +52,6 @@ func (database *ServiceProvider) registerCommands(app foundation.Application) { console.NewObserverMakeCommand(), console.NewSeedCommand(config, seeder), console.NewSeederMakeCommand(), + console.NewFactoryMakeCommand(), }) } From d2120a17526c7f18742f1161b78da6295bb57905 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Fri, 7 Jul 2023 13:33:48 +0530 Subject: [PATCH 02/17] add factory in orm facade --- contracts/database/orm/factory.go | 30 ++++++ contracts/database/orm/orm.go | 1 + database/gorm/factory.go | 166 ++++++++++++++++++++++++++++++ database/orm.go | 4 + 4 files changed, 201 insertions(+) create mode 100644 contracts/database/orm/factory.go create mode 100644 database/gorm/factory.go diff --git a/contracts/database/orm/factory.go b/contracts/database/orm/factory.go new file mode 100644 index 000000000..57174cbf1 --- /dev/null +++ b/contracts/database/orm/factory.go @@ -0,0 +1,30 @@ +package orm + +import "github.com/brianvoe/gofakeit/v6" + +//go:generate mockery --name=Factory +type Factory interface { + Definition() any + New(attributes ...map[string]any) Factory + Times(count int) Factory + Count(count int) Factory + Configure() Factory + Raw() any + CreateOne() error + CreateOneQuietly() error + CreateMany() error + CreateManyQuietly() error + Create() error + CreateQuietly() error + Store() error + MakeOne() Factory + Make() Factory + MakeInstance() Factory + GetExpandedAttributes() map[string]any + GetRawAttributes() any + Faker() *gofakeit.Faker + ExpandAttributes(definition map[string]interface{}) map[string]interface{} + Set() Factory + Model(value any) Factory + NewInstance(attributes ...map[string]any) Factory +} diff --git a/contracts/database/orm/orm.go b/contracts/database/orm/orm.go index 5297a3552..cee490d0b 100644 --- a/contracts/database/orm/orm.go +++ b/contracts/database/orm/orm.go @@ -10,6 +10,7 @@ type Orm interface { Connection(name string) Orm DB() (*sql.DB, error) Query() Query + Factory() Factory Observe(model any, observer Observer) Transaction(txFunc func(tx Transaction) error) error WithContext(ctx context.Context) Orm diff --git a/database/gorm/factory.go b/database/gorm/factory.go new file mode 100644 index 000000000..f3218291c --- /dev/null +++ b/database/gorm/factory.go @@ -0,0 +1,166 @@ +package gorm + +import ( + "fmt" + "reflect" + + "github.com/brianvoe/gofakeit/v6" + "github.com/gookit/color" + ormcontract "github.com/goravel/framework/contracts/database/orm" +) + +type FactoryImpl struct { + model any // model to generate + count int // number of models to generate + facker *gofakeit.Faker // faker instance +} + +func NewFactoryImpl() *FactoryImpl { + return &FactoryImpl{ + count: 1, + facker: gofakeit.New(0), + } +} + +func (f *FactoryImpl) New(attributes ...map[string]any) ormcontract.Factory { + return f.NewInstance(attributes...).Configure() +} + +func (f *FactoryImpl) Times(count int) ormcontract.Factory { + return f.New().Count(count) +} + +func (f *FactoryImpl) Configure() ormcontract.Factory { + return f +} + +func (f *FactoryImpl) Raw() any { + return nil +} + +func (f *FactoryImpl) CreateOne() error { + return nil +} + +func (f *FactoryImpl) CreateOneQuietly() error { + return nil +} + +func (f *FactoryImpl) CreateMany() error { + return nil +} + +func (f *FactoryImpl) CreateManyQuietly() error { + return nil +} + +func (f *FactoryImpl) Create() error { + color.Redf("Create %v", f.model) + return nil +} + +func (f *FactoryImpl) CreateQuietly() error { + return nil +} + +func (f *FactoryImpl) Store() error { + return nil +} + +func (f *FactoryImpl) MakeOne() ormcontract.Factory { + return nil +} + +func (f *FactoryImpl) Make() ormcontract.Factory { + return nil +} + +func (f *FactoryImpl) MakeInstance() ormcontract.Factory { + return nil +} + +func (f *FactoryImpl) GetExpandedAttributes() map[string]any { + return nil +} + +func (f *FactoryImpl) GetRawAttributes() any { + modelFactoryMethod := reflect.ValueOf(f.model).MethodByName("Factory") + + if modelFactoryMethod.IsValid() { + factoryResult := modelFactoryMethod.Call(nil) + if len(factoryResult) > 0 { + factoryInstance, ok := factoryResult[0].Interface().(ormcontract.Factory) + if ok { + definitionMethod := reflect.ValueOf(factoryInstance).MethodByName("Definition") + if definitionMethod.IsValid() { + definitionResult := definitionMethod.Call(nil) + if len(definitionResult) > 0 { + definition := definitionResult[0].Interface() + fmt.Printf("%#v\n", definition) // Print the definition in a human-readable format + return definition + } + } + } + } + } + + definition := f.Definition() + return definition +} + +func (f *FactoryImpl) Faker() *gofakeit.Faker { // Adjust this line to retrieve the Faker instance from your DI container + // return gofakeit.New(0) + return f.facker +} + +func (f *FactoryImpl) ExpandAttributes(definition map[string]interface{}) map[string]interface{} { + expandedAttributes := make(map[string]interface{}) + + for key, attribute := range definition { + switch attr := attribute.(type) { + case func(map[string]interface{}) interface{}: + // Evaluate the callable attribute + expandedAttribute := attr(definition) + expandedAttributes[key] = expandedAttribute + default: + expandedAttributes[key] = attr + } + } + + return expandedAttributes +} + +func (f *FactoryImpl) Set() ormcontract.Factory { + return f +} + +func (f *FactoryImpl) Count(count int) ormcontract.Factory { + return f.NewInstance(map[string]any{"count": count}) +} + +func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Factory { + instance := &FactoryImpl{ + count: f.count, + model: f.model, + } + + if len(attributes) > 0 { + attr := attributes[0] + if count, ok := attr["count"].(int); ok { + instance.count = count + } + if model, ok := attr["model"]; ok { + instance.model = model + } + } + + return instance +} + +func (f *FactoryImpl) Model(value any) ormcontract.Factory { + return f.NewInstance(map[string]any{"model": value}) +} + +func (f *FactoryImpl) Definition() any { + return nil +} diff --git a/database/orm.go b/database/orm.go index 383b4f935..f1651f92a 100644 --- a/database/orm.go +++ b/database/orm.go @@ -72,6 +72,10 @@ func (r *OrmImpl) Query() ormcontract.Query { return r.query } +func (r *OrmImpl) Factory() ormcontract.Factory { + return databasegorm.NewFactoryImpl() +} + func (r *OrmImpl) Observe(model any, observer ormcontract.Observer) { orm.Observers = append(orm.Observers, orm.Observer{ Model: model, From 722d446b955dfbaaabe6ec601fd1538d506adb1b Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Fri, 7 Jul 2023 18:38:19 +0530 Subject: [PATCH 03/17] add query in factory --- contracts/database/factory/factory.go | 5 +++ contracts/database/orm/factory.go | 5 ++- database/console/factory_make_command.go | 7 ++-- database/gorm/factory.go | 52 ++++++++++++++---------- database/orm.go | 2 +- go.mod | 1 + 6 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 contracts/database/factory/factory.go diff --git a/contracts/database/factory/factory.go b/contracts/database/factory/factory.go new file mode 100644 index 000000000..8d288d643 --- /dev/null +++ b/contracts/database/factory/factory.go @@ -0,0 +1,5 @@ +package factory + +type Factory interface { + Definition() any +} diff --git a/contracts/database/orm/factory.go b/contracts/database/orm/factory.go index 57174cbf1..33202d25c 100644 --- a/contracts/database/orm/factory.go +++ b/contracts/database/orm/factory.go @@ -1,10 +1,11 @@ package orm -import "github.com/brianvoe/gofakeit/v6" +import ( + "github.com/brianvoe/gofakeit/v6" +) //go:generate mockery --name=Factory type Factory interface { - Definition() any New(attributes ...map[string]any) Factory Times(count int) Factory Count(count int) Factory diff --git a/database/console/factory_make_command.go b/database/console/factory_make_command.go index a650c521b..19a460b7d 100644 --- a/database/console/factory_make_command.go +++ b/database/console/factory_make_command.go @@ -61,7 +61,7 @@ func (receiver *FactoryMakeCommand) getStub() string { // populateStub Populate the place-holders in the command stub. func (receiver *FactoryMakeCommand) populateStub(stub string, name string) string { - modelName, packageName, _ := receiver.parseName(name) + modelName, packageName, _ := parseName(name, "factories") stub = strings.ReplaceAll(stub, "DummyFactory", str.Case2Camel(modelName)) stub = strings.ReplaceAll(stub, "DummyPackage", packageName) @@ -73,20 +73,19 @@ func (receiver *FactoryMakeCommand) populateStub(stub string, name string) strin func (receiver *FactoryMakeCommand) getPath(name string) string { pwd, _ := os.Getwd() - modelName, _, folderPath := receiver.parseName(name) + modelName, _, folderPath := parseName(name, "factories") return filepath.Join(pwd, "database", "factories", folderPath, str.Camel2Case(modelName)+".go") } // parseName Parse the name to get the model name, package name and folder path. -func (receiver *FactoryMakeCommand) parseName(name string) (string, string, string) { +func parseName(name string, packageName string) (string, string, string) { name = strings.TrimSuffix(name, ".go") segments := strings.Split(name, "/") modelName := segments[len(segments)-1] - packageName := "factories" folderPath := "" if len(segments) > 1 { diff --git a/database/gorm/factory.go b/database/gorm/factory.go index f3218291c..a70d663a0 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -1,24 +1,26 @@ package gorm import ( - "fmt" "reflect" "github.com/brianvoe/gofakeit/v6" "github.com/gookit/color" + + "github.com/goravel/framework/contracts/database/factory" ormcontract "github.com/goravel/framework/contracts/database/orm" ) type FactoryImpl struct { - model any // model to generate - count int // number of models to generate - facker *gofakeit.Faker // faker instance + model any // model to generate + count int // number of models to generate + faker *gofakeit.Faker // faker instance + query ormcontract.Query // query instance } -func NewFactoryImpl() *FactoryImpl { +func NewFactoryImpl(query ormcontract.Query) *FactoryImpl { return &FactoryImpl{ - count: 1, - facker: gofakeit.New(0), + faker: gofakeit.New(0), + query: query, } } @@ -47,6 +49,13 @@ func (f *FactoryImpl) CreateOneQuietly() error { } func (f *FactoryImpl) CreateMany() error { + for i := 0; i < f.count; i++ { + color.Cyanln(f.GetRawAttributes()) + //err := f.query.Create(f.GenerateOne()) + //if err != nil { + // return err + //} + } return nil } @@ -55,8 +64,7 @@ func (f *FactoryImpl) CreateManyQuietly() error { } func (f *FactoryImpl) Create() error { - color.Redf("Create %v", f.model) - return nil + return f.query.Model(f.model).Create(f.GetRawAttributes()) } func (f *FactoryImpl) CreateQuietly() error { @@ -89,28 +97,24 @@ func (f *FactoryImpl) GetRawAttributes() any { if modelFactoryMethod.IsValid() { factoryResult := modelFactoryMethod.Call(nil) if len(factoryResult) > 0 { - factoryInstance, ok := factoryResult[0].Interface().(ormcontract.Factory) + factoryInstance, ok := factoryResult[0].Interface().(factory.Factory) if ok { definitionMethod := reflect.ValueOf(factoryInstance).MethodByName("Definition") if definitionMethod.IsValid() { definitionResult := definitionMethod.Call(nil) if len(definitionResult) > 0 { - definition := definitionResult[0].Interface() - fmt.Printf("%#v\n", definition) // Print the definition in a human-readable format + definition := definitionResult[0].Interface() // Print the definition in a human-readable format return definition } } } } } - - definition := f.Definition() - return definition + return nil } -func (f *FactoryImpl) Faker() *gofakeit.Faker { // Adjust this line to retrieve the Faker instance from your DI container - // return gofakeit.New(0) - return f.facker +func (f *FactoryImpl) Faker() *gofakeit.Faker { + return f.faker } func (f *FactoryImpl) ExpandAttributes(definition map[string]interface{}) map[string]interface{} { @@ -142,6 +146,8 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact instance := &FactoryImpl{ count: f.count, model: f.model, + query: f.query, + faker: f.faker, } if len(attributes) > 0 { @@ -152,6 +158,12 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact if model, ok := attr["model"]; ok { instance.model = model } + if faker, ok := attr["faker"]; ok { + instance.faker = faker.(*gofakeit.Faker) + } + if query, ok := attr["query"]; ok { + instance.query = query.(ormcontract.Query) + } } return instance @@ -160,7 +172,3 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact func (f *FactoryImpl) Model(value any) ormcontract.Factory { return f.NewInstance(map[string]any{"model": value}) } - -func (f *FactoryImpl) Definition() any { - return nil -} diff --git a/database/orm.go b/database/orm.go index f1651f92a..b81d8d50d 100644 --- a/database/orm.go +++ b/database/orm.go @@ -73,7 +73,7 @@ func (r *OrmImpl) Query() ormcontract.Query { } func (r *OrmImpl) Factory() ormcontract.Factory { - return databasegorm.NewFactoryImpl() + return databasegorm.NewFactoryImpl(r.query) } func (r *OrmImpl) Observe(model any, observer ormcontract.Observer) { diff --git a/go.mod b/go.mod index a975a5454..9e93843ce 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae // indirect github.com/aws/aws-sdk-go v1.37.16 // indirect + github.com/brianvoe/gofakeit/v6 v6.23.0 github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect From b9438b2fd6043815129fbd695da6eb18302d1393 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sat, 8 Jul 2023 12:44:04 +0530 Subject: [PATCH 04/17] implement factory interface --- contracts/database/orm/factory.go | 19 ------ database/gorm/factory.go | 107 +++++++----------------------- database/orm_test.go | 4 ++ 3 files changed, 29 insertions(+), 101 deletions(-) diff --git a/contracts/database/orm/factory.go b/contracts/database/orm/factory.go index 33202d25c..dcb81d182 100644 --- a/contracts/database/orm/factory.go +++ b/contracts/database/orm/factory.go @@ -1,31 +1,12 @@ package orm -import ( - "github.com/brianvoe/gofakeit/v6" -) - //go:generate mockery --name=Factory type Factory interface { - New(attributes ...map[string]any) Factory - Times(count int) Factory Count(count int) Factory - Configure() Factory Raw() any - CreateOne() error - CreateOneQuietly() error - CreateMany() error - CreateManyQuietly() error Create() error CreateQuietly() error - Store() error - MakeOne() Factory Make() Factory - MakeInstance() Factory - GetExpandedAttributes() map[string]any - GetRawAttributes() any - Faker() *gofakeit.Faker - ExpandAttributes(definition map[string]interface{}) map[string]interface{} - Set() Factory Model(value any) Factory NewInstance(attributes ...map[string]any) Factory } diff --git a/database/gorm/factory.go b/database/gorm/factory.go index a70d663a0..614fb02e0 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -4,15 +4,13 @@ import ( "reflect" "github.com/brianvoe/gofakeit/v6" - "github.com/gookit/color" - "github.com/goravel/framework/contracts/database/factory" ormcontract "github.com/goravel/framework/contracts/database/orm" ) type FactoryImpl struct { model any // model to generate - count int // number of models to generate + count *int // number of models to generate faker *gofakeit.Faker // faker instance query ormcontract.Query // query instance } @@ -24,74 +22,42 @@ func NewFactoryImpl(query ormcontract.Query) *FactoryImpl { } } -func (f *FactoryImpl) New(attributes ...map[string]any) ormcontract.Factory { - return f.NewInstance(attributes...).Configure() -} - -func (f *FactoryImpl) Times(count int) ormcontract.Factory { - return f.New().Count(count) -} - -func (f *FactoryImpl) Configure() ormcontract.Factory { - return f +// Count Specify the number of models you wish to create / make. +func (f *FactoryImpl) Count(count int) ormcontract.Factory { + return f.NewInstance(map[string]any{"count": count}) } +// Raw Get a raw attribute array for the model's fields. func (f *FactoryImpl) Raw() any { - return nil -} - -func (f *FactoryImpl) CreateOne() error { - return nil -} - -func (f *FactoryImpl) CreateOneQuietly() error { - return nil -} - -func (f *FactoryImpl) CreateMany() error { - for i := 0; i < f.count; i++ { - color.Cyanln(f.GetRawAttributes()) - //err := f.query.Create(f.GenerateOne()) - //if err != nil { - // return err - //} + if f.count == nil { + return f.getRawAttributes() } - return nil -} - -func (f *FactoryImpl) CreateManyQuietly() error { - return nil + result := make([]map[string]any, *f.count) + for i := 0; i < *f.count; i++ { + item := f.getRawAttributes() + if itemMap, ok := item.(map[string]any); ok { + result[i] = itemMap + } + } + return result } +// Create a model and persist it in the database. func (f *FactoryImpl) Create() error { - return f.query.Model(f.model).Create(f.GetRawAttributes()) + return f.query.Model(f.model).Create(f.Raw()) } +// CreateQuietly create a model and persist it in the database without firing any events. func (f *FactoryImpl) CreateQuietly() error { - return nil -} - -func (f *FactoryImpl) Store() error { - return nil -} - -func (f *FactoryImpl) MakeOne() ormcontract.Factory { - return nil + return f.query.Model(f.model).WithoutEvents().Create(f.Raw()) } +// Make a model instance that's not persisted in the database. func (f *FactoryImpl) Make() ormcontract.Factory { return nil } -func (f *FactoryImpl) MakeInstance() ormcontract.Factory { - return nil -} - -func (f *FactoryImpl) GetExpandedAttributes() map[string]any { - return nil -} - -func (f *FactoryImpl) GetRawAttributes() any { +func (f *FactoryImpl) getRawAttributes() any { modelFactoryMethod := reflect.ValueOf(f.model).MethodByName("Factory") if modelFactoryMethod.IsValid() { @@ -103,7 +69,7 @@ func (f *FactoryImpl) GetRawAttributes() any { if definitionMethod.IsValid() { definitionResult := definitionMethod.Call(nil) if len(definitionResult) > 0 { - definition := definitionResult[0].Interface() // Print the definition in a human-readable format + definition := definitionResult[0].Interface() return definition } } @@ -117,31 +83,7 @@ func (f *FactoryImpl) Faker() *gofakeit.Faker { return f.faker } -func (f *FactoryImpl) ExpandAttributes(definition map[string]interface{}) map[string]interface{} { - expandedAttributes := make(map[string]interface{}) - - for key, attribute := range definition { - switch attr := attribute.(type) { - case func(map[string]interface{}) interface{}: - // Evaluate the callable attribute - expandedAttribute := attr(definition) - expandedAttributes[key] = expandedAttribute - default: - expandedAttributes[key] = attr - } - } - - return expandedAttributes -} - -func (f *FactoryImpl) Set() ormcontract.Factory { - return f -} - -func (f *FactoryImpl) Count(count int) ormcontract.Factory { - return f.NewInstance(map[string]any{"count": count}) -} - +// NewInstance create a new factory instance. func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Factory { instance := &FactoryImpl{ count: f.count, @@ -153,7 +95,7 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact if len(attributes) > 0 { attr := attributes[0] if count, ok := attr["count"].(int); ok { - instance.count = count + instance.count = &count } if model, ok := attr["model"]; ok { instance.model = model @@ -169,6 +111,7 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact return instance } +// Model Set the model's attributes. func (f *FactoryImpl) Model(value any) ormcontract.Factory { return f.NewInstance(map[string]any{"model": value}) } diff --git a/database/orm_test.go b/database/orm_test.go index 3f462ab64..c92503fd7 100644 --- a/database/orm_test.go +++ b/database/orm_test.go @@ -138,6 +138,10 @@ func (s *OrmSuite) TestQuery() { } } +func (s *OrmSuite) TestFactory() { + s.NotNil(s.orm.Factory()) +} + func (s *OrmSuite) TestObserve() { s.orm.Observe(User{}, &UserObserver{}) From ccfe5ca369e4af4c75a1846e6d8f29502d9afc2e Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sun, 9 Jul 2023 15:13:22 +0530 Subject: [PATCH 05/17] implement `make` method --- contracts/database/orm/factory.go | 11 ++-- database/gorm/factory.go | 103 +++++++++++++++++++----------- 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/contracts/database/orm/factory.go b/contracts/database/orm/factory.go index dcb81d182..d37d4b450 100644 --- a/contracts/database/orm/factory.go +++ b/contracts/database/orm/factory.go @@ -2,11 +2,8 @@ package orm //go:generate mockery --name=Factory type Factory interface { - Count(count int) Factory - Raw() any - Create() error - CreateQuietly() error - Make() Factory - Model(value any) Factory - NewInstance(attributes ...map[string]any) Factory + Times(count int) Factory + Create(value any) error + CreateQuietly(value any) error + Make(value any) error } diff --git a/database/gorm/factory.go b/database/gorm/factory.go index 614fb02e0..829e15c82 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -1,6 +1,7 @@ package gorm import ( + "github.com/mitchellh/mapstructure" "reflect" "github.com/brianvoe/gofakeit/v6" @@ -22,44 +23,57 @@ func NewFactoryImpl(query ormcontract.Query) *FactoryImpl { } } -// Count Specify the number of models you wish to create / make. -func (f *FactoryImpl) Count(count int) ormcontract.Factory { - return f.NewInstance(map[string]any{"count": count}) -} - -// Raw Get a raw attribute array for the model's fields. -func (f *FactoryImpl) Raw() any { - if f.count == nil { - return f.getRawAttributes() - } - result := make([]map[string]any, *f.count) - for i := 0; i < *f.count; i++ { - item := f.getRawAttributes() - if itemMap, ok := item.(map[string]any); ok { - result[i] = itemMap - } - } - return result +// Times Specify the number of models you wish to create / make. +func (f *FactoryImpl) Times(count int) ormcontract.Factory { + return f.newInstance(map[string]any{"count": count}) } // Create a model and persist it in the database. -func (f *FactoryImpl) Create() error { - return f.query.Model(f.model).Create(f.Raw()) +func (f *FactoryImpl) Create(value any) error { + if err := f.Make(value); err != nil { + return err + } + return f.query.Create(value) } // CreateQuietly create a model and persist it in the database without firing any events. -func (f *FactoryImpl) CreateQuietly() error { - return f.query.Model(f.model).WithoutEvents().Create(f.Raw()) +func (f *FactoryImpl) CreateQuietly(value any) error { + if err := f.Make(value); err != nil { + return err + } + return f.query.WithoutEvents().Create(value) } // Make a model instance that's not persisted in the database. -func (f *FactoryImpl) Make() ormcontract.Factory { - return nil +func (f *FactoryImpl) Make(value any) error { + reflectValue := reflect.Indirect(reflect.ValueOf(value)) + switch reflectValue.Kind() { + case reflect.Array, reflect.Slice: + count := 1 + if f.count != nil { + count = *f.count + } + for i := 0; i < count; i++ { + elemValue := reflect.New(reflectValue.Type().Elem()).Interface() + attributes := f.getRawAttributes(elemValue) + if err := mapstructure.Decode(attributes, elemValue); err != nil { + return err + } + reflectValue = reflect.Append(reflectValue, reflect.ValueOf(elemValue).Elem()) + } + reflect.ValueOf(value).Elem().Set(reflectValue) + return nil + default: + attributes := f.getRawAttributes(value) + if err := mapstructure.Decode(attributes, value); err != nil { + return err + } + return nil + } } -func (f *FactoryImpl) getRawAttributes() any { - modelFactoryMethod := reflect.ValueOf(f.model).MethodByName("Factory") - +func (f *FactoryImpl) getRawAttributes(value any) any { + modelFactoryMethod := reflect.ValueOf(value).MethodByName("Factory") if modelFactoryMethod.IsValid() { factoryResult := modelFactoryMethod.Call(nil) if len(factoryResult) > 0 { @@ -69,8 +83,7 @@ func (f *FactoryImpl) getRawAttributes() any { if definitionMethod.IsValid() { definitionResult := definitionMethod.Call(nil) if len(definitionResult) > 0 { - definition := definitionResult[0].Interface() - return definition + return definitionResult[0].Interface() } } } @@ -79,12 +92,8 @@ func (f *FactoryImpl) getRawAttributes() any { return nil } -func (f *FactoryImpl) Faker() *gofakeit.Faker { - return f.faker -} - -// NewInstance create a new factory instance. -func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Factory { +// newInstance create a new factory instance. +func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Factory { instance := &FactoryImpl{ count: f.count, model: f.model, @@ -111,7 +120,29 @@ func (f *FactoryImpl) NewInstance(attributes ...map[string]any) ormcontract.Fact return instance } +// TODO: Method below this will be removed in final release + // Model Set the model's attributes. func (f *FactoryImpl) Model(value any) ormcontract.Factory { - return f.NewInstance(map[string]any{"model": value}) + return f.newInstance(map[string]any{"model": value}) +} + +func (f *FactoryImpl) Faker() *gofakeit.Faker { + return f.faker +} + +// Raw Get a raw attribute array for the model's fields. +func (f *FactoryImpl) Raw() any { + //if f.count == nil { + // return f.getRawAttributes() + //} + //result := make([]map[string]any, *f.count) + //for i := 0; i < *f.count; i++ { + // item := f.getRawAttributes() + // if itemMap, ok := item.(map[string]any); ok { + // result[i] = itemMap + // } + //} + //return result + return nil } From 64d0323731415cacac35ceb1e44c9078348d2ddf Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Wed, 12 Jul 2023 21:52:28 +0530 Subject: [PATCH 06/17] remove unnecessary methods --- database/gorm/factory.go | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/database/gorm/factory.go b/database/gorm/factory.go index 829e15c82..adc51ea0f 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -1,10 +1,12 @@ package gorm import ( - "github.com/mitchellh/mapstructure" + "errors" "reflect" "github.com/brianvoe/gofakeit/v6" + "github.com/mitchellh/mapstructure" + "github.com/goravel/framework/contracts/database/factory" ormcontract "github.com/goravel/framework/contracts/database/orm" ) @@ -56,6 +58,9 @@ func (f *FactoryImpl) Make(value any) error { for i := 0; i < count; i++ { elemValue := reflect.New(reflectValue.Type().Elem()).Interface() attributes := f.getRawAttributes(elemValue) + if attributes == nil { + return errors.New("failed to get raw attributes") + } if err := mapstructure.Decode(attributes, elemValue); err != nil { return err } @@ -65,6 +70,9 @@ func (f *FactoryImpl) Make(value any) error { return nil default: attributes := f.getRawAttributes(value) + if attributes == nil { + return errors.New("failed to get raw attributes") + } if err := mapstructure.Decode(attributes, value); err != nil { return err } @@ -119,30 +127,3 @@ func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Fact return instance } - -// TODO: Method below this will be removed in final release - -// Model Set the model's attributes. -func (f *FactoryImpl) Model(value any) ormcontract.Factory { - return f.newInstance(map[string]any{"model": value}) -} - -func (f *FactoryImpl) Faker() *gofakeit.Faker { - return f.faker -} - -// Raw Get a raw attribute array for the model's fields. -func (f *FactoryImpl) Raw() any { - //if f.count == nil { - // return f.getRawAttributes() - //} - //result := make([]map[string]any, *f.count) - //for i := 0; i < *f.count; i++ { - // item := f.getRawAttributes() - // if itemMap, ok := item.(map[string]any); ok { - // result[i] = itemMap - // } - //} - //return result - return nil -} From 6812f356ba4363fb45b31e5bf1de39a2fc8d0e46 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Wed, 12 Jul 2023 22:11:12 +0530 Subject: [PATCH 07/17] generate mockery for factory contract --- contracts/database/orm/mocks/Factory.go | 85 +++++++++++++++++++++++++ contracts/database/orm/mocks/Orm.go | 32 ++++++++-- 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 contracts/database/orm/mocks/Factory.go diff --git a/contracts/database/orm/mocks/Factory.go b/contracts/database/orm/mocks/Factory.go new file mode 100644 index 000000000..a1c0bb8cc --- /dev/null +++ b/contracts/database/orm/mocks/Factory.go @@ -0,0 +1,85 @@ +// Code generated by mockery v2.30.1. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/goravel/framework/contracts/database/orm" + mock "github.com/stretchr/testify/mock" +) + +// Factory is an autogenerated mock type for the Factory type +type Factory struct { + mock.Mock +} + +// Create provides a mock function with given fields: value +func (_m *Factory) Create(value interface{}) error { + ret := _m.Called(value) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(value) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateQuietly provides a mock function with given fields: value +func (_m *Factory) CreateQuietly(value interface{}) error { + ret := _m.Called(value) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(value) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Make provides a mock function with given fields: value +func (_m *Factory) Make(value interface{}) error { + ret := _m.Called(value) + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(value) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Times provides a mock function with given fields: count +func (_m *Factory) Times(count int) orm.Factory { + ret := _m.Called(count) + + var r0 orm.Factory + if rf, ok := ret.Get(0).(func(int) orm.Factory); ok { + r0 = rf(count) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(orm.Factory) + } + } + + return r0 +} + +// NewFactory creates a new instance of Factory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFactory(t interface { + mock.TestingT + Cleanup(func()) +}) *Factory { + mock := &Factory{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/contracts/database/orm/mocks/Orm.go b/contracts/database/orm/mocks/Orm.go index 4fb62d3d5..a0c653267 100644 --- a/contracts/database/orm/mocks/Orm.go +++ b/contracts/database/orm/mocks/Orm.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.30.1. DO NOT EDIT. package mocks @@ -37,6 +37,10 @@ func (_m *Orm) DB() (*sql.DB, error) { ret := _m.Called() var r0 *sql.DB + var r1 error + if rf, ok := ret.Get(0).(func() (*sql.DB, error)); ok { + return rf() + } if rf, ok := ret.Get(0).(func() *sql.DB); ok { r0 = rf() } else { @@ -45,7 +49,6 @@ func (_m *Orm) DB() (*sql.DB, error) { } } - var r1 error if rf, ok := ret.Get(1).(func() error); ok { r1 = rf() } else { @@ -55,6 +58,22 @@ func (_m *Orm) DB() (*sql.DB, error) { return r0, r1 } +// Factory provides a mock function with given fields: +func (_m *Orm) Factory() orm.Factory { + ret := _m.Called() + + var r0 orm.Factory + if rf, ok := ret.Get(0).(func() orm.Factory); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(orm.Factory) + } + } + + return r0 +} + // Observe provides a mock function with given fields: model, observer func (_m *Orm) Observe(model interface{}, observer orm.Observer) { _m.Called(model, observer) @@ -106,13 +125,12 @@ func (_m *Orm) WithContext(ctx context.Context) orm.Orm { return r0 } -type mockConstructorTestingTNewOrm interface { +// NewOrm creates a new instance of Orm. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrm(t interface { mock.TestingT Cleanup(func()) -} - -// NewOrm creates a new instance of Orm. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewOrm(t mockConstructorTestingTNewOrm) *Orm { +}) *Orm { mock := &Orm{} mock.Mock.Test(t) From 17656e1749863a729322fad39319f82affe4ee32 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Wed, 12 Jul 2023 22:47:23 +0530 Subject: [PATCH 08/17] add test for orm's factory method --- database/orm_test.go | 4 ++++ go.sum | 2 ++ 2 files changed, 6 insertions(+) diff --git a/database/orm_test.go b/database/orm_test.go index c92503fd7..c593710ad 100644 --- a/database/orm_test.go +++ b/database/orm_test.go @@ -140,6 +140,10 @@ func (s *OrmSuite) TestQuery() { func (s *OrmSuite) TestFactory() { s.NotNil(s.orm.Factory()) + + for _, connection := range connections { + s.NotNil(s.orm.Connection(connection.String()).Factory()) + } } func (s *OrmSuite) TestObserve() { diff --git a/go.sum b/go.sum index 4be57febd..f071f8be3 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ github.com/aws/aws-sdk-go v1.37.16 h1:Q4YOP2s00NpB9wfmTDZArdcLRuG9ijbnoAwTW3ivle github.com/aws/aws-sdk-go v1.37.16/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/brianvoe/gofakeit/v6 v6.23.0 h1:pgVhyWpYq4e0GEVCh2gdZnS/nBX+8SnyTBliHg5xjks= +github.com/brianvoe/gofakeit/v6 v6.23.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM= github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= From e881b63f584bb988f84c2445cb2d155309892618 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Fri, 14 Jul 2023 14:09:11 +0530 Subject: [PATCH 09/17] add tests for factory --- database/gorm/factory_test.go | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 database/gorm/factory_test.go diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go new file mode 100644 index 000000000..6e0c99a2c --- /dev/null +++ b/database/gorm/factory_test.go @@ -0,0 +1,83 @@ +package gorm + +import ( + "reflect" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/contracts/database/factory" + ormmocks "github.com/goravel/framework/contracts/database/orm/mocks" + "github.com/goravel/framework/database/orm" +) + +type Person struct { + orm.Model + orm.SoftDeletes + Name string + Avatar string +} + +func (p *Person) Factory() factory.Factory { + return &PersonFactory{} +} + +type PersonFactory struct { +} + +func (p *PersonFactory) Definition() any { + faker := gofakeit.New(0) + return map[string]interface{}{ + "name": faker.Name(), + "avatar": faker.Email(), + "created_at": faker.Date(), + "updated_at": faker.Date(), + } +} + +type FactoryTestSuite struct { + suite.Suite + factory *FactoryImpl + mockQuery *ormmocks.Query +} + +func TestFactoryTestSuite(t *testing.T) { + suite.Run(t, new(FactoryTestSuite)) +} + +func (s *FactoryTestSuite) SetupTest() { + s.mockQuery = ormmocks.NewQuery(s.T()) + s.factory = NewFactoryImpl(s.mockQuery) +} + +func (s *FactoryTestSuite) TestTimes() { + factInstance := s.factory.Times(2) + s.NotNil(factInstance) + s.NotNil(reflect.TypeOf(factInstance) == reflect.TypeOf((*FactoryImpl)(nil))) + s.mockQuery.AssertExpectations(s.T()) +} + +func (s *FactoryTestSuite) TestCreate() { + var person []Person + s.mockQuery.On("Create", &person).Return(nil).Once() + s.Nil(s.factory.Create(&person)) + s.True(len(person) > 0) + s.True(reflect.TypeOf(person) == reflect.TypeOf([]Person{})) + s.mockQuery.AssertExpectations(s.T()) + + var person1 Person + s.mockQuery.On("Create", &person1).Return(nil).Once() + s.Nil(s.factory.Create(&person1)) + s.NotNil(person1) + s.True(reflect.TypeOf(person1) == reflect.TypeOf(Person{})) + s.mockQuery.AssertExpectations(s.T()) +} + +func (s *FactoryTestSuite) TestMake() { + var person []Person + s.Nil(s.factory.Make(&person)) + s.True(len(person) > 0) + s.True(reflect.TypeOf(person) == reflect.TypeOf([]Person{})) + s.mockQuery.AssertExpectations(s.T()) +} From e677c2cc811f292645f2fa17be06988f6739d660 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sat, 15 Jul 2023 22:30:17 +0530 Subject: [PATCH 10/17] clean-up --- database/gorm/factory.go | 5 ---- database/gorm/factory_test.go | 36 +++++++++++++------------- database/gorm/test_utils.go | 48 ++++++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/database/gorm/factory.go b/database/gorm/factory.go index adc51ea0f..82405393f 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -12,7 +12,6 @@ import ( ) type FactoryImpl struct { - model any // model to generate count *int // number of models to generate faker *gofakeit.Faker // faker instance query ormcontract.Query // query instance @@ -104,7 +103,6 @@ func (f *FactoryImpl) getRawAttributes(value any) any { func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Factory { instance := &FactoryImpl{ count: f.count, - model: f.model, query: f.query, faker: f.faker, } @@ -114,9 +112,6 @@ func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Fact if count, ok := attr["count"].(int); ok { instance.count = &count } - if model, ok := attr["model"]; ok { - instance.model = model - } if faker, ok := attr["faker"]; ok { instance.faker = faker.(*gofakeit.Faker) } diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go index 6e0c99a2c..d276963c5 100644 --- a/database/gorm/factory_test.go +++ b/database/gorm/factory_test.go @@ -1,14 +1,14 @@ package gorm import ( - "reflect" + "log" "testing" "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/database/factory" - ormmocks "github.com/goravel/framework/contracts/database/orm/mocks" + ormcontract "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/database/orm" ) @@ -38,8 +38,8 @@ func (p *PersonFactory) Definition() any { type FactoryTestSuite struct { suite.Suite - factory *FactoryImpl - mockQuery *ormmocks.Query + factory ormcontract.Factory + query ormcontract.Query } func TestFactoryTestSuite(t *testing.T) { @@ -47,37 +47,39 @@ func TestFactoryTestSuite(t *testing.T) { } func (s *FactoryTestSuite) SetupTest() { - s.mockQuery = ormmocks.NewQuery(s.T()) - s.factory = NewFactoryImpl(s.mockQuery) + mysqlDocker := NewMysqlDocker() + _, _, mysqlQuery, err := mysqlDocker.New() + if err != nil { + log.Fatalf("Init mysql error: %s", err) + } + s.query = mysqlQuery + s.factory = NewFactoryImpl(s.query) } func (s *FactoryTestSuite) TestTimes() { + var person []Person factInstance := s.factory.Times(2) - s.NotNil(factInstance) - s.NotNil(reflect.TypeOf(factInstance) == reflect.TypeOf((*FactoryImpl)(nil))) - s.mockQuery.AssertExpectations(s.T()) + s.Nil(factInstance.Make(&person)) + s.True(len(person) == 2) + s.True(len(person[0].Name) > 0) + s.True(len(person[1].Name) > 0) } func (s *FactoryTestSuite) TestCreate() { var person []Person - s.mockQuery.On("Create", &person).Return(nil).Once() s.Nil(s.factory.Create(&person)) s.True(len(person) > 0) - s.True(reflect.TypeOf(person) == reflect.TypeOf([]Person{})) - s.mockQuery.AssertExpectations(s.T()) + s.True(person[0].ID > 0) var person1 Person - s.mockQuery.On("Create", &person1).Return(nil).Once() s.Nil(s.factory.Create(&person1)) s.NotNil(person1) - s.True(reflect.TypeOf(person1) == reflect.TypeOf(Person{})) - s.mockQuery.AssertExpectations(s.T()) + s.True(person1.ID > 0) } func (s *FactoryTestSuite) TestMake() { var person []Person s.Nil(s.factory.Make(&person)) s.True(len(person) > 0) - s.True(reflect.TypeOf(person) == reflect.TypeOf([]Person{})) - s.mockQuery.AssertExpectations(s.T()) + s.True(len(person[0].Name) > 0) } diff --git a/database/gorm/test_utils.go b/database/gorm/test_utils.go index 9c67fef70..1cd2f59f5 100644 --- a/database/gorm/test_utils.go +++ b/database/gorm/test_utils.go @@ -592,7 +592,11 @@ type Table struct { } func (r Table) Create(driver orm.Driver, db orm.Query) error { - _, err := db.Exec(r.createUserTable(driver)) + _, err := db.Exec(r.createPersonTable(driver)) + if err != nil { + return err + } + _, err = db.Exec(r.createUserTable(driver)) if err != nil { return err } @@ -636,6 +640,48 @@ func (r Table) CreateWithPrefixAndSingular(driver orm.Driver, db orm.Query) erro return nil } +func (r Table) createPersonTable(driver orm.Driver) string { + switch driver { + case orm.DriverMysql: + return ` +CREATE TABLE people ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + name varchar(255) NOT NULL, + avatar varchar(255) NOT NULL, + created_at datetime(3) NOT NULL, + updated_at datetime(3) NOT NULL, + deleted_at datetime(3) DEFAULT NULL, + PRIMARY KEY (id), + KEY idx_people_created_at (created_at), + KEY idx_people_updated_at (updated_at) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; +` + case orm.DriverPostgresql: + return ` +CREATE TABLE people ( + id SERIAL PRIMARY KEY NOT NULL, + name varchar(255) NOT NULL, + avatar varchar(255) NOT NULL, + created_at timestamp NOT NULL, + updated_at timestamp NOT NULL, + deleted_at timestamp DEFAULT NULL +); +` + case orm.DriverSqlite: + return ` +CREATE TABLE people ( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar(255) NOT NULL, + avatar varchar(255) NOT NULL, + created_at datetime(3) NOT NULL, + updated_at datetime(3) NOT NULL, + deleted_at datetime(3) DEFAULT NULL +); +` + default: + return "" + } +} func (r Table) createUserTable(driver orm.Driver) string { switch driver { From a70b99691fb8c6c5443623515b80e527f2e310bb Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 13:43:22 +0530 Subject: [PATCH 11/17] add test cases for `getRawAttributes` method --- database/gorm/factory.go | 62 +++++++++++--------- database/gorm/factory_test.go | 104 +++++++++++++++++++++++----------- database/gorm/query_test.go | 9 +++ database/gorm/test_utils.go | 48 +--------------- 4 files changed, 114 insertions(+), 109 deletions(-) diff --git a/database/gorm/factory.go b/database/gorm/factory.go index 82405393f..6fb78127e 100644 --- a/database/gorm/factory.go +++ b/database/gorm/factory.go @@ -2,9 +2,9 @@ package gorm import ( "errors" + "fmt" "reflect" - "github.com/brianvoe/gofakeit/v6" "github.com/mitchellh/mapstructure" "github.com/goravel/framework/contracts/database/factory" @@ -13,13 +13,11 @@ import ( type FactoryImpl struct { count *int // number of models to generate - faker *gofakeit.Faker // faker instance query ormcontract.Query // query instance } func NewFactoryImpl(query ormcontract.Query) *FactoryImpl { return &FactoryImpl{ - faker: gofakeit.New(0), query: query, } } @@ -56,7 +54,10 @@ func (f *FactoryImpl) Make(value any) error { } for i := 0; i < count; i++ { elemValue := reflect.New(reflectValue.Type().Elem()).Interface() - attributes := f.getRawAttributes(elemValue) + attributes, err := f.getRawAttributes(elemValue) + if err != nil { + return err + } if attributes == nil { return errors.New("failed to get raw attributes") } @@ -68,7 +69,10 @@ func (f *FactoryImpl) Make(value any) error { reflect.ValueOf(value).Elem().Set(reflectValue) return nil default: - attributes := f.getRawAttributes(value) + attributes, err := f.getRawAttributes(value) + if err != nil { + return err + } if attributes == nil { return errors.New("failed to get raw attributes") } @@ -79,24 +83,33 @@ func (f *FactoryImpl) Make(value any) error { } } -func (f *FactoryImpl) getRawAttributes(value any) any { +func (f *FactoryImpl) getRawAttributes(value any) (any, error) { modelFactoryMethod := reflect.ValueOf(value).MethodByName("Factory") - if modelFactoryMethod.IsValid() { - factoryResult := modelFactoryMethod.Call(nil) - if len(factoryResult) > 0 { - factoryInstance, ok := factoryResult[0].Interface().(factory.Factory) - if ok { - definitionMethod := reflect.ValueOf(factoryInstance).MethodByName("Definition") - if definitionMethod.IsValid() { - definitionResult := definitionMethod.Call(nil) - if len(definitionResult) > 0 { - return definitionResult[0].Interface() - } - } - } - } + if !modelFactoryMethod.IsValid() { + return nil, errors.New("factory method not found") + } + if !modelFactoryMethod.IsValid() { + return nil, errors.New("factory method not found for value type " + reflect.TypeOf(value).String()) } - return nil + factoryResult := modelFactoryMethod.Call(nil) + if len(factoryResult) == 0 { + return nil, errors.New("factory method returned nothing") + } + factoryInstance, ok := factoryResult[0].Interface().(factory.Factory) + if !ok { + expectedType := reflect.TypeOf((*factory.Factory)(nil)).Elem() + return nil, fmt.Errorf("factory method does not return a factory instance (expected %v)", expectedType) + } + definitionMethod := reflect.ValueOf(factoryInstance).MethodByName("Definition") + if !definitionMethod.IsValid() { + return nil, errors.New("definition method not found in factory instance") + } + definitionResult := definitionMethod.Call(nil) + if len(definitionResult) == 0 { + return nil, errors.New("definition method returned nothing") + } + + return definitionResult[0].Interface(), nil } // newInstance create a new factory instance. @@ -104,7 +117,6 @@ func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Fact instance := &FactoryImpl{ count: f.count, query: f.query, - faker: f.faker, } if len(attributes) > 0 { @@ -112,12 +124,6 @@ func (f *FactoryImpl) newInstance(attributes ...map[string]any) ormcontract.Fact if count, ok := attr["count"].(int); ok { instance.count = &count } - if faker, ok := attr["faker"]; ok { - instance.faker = faker.(*gofakeit.Faker) - } - if query, ok := attr["query"]; ok { - instance.query = query.(ormcontract.Query) - } } return instance diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go index d276963c5..259ae3cb0 100644 --- a/database/gorm/factory_test.go +++ b/database/gorm/factory_test.go @@ -7,26 +7,13 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/suite" - "github.com/goravel/framework/contracts/database/factory" ormcontract "github.com/goravel/framework/contracts/database/orm" - "github.com/goravel/framework/database/orm" ) -type Person struct { - orm.Model - orm.SoftDeletes - Name string - Avatar string +type UserFactory struct { } -func (p *Person) Factory() factory.Factory { - return &PersonFactory{} -} - -type PersonFactory struct { -} - -func (p *PersonFactory) Definition() any { +func (u *UserFactory) Definition() any { faker := gofakeit.New(0) return map[string]interface{}{ "name": faker.Name(), @@ -38,7 +25,7 @@ func (p *PersonFactory) Definition() any { type FactoryTestSuite struct { suite.Suite - factory ormcontract.Factory + factory *FactoryImpl query ormcontract.Query } @@ -57,29 +44,78 @@ func (s *FactoryTestSuite) SetupTest() { } func (s *FactoryTestSuite) TestTimes() { - var person []Person + var user []User factInstance := s.factory.Times(2) - s.Nil(factInstance.Make(&person)) - s.True(len(person) == 2) - s.True(len(person[0].Name) > 0) - s.True(len(person[1].Name) > 0) + s.Nil(factInstance.Make(&user)) + s.True(len(user) == 2) + s.True(len(user[0].Name) > 0) + s.True(len(user[1].Name) > 0) } func (s *FactoryTestSuite) TestCreate() { - var person []Person - s.Nil(s.factory.Create(&person)) - s.True(len(person) > 0) - s.True(person[0].ID > 0) - - var person1 Person - s.Nil(s.factory.Create(&person1)) - s.NotNil(person1) - s.True(person1.ID > 0) + var user []User + s.Nil(s.factory.Create(&user)) + s.True(len(user) == 1) + s.True(user[0].ID > 0) + s.True(len(user[0].Name) > 0) + + var user1 User + s.Nil(s.factory.Create(&user1)) + s.NotNil(user1) + s.True(user1.ID > 0) + + var user3 []User + factInstance := s.factory.Times(2) + s.Nil(factInstance.Create(&user3)) + s.True(len(user3) == 2) + s.True(user3[0].ID > 0) + s.True(user3[1].ID > 0) + s.True(len(user3[0].Name) > 0) + s.True(len(user3[1].Name) > 0) +} + +func (s *FactoryTestSuite) TestCreateQuietly() { + var user []User + s.Nil(s.factory.CreateQuietly(&user)) + s.True(len(user) == 1) + s.True(user[0].ID > 0) + s.True(len(user[0].Name) > 0) + + var user1 User + s.Nil(s.factory.CreateQuietly(&user1)) + s.NotNil(user1) + s.True(user1.ID > 0) + + var user3 []User + factInstance := s.factory.Times(2) + s.Nil(factInstance.CreateQuietly(&user3)) + s.True(len(user3) == 2) + s.True(user3[0].ID > 0) + s.True(user3[1].ID > 0) + s.True(len(user3[0].Name) > 0) + s.True(len(user3[1].Name) > 0) } func (s *FactoryTestSuite) TestMake() { - var person []Person - s.Nil(s.factory.Make(&person)) - s.True(len(person) > 0) - s.True(len(person[0].Name) > 0) + var user []User + s.Nil(s.factory.Make(&user)) + s.True(len(user) == 1) + s.True(len(user[0].Name) > 0) +} + +func (s *FactoryTestSuite) TestGetRawAttributes() { + var author Author + attributes, err := s.factory.getRawAttributes(&author) + s.NotNil(err) + s.Nil(attributes) + + var house House + attributes, err = s.factory.getRawAttributes(&house) + s.NotNil(err) + s.Nil(attributes) + + var user User + attributes, err = s.factory.getRawAttributes(&user) + s.Nil(err) + s.NotNil(attributes) } diff --git a/database/gorm/query_test.go b/database/gorm/query_test.go index 61899f29e..df84f1c8d 100644 --- a/database/gorm/query_test.go +++ b/database/gorm/query_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/suite" _ "gorm.io/driver/postgres" + "github.com/goravel/framework/contracts/database/factory" ormcontract "github.com/goravel/framework/contracts/database/orm" databasedb "github.com/goravel/framework/database/db" "github.com/goravel/framework/database/orm" @@ -35,6 +36,10 @@ type User struct { Roles []*Role `gorm:"many2many:role_user"` } +func (u *User) Factory() factory.Factory { + return &UserFactory{} +} + func (u *User) DispatchesEvents() map[ormcontract.EventType]func(ormcontract.Event) error { return map[ormcontract.EventType]func(ormcontract.Event) error{ ormcontract.EventCreating: func(event ormcontract.Event) error { @@ -287,6 +292,10 @@ type House struct { HouseableType string } +func (h *House) Factory() string { + return "house" +} + type Phone struct { orm.Model Name string diff --git a/database/gorm/test_utils.go b/database/gorm/test_utils.go index 1cd2f59f5..9c67fef70 100644 --- a/database/gorm/test_utils.go +++ b/database/gorm/test_utils.go @@ -592,11 +592,7 @@ type Table struct { } func (r Table) Create(driver orm.Driver, db orm.Query) error { - _, err := db.Exec(r.createPersonTable(driver)) - if err != nil { - return err - } - _, err = db.Exec(r.createUserTable(driver)) + _, err := db.Exec(r.createUserTable(driver)) if err != nil { return err } @@ -640,48 +636,6 @@ func (r Table) CreateWithPrefixAndSingular(driver orm.Driver, db orm.Query) erro return nil } -func (r Table) createPersonTable(driver orm.Driver) string { - switch driver { - case orm.DriverMysql: - return ` -CREATE TABLE people ( - id bigint(20) unsigned NOT NULL AUTO_INCREMENT, - name varchar(255) NOT NULL, - avatar varchar(255) NOT NULL, - created_at datetime(3) NOT NULL, - updated_at datetime(3) NOT NULL, - deleted_at datetime(3) DEFAULT NULL, - PRIMARY KEY (id), - KEY idx_people_created_at (created_at), - KEY idx_people_updated_at (updated_at) -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -` - case orm.DriverPostgresql: - return ` -CREATE TABLE people ( - id SERIAL PRIMARY KEY NOT NULL, - name varchar(255) NOT NULL, - avatar varchar(255) NOT NULL, - created_at timestamp NOT NULL, - updated_at timestamp NOT NULL, - deleted_at timestamp DEFAULT NULL -); -` - case orm.DriverSqlite: - return ` -CREATE TABLE people ( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar(255) NOT NULL, - avatar varchar(255) NOT NULL, - created_at datetime(3) NOT NULL, - updated_at datetime(3) NOT NULL, - deleted_at datetime(3) DEFAULT NULL -); -` - default: - return "" - } -} func (r Table) createUserTable(driver orm.Driver) string { switch driver { From 6c22c83dee999208f5e928b30c4f0907a343b2a9 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 14:32:28 +0530 Subject: [PATCH 12/17] missing commit message (couldn't think of one) --- database/gorm/factory_test.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go index 259ae3cb0..81e00b7c0 100644 --- a/database/gorm/factory_test.go +++ b/database/gorm/factory_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/brianvoe/gofakeit/v6" + "github.com/goravel/framework/support/file" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ormcontract "github.com/goravel/framework/contracts/database/orm" @@ -30,23 +32,26 @@ type FactoryTestSuite struct { } func TestFactoryTestSuite(t *testing.T) { - suite.Run(t, new(FactoryTestSuite)) -} - -func (s *FactoryTestSuite) SetupTest() { mysqlDocker := NewMysqlDocker() - _, _, mysqlQuery, err := mysqlDocker.New() + mysqlPool, mysqlResource, mysqlQuery, err := mysqlDocker.New() if err != nil { log.Fatalf("Init mysql error: %s", err) } - s.query = mysqlQuery + suite.Run(t, &FactoryTestSuite{ + query: mysqlQuery, + }) + + assert.Nil(t, file.Remove(dbDatabase)) + assert.Nil(t, mysqlPool.Purge(mysqlResource)) +} + +func (s *FactoryTestSuite) SetupTest() { s.factory = NewFactoryImpl(s.query) } func (s *FactoryTestSuite) TestTimes() { var user []User - factInstance := s.factory.Times(2) - s.Nil(factInstance.Make(&user)) + s.Nil(s.factory.Times(2).Make(&user)) s.True(len(user) == 2) s.True(len(user[0].Name) > 0) s.True(len(user[1].Name) > 0) @@ -65,8 +70,7 @@ func (s *FactoryTestSuite) TestCreate() { s.True(user1.ID > 0) var user3 []User - factInstance := s.factory.Times(2) - s.Nil(factInstance.Create(&user3)) + s.Nil(s.factory.Times(2).Create(&user3)) s.True(len(user3) == 2) s.True(user3[0].ID > 0) s.True(user3[1].ID > 0) @@ -87,8 +91,7 @@ func (s *FactoryTestSuite) TestCreateQuietly() { s.True(user1.ID > 0) var user3 []User - factInstance := s.factory.Times(2) - s.Nil(factInstance.CreateQuietly(&user3)) + s.Nil(s.factory.Times(2).CreateQuietly(&user3)) s.True(len(user3) == 2) s.True(user3[0].ID > 0) s.True(user3[1].ID > 0) From 55d9f37b03d2bc182849a0c2d7b327064dc6aa7f Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 14:47:56 +0530 Subject: [PATCH 13/17] change name of function in factory stub --- database/console/stubs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/console/stubs.go b/database/console/stubs.go index 9bc62a38d..73d3de1cf 100644 --- a/database/console/stubs.go +++ b/database/console/stubs.go @@ -96,8 +96,8 @@ func (r Stubs) Factory() string { type DummyFactory struct { } -// Defination Define the model's default state. -func (f *DummyFactory) Defination() { +// Definition Define the model's default state. +func (f *DummyFactory) Definition() { } ` From ce094d2c80da6b78a54110e3d9742cbb33d33422 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 14:48:33 +0530 Subject: [PATCH 14/17] change name of function in factory stub --- database/console/stubs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/console/stubs.go b/database/console/stubs.go index 73d3de1cf..4e68f50aa 100644 --- a/database/console/stubs.go +++ b/database/console/stubs.go @@ -101,4 +101,4 @@ func (f *DummyFactory) Definition() { } ` -} \ No newline at end of file +} From b2ffccf3a918a1adb234a39a4364ba554bc6b089 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 14:49:37 +0530 Subject: [PATCH 15/17] change name of function in factory stub --- database/console/stubs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/console/stubs.go b/database/console/stubs.go index 4e68f50aa..0571ac183 100644 --- a/database/console/stubs.go +++ b/database/console/stubs.go @@ -97,8 +97,8 @@ type DummyFactory struct { } // Definition Define the model's default state. -func (f *DummyFactory) Definition() { - +func (f *DummyFactory) Definition() any { + return nil } ` } From bb6f5eac75390aa53fddd06bca69e919bdcf62c1 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 15:54:49 +0530 Subject: [PATCH 16/17] change return type of definition method --- contracts/database/factory/factory.go | 2 +- database/console/stubs.go | 2 +- database/gorm/factory_test.go | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/contracts/database/factory/factory.go b/contracts/database/factory/factory.go index 8d288d643..31da15ca1 100644 --- a/contracts/database/factory/factory.go +++ b/contracts/database/factory/factory.go @@ -1,5 +1,5 @@ package factory type Factory interface { - Definition() any + Definition() map[string]any } diff --git a/database/console/stubs.go b/database/console/stubs.go index 0571ac183..ac57aeca8 100644 --- a/database/console/stubs.go +++ b/database/console/stubs.go @@ -97,7 +97,7 @@ type DummyFactory struct { } // Definition Define the model's default state. -func (f *DummyFactory) Definition() any { +func (f *DummyFactory) Definition() map[string]any { return nil } ` diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go index 81e00b7c0..cf0c1adfe 100644 --- a/database/gorm/factory_test.go +++ b/database/gorm/factory_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/brianvoe/gofakeit/v6" - "github.com/goravel/framework/support/file" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -15,9 +14,9 @@ import ( type UserFactory struct { } -func (u *UserFactory) Definition() any { +func (u *UserFactory) Definition() map[string]any { faker := gofakeit.New(0) - return map[string]interface{}{ + return map[string]any{ "name": faker.Name(), "avatar": faker.Email(), "created_at": faker.Date(), @@ -40,8 +39,6 @@ func TestFactoryTestSuite(t *testing.T) { suite.Run(t, &FactoryTestSuite{ query: mysqlQuery, }) - - assert.Nil(t, file.Remove(dbDatabase)) assert.Nil(t, mysqlPool.Purge(mysqlResource)) } From c37446207a24e73f6ac9bfbf8859b803ec7d51e7 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Mon, 17 Jul 2023 16:04:52 +0530 Subject: [PATCH 17/17] skip tests for factory when using docker --- database/gorm/factory_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/database/gorm/factory_test.go b/database/gorm/factory_test.go index cf0c1adfe..71f787898 100644 --- a/database/gorm/factory_test.go +++ b/database/gorm/factory_test.go @@ -31,6 +31,10 @@ type FactoryTestSuite struct { } func TestFactoryTestSuite(t *testing.T) { + if testing.Short() { + t.Skip("Skipping tests of using docker") + } + mysqlDocker := NewMysqlDocker() mysqlPool, mysqlResource, mysqlQuery, err := mysqlDocker.New() if err != nil {