package boil import ( "database/sql/driver" "reflect" "testing" "time" "gopkg.in/DATA-DOG/go-sqlmock.v1" "gopkg.in/nullbio/null.v4" ) type mockRowMaker struct { int rows []driver.Value } func TestBind(t *testing.T) { t.Parallel() testResults := []*struct { ID int Name string `boil:"test"` }{} query := &Query{ from: []string{"fun"}, } db, mock, err := sqlmock.New() if err != nil { t.Error(err) } ret := sqlmock.NewRows([]string{"id", "test"}) ret.AddRow(driver.Value(int64(35)), driver.Value("pat")) ret.AddRow(driver.Value(int64(12)), driver.Value("cat")) mock.ExpectQuery(`SELECT \* FROM "fun";`).WillReturnRows(ret) SetExecutor(query, db) err = query.Bind(&testResults) if err != nil { t.Error(err) } if len(testResults) != 2 { t.Fatal("wrong number of results:", len(testResults)) } if id := testResults[0].ID; id != 35 { t.Error("wrong ID:", id) } if name := testResults[0].Name; name != "pat" { t.Error("wrong name:", name) } if id := testResults[1].ID; id != 12 { t.Error("wrong ID:", id) } if name := testResults[1].Name; name != "cat" { t.Error("wrong name:", name) } if err := mock.ExpectationsWereMet(); err != nil { t.Error(err) } } func TestGetBoilTag(t *testing.T) { type TestStruct struct { FirstName string `boil:"test_one,boil"` LastName string `boil:"test_two"` MiddleName string `boil:"middle_name,boil"` AwesomeName string `boil:"awesome_name"` Age string `boil:",boil"` Face string `boil:"-"` Nose string } var testTitleCases = map[string]string{ "test_one": "TestOne", "test_two": "TestTwo", "middle_name": "MiddleName", "awesome_name": "AwesomeName", "age": "Age", "face": "Face", "nose": "Nose", } var structFields []reflect.StructField typ := reflect.TypeOf(TestStruct{}) removeOk := func(thing reflect.StructField, ok bool) reflect.StructField { if !ok { panic("Exploded") } return thing } structFields = append(structFields, removeOk(typ.FieldByName("FirstName"))) structFields = append(structFields, removeOk(typ.FieldByName("LastName"))) structFields = append(structFields, removeOk(typ.FieldByName("MiddleName"))) structFields = append(structFields, removeOk(typ.FieldByName("AwesomeName"))) structFields = append(structFields, removeOk(typ.FieldByName("Age"))) structFields = append(structFields, removeOk(typ.FieldByName("Face"))) structFields = append(structFields, removeOk(typ.FieldByName("Nose"))) expect := []struct { Name string Recurse bool }{ {"TestOne", true}, {"TestTwo", false}, {"MiddleName", true}, {"AwesomeName", false}, {"Age", true}, {"-", false}, {"Nose", false}, } for i, s := range structFields { name, recurse := getBoilTag(s, testTitleCases) if expect[i].Name != name { t.Errorf("Invalid name, expect %q, got %q", expect[i].Name, name) } if expect[i].Recurse != recurse { t.Errorf("Invalid recurse, expect %v, got %v", !recurse, recurse) } } } func TestBindSingular(t *testing.T) { t.Parallel() testResults := struct { ID int Name string `boil:"test"` }{} query := &Query{ from: []string{"fun"}, } db, mock, err := sqlmock.New() if err != nil { t.Error(err) } ret := sqlmock.NewRows([]string{"id", "test"}) ret.AddRow(driver.Value(int64(35)), driver.Value("pat")) mock.ExpectQuery(`SELECT \* FROM "fun";`).WillReturnRows(ret) SetExecutor(query, db) err = query.Bind(&testResults) if err != nil { t.Error(err) } if id := testResults.ID; id != 35 { t.Error("wrong ID:", id) } if name := testResults.Name; name != "pat" { t.Error("wrong name:", name) } if err := mock.ExpectationsWereMet(); err != nil { t.Error(err) } } var loadFunctionCalled bool var loadFunctionNestedCalled int type testRStruct struct { } type testNestedStruct struct { ID int R *testNestedRStruct } type testNestedRStruct struct { ToEagerLoad *testNestedStruct } type testNestedSlice struct { ID int R *testNestedRSlice } type testNestedRSlice struct { ToEagerLoad []*testNestedSlice } func (r *testRStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error { loadFunctionCalled = true return nil } func (r *testNestedRStruct) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error { switch x := obj.(type) { case *testNestedStruct: x.R = &testNestedRStruct{ &testNestedStruct{ID: 5}, } case *[]*testNestedStruct: for _, r := range *x { r.R = &testNestedRStruct{ &testNestedStruct{ID: 5}, } } } loadFunctionNestedCalled++ return nil } func (r *testNestedRSlice) LoadToEagerLoad(exec Executor, singular bool, obj interface{}) error { switch x := obj.(type) { case *testNestedSlice: x.R = &testNestedRSlice{ []*testNestedSlice{&testNestedSlice{ID: 5}}, } case *[]*testNestedSlice: for _, r := range *x { r.R = &testNestedRSlice{ []*testNestedSlice{&testNestedSlice{ID: 5}}, } } } loadFunctionNestedCalled++ return nil } func TestLoadRelationshipsSlice(t *testing.T) { // t.Parallel() Function uses globals loadFunctionCalled = false testSlice := []*struct { ID int R *testRStruct }{} if err := loadRelationships(nil, []string{"TestOne"}, &testSlice, false); err != nil { t.Error(err) } if !loadFunctionCalled { t.Errorf("Load function was not called for testSlice") } } func TestLoadRelationshipsSingular(t *testing.T) { // t.Parallel() Function uses globals loadFunctionCalled = false testSingular := struct { ID int R *testRStruct }{} if err := loadRelationships(nil, []string{"TestOne"}, &testSingular, true); err != nil { t.Error(err) } if !loadFunctionCalled { t.Errorf("Load function was not called for singular") } } func TestLoadRelationshipsSliceNested(t *testing.T) { // t.Parallel() Function uses globals testSlice := []*testNestedStruct{ { ID: 5, }, } loadFunctionNestedCalled = 0 if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSlice, false); err != nil { t.Error(err) } if loadFunctionNestedCalled != 3 { t.Error("Load function was called:", loadFunctionNestedCalled, "times") } testSliceSlice := []*testNestedSlice{ { ID: 5, }, } loadFunctionNestedCalled = 0 if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSliceSlice, false); err != nil { t.Error(err) } if loadFunctionNestedCalled != 3 { t.Error("Load function was called:", loadFunctionNestedCalled, "times") } } func TestLoadRelationshipsSingularNested(t *testing.T) { // t.Parallel() Function uses globals testSingular := testNestedStruct{ ID: 5, } loadFunctionNestedCalled = 0 if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSingular, true); err != nil { t.Error(err) } if loadFunctionNestedCalled != 3 { t.Error("Load function was called:", loadFunctionNestedCalled, "times") } testSingularSlice := testNestedSlice{ ID: 5, } loadFunctionNestedCalled = 0 if err := loadRelationships(nil, []string{"ToEagerLoad", "ToEagerLoad", "ToEagerLoad"}, &testSingularSlice, true); err != nil { t.Error(err) } if loadFunctionNestedCalled != 3 { t.Error("Load function was called:", loadFunctionNestedCalled, "times") } } func TestBind_InnerJoin(t *testing.T) { t.Parallel() testResults := []*struct { Happy struct { ID int `boil:"identifier"` } `boil:",bind"` Fun struct { ID int `boil:"id"` } `boil:",bind"` }{} query := &Query{ from: []string{"fun"}, joins: []join{{kind: JoinInner, clause: "happy as h on fun.id = h.fun_id"}}, } db, mock, err := sqlmock.New() if err != nil { t.Error(err) } ret := sqlmock.NewRows([]string{"id"}) ret.AddRow(driver.Value(int64(10))) ret.AddRow(driver.Value(int64(11))) mock.ExpectQuery(`SELECT "fun"\.\* FROM "fun" INNER JOIN happy as h on fun.id = h.fun_id;`).WillReturnRows(ret) SetExecutor(query, db) err = query.Bind(&testResults) if err != nil { t.Error(err) } if len(testResults) != 2 { t.Fatal("wrong number of results:", len(testResults)) } if id := testResults[0].Happy.ID; id != 0 { t.Error("wrong ID:", id) } if id := testResults[0].Fun.ID; id != 10 { t.Error("wrong ID:", id) } if id := testResults[1].Happy.ID; id != 0 { t.Error("wrong ID:", id) } if id := testResults[1].Fun.ID; id != 11 { t.Error("wrong ID:", id) } if err := mock.ExpectationsWereMet(); err != nil { t.Error(err) } } func TestBind_InnerJoinSelect(t *testing.T) { t.Parallel() testResults := []*struct { Happy struct { ID int } `boil:"h,bind"` Fun struct { ID int } `boil:",bind"` }{} query := &Query{ selectCols: []string{"fun.id", "h.id"}, from: []string{"fun"}, joins: []join{{kind: JoinInner, clause: "happy as h on fun.happy_id = h.id"}}, } db, mock, err := sqlmock.New() if err != nil { t.Error(err) } ret := sqlmock.NewRows([]string{"fun.id", "h.id"}) ret.AddRow(driver.Value(int64(10)), driver.Value(int64(11))) ret.AddRow(driver.Value(int64(12)), driver.Value(int64(13))) mock.ExpectQuery(`SELECT "fun"."id" as "fun.id", "h"."id" as "h.id" FROM "fun" INNER JOIN happy as h on fun.happy_id = h.id;`).WillReturnRows(ret) SetExecutor(query, db) err = query.Bind(&testResults) if err != nil { t.Error(err) } if len(testResults) != 2 { t.Fatal("wrong number of results:", len(testResults)) } if id := testResults[0].Happy.ID; id != 11 { t.Error("wrong ID:", id) } if id := testResults[0].Fun.ID; id != 10 { t.Error("wrong ID:", id) } if id := testResults[1].Happy.ID; id != 13 { t.Error("wrong ID:", id) } if id := testResults[1].Fun.ID; id != 12 { t.Error("wrong ID:", id) } if err := mock.ExpectationsWereMet(); err != nil { t.Error(err) } } func TestBindPtrs_Easy(t *testing.T) { t.Parallel() testStruct := struct { ID int `boil:"identifier"` Date time.Time }{} cols := []string{"identifier", "date"} ptrs, err := bindPtrs(&testStruct, nil, cols...) if err != nil { t.Error(err) } if ptrs[0].(*int) != &testStruct.ID { t.Error("id is the wrong pointer") } if ptrs[1].(*time.Time) != &testStruct.Date { t.Error("id is the wrong pointer") } } func TestBindPtrs_Recursive(t *testing.T) { t.Parallel() testStruct := struct { Happy struct { ID int `boil:"identifier"` } Fun struct { ID int } `boil:",bind"` }{} cols := []string{"id", "fun.id"} ptrs, err := bindPtrs(&testStruct, nil, cols...) if err != nil { t.Error(err) } if ptrs[0].(*int) != &testStruct.Fun.ID { t.Error("id is the wrong pointer") } if ptrs[1].(*int) != &testStruct.Fun.ID { t.Error("id is the wrong pointer") } } func TestBindPtrs_RecursiveTags(t *testing.T) { t.Parallel() testStruct := struct { Happy struct { ID int `boil:"identifier"` } `boil:",bind"` Fun struct { ID int `boil:"identification"` } `boil:",bind"` }{} cols := []string{"happy.identifier", "fun.identification"} ptrs, err := bindPtrs(&testStruct, nil, cols...) if err != nil { t.Error(err) } if ptrs[0].(*int) != &testStruct.Happy.ID { t.Error("id is the wrong pointer") } if ptrs[1].(*int) != &testStruct.Fun.ID { t.Error("id is the wrong pointer") } } func TestBindPtrs_Ignore(t *testing.T) { t.Parallel() testStruct := struct { ID int `boil:"-"` Happy struct { ID int } `boil:",bind"` }{} cols := []string{"id"} ptrs, err := bindPtrs(&testStruct, nil, cols...) if err != nil { t.Error(err) } if ptrs[0].(*int) != &testStruct.Happy.ID { t.Error("id is the wrong pointer") } } func TestGetStructValues(t *testing.T) { t.Parallel() timeThing := time.Now() o := struct { TitleThing string Name string ID int Stuff int Things int Time time.Time NullBool null.Bool }{ TitleThing: "patrick", Stuff: 10, Things: 0, Time: timeThing, NullBool: null.NewBool(true, false), } vals := GetStructValues(&o, nil, "title_thing", "name", "id", "stuff", "things", "time", "null_bool") if vals[0].(string) != "patrick" { t.Errorf("Want test, got %s", vals[0]) } if vals[1].(string) != "" { t.Errorf("Want empty string, got %s", vals[1]) } if vals[2].(int) != 0 { t.Errorf("Want 0, got %d", vals[2]) } if vals[3].(int) != 10 { t.Errorf("Want 10, got %d", vals[3]) } if vals[4].(int) != 0 { t.Errorf("Want 0, got %d", vals[4]) } if !vals[5].(time.Time).Equal(timeThing) { t.Errorf("Want %s, got %s", o.Time, vals[5]) } if !vals[6].(null.Bool).IsZero() { t.Errorf("Want %v, got %v", o.NullBool, vals[6]) } } func TestGetSliceValues(t *testing.T) { t.Parallel() o := []struct { ID int Name string }{ {5, "a"}, {6, "b"}, } in := make([]interface{}, len(o)) in[0] = o[0] in[1] = o[1] vals := GetSliceValues(in, nil, "id", "name") if got := vals[0].(int); got != 5 { t.Error(got) } if got := vals[1].(string); got != "a" { t.Error(got) } if got := vals[2].(int); got != 6 { t.Error(got) } if got := vals[3].(string); got != "b" { t.Error(got) } } func TestGetStructPointers(t *testing.T) { t.Parallel() o := struct { Title string ID *int }{ Title: "patrick", } ptrs := GetStructPointers(&o, nil, "title", "id") *ptrs[0].(*string) = "test" if o.Title != "test" { t.Errorf("Expected test, got %s", o.Title) } x := 5 *ptrs[1].(**int) = &x if *o.ID != 5 { t.Errorf("Expected 5, got %d", *o.ID) } }