github.com/Accefy/pop@v0.0.0-20230428174248-e9f677eab5b9/preload_associations_test.go (about)

     1  package pop
     2  
     3  import (
     4  	"github.com/gobuffalo/nulls"
     5  	"github.com/stretchr/testify/require"
     6  	"testing"
     7  )
     8  
     9  func Test_New_Implementation_For_Nplus1(t *testing.T) {
    10  	if PDB == nil {
    11  		t.Skip("skipping integration tests")
    12  	}
    13  	transaction(func(tx *Connection) {
    14  		a := require.New(t)
    15  		for _, name := range []string{"Mark", "Joe", "Jane"} {
    16  			user := User{Name: nulls.NewString(name)}
    17  			a.NoError(tx.Create(&user))
    18  
    19  			book := Book{UserID: nulls.NewInt(user.ID)}
    20  			a.NoError(tx.Create(&book))
    21  
    22  			writer := Writer{Name: "Larry", BookID: book.ID}
    23  			a.NoError(tx.Create(&writer))
    24  
    25  			if name == "Mark" {
    26  				song := Song{UserID: user.ID}
    27  				a.NoError(tx.Create(&song))
    28  
    29  				address := Address{Street: "Pop"}
    30  				a.NoError(tx.Create(&address))
    31  
    32  				home := UsersAddress{UserID: user.ID, AddressID: address.ID}
    33  				a.NoError(tx.Create(&home))
    34  			}
    35  		}
    36  
    37  		users := []User{}
    38  		a.NoError(tx.All(&users))
    39  
    40  		// FILL THE HAS-MANY and HAS_ONE
    41  		a.NoError(preload(tx, &users))
    42  
    43  		a.Len(users[0].Books, 1)
    44  		a.Len(users[1].Books, 1)
    45  		a.Len(users[2].Books, 1)
    46  		a.Equal(users[0].FavoriteSong.UserID, users[0].ID)
    47  		a.Len(users[0].Houses, 1)
    48  
    49  		book := Book{}
    50  		a.NoError(tx.First(&book))
    51  		a.NoError(preload(tx, &book))
    52  		a.Len(book.Writers, 1)
    53  		a.Equal("Larry", book.Writers[0].Name)
    54  		a.Equal("Mark", book.User.Name.String)
    55  
    56  		usersWithPointers := []UserPointerAssocs{}
    57  		a.NoError(tx.All(&usersWithPointers))
    58  
    59  		// FILL THE HAS-MANY and HAS_ONE
    60  		a.NoError(preload(tx, &usersWithPointers))
    61  
    62  		a.Len(usersWithPointers[0].Books, 1)
    63  		a.Len(usersWithPointers[1].Books, 1)
    64  		a.Len(usersWithPointers[2].Books, 1)
    65  		a.Equal(usersWithPointers[0].FavoriteSong.UserID, users[0].ID)
    66  		a.Len(usersWithPointers[0].Houses, 1)
    67  	})
    68  }
    69  
    70  func Test_New_Implementation_For_Nplus1_With_UUID(t *testing.T) {
    71  	if PDB == nil {
    72  		t.Skip("skipping integration tests")
    73  	}
    74  	transaction(func(tx *Connection) {
    75  		a := require.New(t)
    76  
    77  		courses := []Course{}
    78  		for i := 0; i < 3; i++ {
    79  			course := Course{}
    80  			a.NoError(tx.Create(&course))
    81  			courses = append(courses, course)
    82  			if i == 0 {
    83  				a.NoError(tx.Create(&CourseCode{
    84  					CourseID: course.ID,
    85  				}))
    86  			}
    87  		}
    88  
    89  		courseCodes := []CourseCode{}
    90  		a.NoError(tx.All(&courseCodes))
    91  		a.Len(courseCodes, 1)
    92  
    93  		// FILL THE HAS-MANY and HAS_ONE
    94  		a.NoError(preload(tx, &courseCodes))
    95  		a.Equal(courses[0].ID, courseCodes[0].Course.ID)
    96  
    97  		student := Student{}
    98  		a.NoError(tx.Create(&student))
    99  
   100  		parent := Parent{}
   101  		a.NoError(tx.Create(&parent))
   102  
   103  		a.NoError(tx.RawQuery("insert into parents_students(parent_id, student_id) values(?,?)", parent.ID.String(), student.ID.String()).Exec())
   104  
   105  		parents := []Parent{}
   106  		a.NoError(tx.All(&parents))
   107  
   108  		a.NoError(preload(tx, &parents))
   109  		a.Len(parents, 1)
   110  		a.Len(parents[0].Students, 1)
   111  		a.Equal(student.ID, parents[0].Students[0].ID)
   112  	})
   113  }
   114  
   115  func Test_New_Implementation_For_Nplus1_With_NullUUIDs_And_FK_ID(t *testing.T) {
   116  	if PDB == nil {
   117  		t.Skip("skipping integration tests")
   118  	}
   119  
   120  	// This test suite prevents regressions of an obscure bug in the preload code which caused
   121  	// pointer values to be set with their empty values when relations did not exist.
   122  	//
   123  	// See also: https://github.com/gobuffalo/pop/issues/139
   124  	transaction(func(tx *Connection) {
   125  		a := require.New(t)
   126  
   127  		var server Server
   128  		a.NoError(tx.Create(&server))
   129  
   130  		class := &NetClient{
   131  			// The bug only appears when we have two elements in the slice where
   132  			// one has a relation and the other one has no such relation.
   133  			Hops: []Hop{
   134  				{Server: &server},
   135  				{},
   136  			}}
   137  
   138  		// This code basically just sets up
   139  		a.NoError(tx.Eager().Create(class))
   140  
   141  		var expected NetClient
   142  		a.NoError(tx.EagerPreload("Hops.Server").First(&expected))
   143  
   144  		// What would happen before the patch resolved this issue is that:
   145  		//
   146  		// Classes.CourseCodes[0].Course would be the correct value (a filled struct)
   147  		//
   148  		//   "server": {
   149  		//     "id": "fa51f71f-e884-4641-8005-923258b814f9",
   150  		//     "created_at": "2021-12-09T23:20:10.208019+01:00",
   151  		//     "updated_at": "2021-12-09T23:20:10.208019+01:00"
   152  		//   },
   153  		//
   154  		// Classes.CourseCodes[1].Course would an "empty" struct of Course even though there is no relation set up:
   155  		//
   156  		//	  "server": {
   157  		//      "id": "00000000-0000-0000-0000-000000000000",
   158  		//      "created_at": "0001-01-01T00:00:00Z",
   159  		//      "updated_at": "0001-01-01T00:00:00Z"
   160  		//    },
   161  		var foundValid, foundEmpty int
   162  		for _, hop := range expected.Hops {
   163  			if hop.ServerID.Valid {
   164  				foundValid++
   165  				a.NotNil(hop.Server, "%+v", hop)
   166  			} else {
   167  				foundEmpty++
   168  				a.Nil(hop.Server, "%+v", hop)
   169  			}
   170  		}
   171  
   172  		a.Equal(1, foundValid)
   173  		a.Equal(1, foundEmpty)
   174  	})
   175  }
   176  
   177  func Test_New_Implementation_For_Nplus1_Single(t *testing.T) {
   178  	if PDB == nil {
   179  		t.Skip("skipping integration tests")
   180  	}
   181  	transaction(func(tx *Connection) {
   182  		a := require.New(t)
   183  		for _, name := range []string{"Mark", "Joe", "Jane"} {
   184  			user := User{Name: nulls.NewString(name)}
   185  			a.NoError(tx.Create(&user))
   186  
   187  			book := Book{UserID: nulls.NewInt(user.ID)}
   188  			a.NoError(tx.Create(&book))
   189  
   190  			writer := Writer{Name: "Larry", BookID: book.ID}
   191  			a.NoError(tx.Create(&writer))
   192  
   193  			if name == "Mark" {
   194  				song := Song{UserID: user.ID}
   195  				a.NoError(tx.Create(&song))
   196  
   197  				address := Address{Street: "Pop"}
   198  				a.NoError(tx.Create(&address))
   199  
   200  				home := UsersAddress{UserID: user.ID, AddressID: address.ID}
   201  				a.NoError(tx.Create(&home))
   202  			}
   203  		}
   204  
   205  		users := []User{}
   206  		a.NoError(tx.All(&users))
   207  
   208  		// FILL THE HAS-MANY and HAS_ONE
   209  		a.NoError(preload(tx, &users, "Books"))
   210  
   211  		a.Len(users[0].Books, 1)
   212  		a.Len(users[1].Books, 1)
   213  		a.Len(users[2].Books, 1)
   214  		a.Zero(users[0].FavoriteSong.UserID)
   215  		a.Len(users[0].Houses, 0)
   216  	})
   217  }
   218  
   219  func Test_New_Implementation_For_Nplus1_Nested(t *testing.T) {
   220  	if PDB == nil {
   221  		t.Skip("skipping integration tests")
   222  	}
   223  	transaction(func(tx *Connection) {
   224  		a := require.New(t)
   225  		var song Song
   226  		for _, name := range []string{"Mark", "Joe", "Jane"} {
   227  			user := User{Name: nulls.NewString(name)}
   228  			a.NoError(tx.Create(&user))
   229  
   230  			book := Book{UserID: nulls.NewInt(user.ID)}
   231  			a.NoError(tx.Create(&book))
   232  
   233  			if name == "Mark" {
   234  				song = Song{UserID: user.ID}
   235  				a.NoError(tx.Create(&song))
   236  
   237  				address := Address{Street: "Pop"}
   238  				a.NoError(tx.Create(&address))
   239  
   240  				home := UsersAddress{UserID: user.ID, AddressID: address.ID}
   241  				a.NoError(tx.Create(&home))
   242  			}
   243  		}
   244  
   245  		SetEagerMode(EagerPreload)
   246  		users := []User{}
   247  		a.NoError(tx.Eager("Houses", "Books", "Books.User.FavoriteSong").All(&users))
   248  		a.Len(users[0].Books, 1)
   249  		a.Len(users[1].Books, 1)
   250  		a.Len(users[2].Books, 1)
   251  		a.Len(users[0].Houses, 1)
   252  
   253  		a.Equal(users[0].ID, users[0].Books[0].User.ID)
   254  		a.Equal(song.ID, users[0].Books[0].User.FavoriteSong.ID)
   255  		SetEagerMode(EagerDefault)
   256  	})
   257  }
   258  
   259  func Test_New_Implementation_For_Nplus1_BelongsTo_Not_Underscore(t *testing.T) {
   260  	if PDB == nil {
   261  		t.Skip("skipping integration tests")
   262  	}
   263  	transaction(func(tx *Connection) {
   264  		a := require.New(t)
   265  		user := User{Name: nulls.NewString("Mark")}
   266  		a.NoError(tx.Create(&user))
   267  
   268  		taxi := Taxi{UserID: nulls.NewInt(user.ID)}
   269  		a.NoError(tx.Create(&taxi))
   270  
   271  		SetEagerMode(EagerPreload)
   272  		taxis := []Taxi{}
   273  		a.NoError(tx.EagerPreload().All(&taxis))
   274  		a.Len(taxis, 1)
   275  		a.Equal("Mark", taxis[0].Driver.Name.String)
   276  		SetEagerMode(EagerDefault)
   277  	})
   278  }
   279  
   280  func Test_New_Implementation_For_BelongsTo_Multiple_Fields(t *testing.T) {
   281  	if PDB == nil {
   282  		t.Skip("skipping integration tests")
   283  	}
   284  	transaction(func(tx *Connection) {
   285  		a := require.New(t)
   286  		user := User{Name: nulls.NewString("Mark")}
   287  		a.NoError(tx.Create(&user))
   288  
   289  		address := Address{HouseNumber: 2, Street: "Street One"}
   290  		a.NoError(tx.Create(&address))
   291  
   292  		taxi := Taxi{UserID: nulls.NewInt(user.ID), AddressID: nulls.NewInt(address.ID)}
   293  		a.NoError(tx.Create(&taxi))
   294  
   295  		book := Book{TaxiID: nulls.NewInt(taxi.ID), Title: "My Book"}
   296  		a.NoError(tx.Create(&book))
   297  
   298  		SetEagerMode(EagerPreload)
   299  		books := []Book{}
   300  		a.NoError(tx.EagerPreload("Taxi.Driver", "Taxi.Address").All(&books))
   301  		a.Len(books, 1)
   302  		a.Equal(user.Name.String, books[0].Taxi.Driver.Name.String)
   303  		a.Equal(address.Street, books[0].Taxi.Address.Street)
   304  		SetEagerMode(EagerDefault)
   305  	})
   306  }
   307  
   308  func Test_New_Implementation_For_BelongsTo_Ptr_Field(t *testing.T) {
   309  	if PDB == nil {
   310  		t.Skip("skipping integration tests")
   311  	}
   312  	transaction(func(tx *Connection) {
   313  		a := require.New(t)
   314  		toAddress := Address{HouseNumber: 1, Street: "Destination Ave"}
   315  		a.NoError(tx.Create(&toAddress))
   316  
   317  		taxi := Taxi{ToAddressID: &toAddress.ID}
   318  		a.NoError(tx.Create(&taxi))
   319  
   320  		book1 := Book{TaxiID: nulls.NewInt(taxi.ID), Title: "My Book"}
   321  		a.NoError(tx.Create(&book1))
   322  
   323  		taxiNilToAddress := Taxi{ToAddressID: nil}
   324  		a.NoError(tx.Create(&taxiNilToAddress))
   325  
   326  		book2 := Book{TaxiID: nulls.NewInt(taxiNilToAddress.ID), Title: "Another Book"}
   327  		a.NoError(tx.Create(&book2))
   328  
   329  		SetEagerMode(EagerPreload)
   330  		books := []Book{}
   331  		a.NoError(tx.EagerPreload("Taxi.ToAddress").Order("created_at").All(&books))
   332  		a.Len(books, 2)
   333  		a.Equal(toAddress.Street, books[0].Taxi.ToAddress.Street)
   334  		a.NotNil(books[0].Taxi.ToAddressID)
   335  		a.Nil(books[1].Taxi.ToAddress)
   336  		a.Nil(books[1].Taxi.ToAddressID)
   337  		SetEagerMode(EagerDefault)
   338  	})
   339  }
   340  
   341  func Test_New_Implementation_For_HasMany_Ptr_Field(t *testing.T) {
   342  	if PDB == nil {
   343  		t.Skip("skipping integration tests")
   344  	}
   345  	transaction(func(tx *Connection) {
   346  		a := require.New(t)
   347  		toAddress1 := Address{HouseNumber: 1, Street: "Destination Ave"}
   348  		a.NoError(tx.Create(&toAddress1))
   349  		taxi1 := Taxi{Model: "Ford", ToAddressID: &toAddress1.ID}
   350  		a.NoError(tx.Create(&taxi1))
   351  		taxi2 := Taxi{Model: "Honda", ToAddressID: &toAddress1.ID}
   352  		a.NoError(tx.Create(&taxi2))
   353  
   354  		taxiNilToAddress := Taxi{ToAddressID: nil}
   355  		a.NoError(tx.Create(&taxiNilToAddress))
   356  
   357  		toAddress2 := Address{HouseNumber: 2, Street: "Final Way"}
   358  		a.NoError(tx.Create(&toAddress2))
   359  		taxi3 := Taxi{Model: "Mazda", ToAddressID: &toAddress2.ID}
   360  		a.NoError(tx.Create(&taxi3))
   361  
   362  		SetEagerMode(EagerPreload)
   363  		addresses := []Address{}
   364  		a.NoError(tx.EagerPreload("TaxisToHere").Order("created_at").All(&addresses))
   365  		a.Len(addresses, 2)
   366  		a.NotNil(addresses[0].TaxisToHere)
   367  		a.Len(addresses[0].TaxisToHere, 2)
   368  		a.Equal(taxi1.Model, addresses[0].TaxisToHere[0].Model)
   369  		a.Equal(taxi2.Model, addresses[0].TaxisToHere[1].Model)
   370  		a.NotNil(addresses[1].TaxisToHere)
   371  		a.Len(addresses[1].TaxisToHere, 1)
   372  		a.Equal(taxi3.Model, addresses[1].TaxisToHere[0].Model)
   373  		SetEagerMode(EagerDefault)
   374  	})
   375  }