From 28e52a4e08c5b90f5bd984dae7a20b037c857102 Mon Sep 17 00:00:00 2001 From: David Lick Date: Sun, 31 May 2020 00:40:32 -0400 Subject: [PATCH] refactors data access layer to use pop ORM for eager loading, adds book titles rented to renters in response body --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 1 + bookish-server/Dockerfile | 2 +- bookish-server/cmd/api/main.go | 11 +- bookish-server/cmd/http/http.go | 6 +- bookish-server/cmd/http/middleware.go | 9 +- ...ventory_handlers.go => rental_handlers.go} | 29 +- bookish-server/cmd/http/renter_handlers.go | 7 +- bookish-server/config/database.yml | 6 + bookish-server/go.mod | 19 +- bookish-server/go.sum | 288 ++++++++++++++++++ bookish-server/models/rental.go | 19 ++ bookish-server/models/renter.go | 18 ++ bookish-server/mysql/errors.go | 2 +- bookish-server/mysql/inventory.go | 111 ------- bookish-server/mysql/mysql.go | 8 +- bookish-server/mysql/rental.go | 81 +++++ bookish-server/mysql/renter.go | 62 ++-- .../{inventory => rental}/errors.go | 2 +- .../inventory.go => rental/rental.go} | 23 +- .../rental_mock.go} | 17 +- .../rental_test.go} | 39 +-- bookish-server/renter.go | 11 +- bookish-server/renter/renter.go | 55 +++- bookish-server/renter/renter_mock.go | 84 ++++- bookish-server/renter/renter_test.go | 46 ++- docker-compose.yml | 20 +- init/bookish-ddl.sql | 21 -- init/schema.sql | 22 ++ 29 files changed, 732 insertions(+), 287 deletions(-) delete mode 100644 .DS_Store rename bookish-server/cmd/http/{inventory_handlers.go => rental_handlers.go} (64%) create mode 100644 bookish-server/config/database.yml create mode 100644 bookish-server/models/rental.go create mode 100644 bookish-server/models/renter.go delete mode 100644 bookish-server/mysql/inventory.go create mode 100644 bookish-server/mysql/rental.go rename bookish-server/{inventory => rental}/errors.go (90%) rename bookish-server/{inventory/inventory.go => rental/rental.go} (68%) rename bookish-server/{inventory/inventory_mock.go => rental/rental_mock.go} (90%) rename bookish-server/{inventory/inventory_test.go => rental/rental_test.go} (79%) delete mode 100644 init/bookish-ddl.sql create mode 100644 init/schema.sql diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index c03e5ef4ba3108d0f2287b49a20c2ac731e81daf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMO>YuG7=8y(7yKxGB*oiaOgywrn`((MO%Um!2NNSk4{GQx5ZHFvL3SxYgmCdc zsQhxh~ZnU6H=E*!iV8)wMOGqd|X?>qC%u){1w#Hz#A4pE7SEM$Sj%P5v4 zabM(A%IGU=Kn6aMM})L`@XtsN~3^L;J;Kr zJRfXiffbE&h4RsXLLLEtIb4Y#)AH8jyoLhL=7C)QNkchv0r(@NzBXC8Rd^r=ncKUF<<2#T}BD z;CxB+dMU>pg?&BJZ*JfB!f1Q@2g_u07cO41tb$dzT6k0M@=-lvC4(1xd;tPROWD~$6}qrQ$2I&#zwbzr{i7TWX-KB8 zMggP1A1c6dwR)`zlHApoki{- 4 { - return false, nil - } - - return true, nil -} - -// RenterAlreadyCheckedOut queries the database for the given renterId and title. If a record is returned checkedOut will be true. -func (s *store) RenterAlreadyCheckedOut(renterId int, title string) (checkedOut bool, err error) { - q := ` - SELECT - * - FROM - rentals - WHERE - renter_id = ? - AND book_title = ? - AND return_date IS NULL; - ` - - var rr []struct { - ID int `db:"id"` - RenterID int `db:"renter_id"` - RentalDate time.Time `db:"rental_date"` - ReturnDate sql.NullTime `db:"return_date"` - BookTitle string `db:"book_title"` - } - - err = s.Select(&rr, q, renterId, title) - if err != nil { - if errors.Is(sql.ErrNoRows, err) { - return false, nil - } - - return false, err - } - - if len(rr) == 0 { - return false, nil - } - - return true, nil -} - -// CheckoutBook inserts a record indicating the book is checked out to the renter. -func (s *store) CheckoutBook(renterId int, title string) error { - q := ` - INSERT INTO - rentals (renter_id, rental_date, book_title) - VALUES - (?, ?, ?); - ` - - _, err := s.Exec(q, renterId, time.Now(), title) - return err -} - -// ReturnBook sets the return_date field to now indicating the book has been returned. -func (s *store) ReturnBook(renterId int, title string) error { - q := ` - UPDATE - rentals - SET - return_date = ? - WHERE - renter_id = ? - AND book_title = ?; - ` - - _, err := s.Exec(q, time.Now(), renterId, title) - return err -} diff --git a/bookish-server/mysql/mysql.go b/bookish-server/mysql/mysql.go index 88d0d61..9a8001b 100644 --- a/bookish-server/mysql/mysql.go +++ b/bookish-server/mysql/mysql.go @@ -1,19 +1,19 @@ package mysql import ( - "github.com/jmoiron/sqlx" + "github.com/gobuffalo/pop" ) // store holds a connection to the application database. type store struct { - *sqlx.DB + *pop.Connection } // NewStore creates a new connection to the database and returns a store containing the connection. func NewStore(dsn string) (s *store, err error) { - db, err := sqlx.Open("mysql", dsn) + db, err := pop.Connect("development") if err != nil { - return s, err + return &store{}, err } return &store{db}, nil diff --git a/bookish-server/mysql/rental.go b/bookish-server/mysql/rental.go new file mode 100644 index 0000000..6b09796 --- /dev/null +++ b/bookish-server/mysql/rental.go @@ -0,0 +1,81 @@ +package mysql + +import ( + "database/sql" + "errors" + "fmt" + "time" + + "github.com/davidlick/bookish/bookish-server/models" + "github.com/gobuffalo/nulls" + "github.com/gobuffalo/uuid" +) + +// IsBookAvailable queries the database for available books. +func (s *store) IsBookAvailable(title string) (available bool, err error) { + rr := []models.Rental{} + err = s.Where(fmt.Sprintf("book_title = '%s'", title)).Where("return_date IS NULL").All(&rr) + if err != nil { + // If no records are returned then the book is available for rent. + if errors.Is(sql.ErrNoRows, err) { + return true, nil + } + + return false, err + } + + // If 5 books are rented out this book is not available. + if len(rr) > 4 { + return false, nil + } + + return true, nil +} + +// RenterAlreadyCheckedOut queries the database for the given renterId and title. If a record is returned checkedOut will be true. +func (s *store) RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (checkedOut bool, err error) { + rr := []models.Rental{} + err = s.Where(fmt.Sprintf("renter_id = '%s'", renterId)). + Where(fmt.Sprintf("book_title = '%s'", title)). + Where("return_date IS NULL"). + All(&rr) + if err != nil { + // If no records are returned then the book is available for rent. + if errors.Is(sql.ErrNoRows, err) { + return false, nil + } + + return false, err + } + + // If 5 books are rented out this book is not available. + if len(rr) == 0 { + return false, nil + } + + return true, nil +} + +// CheckoutBook inserts a record indicating the book is checked out to the renter. +func (s *store) CheckoutBook(renterId uuid.UUID, title string) error { + r := models.Rental{BookTitle: title, RenterID: renterId, RentalDate: time.Now()} + err := s.Create(&r) + return err +} + +// ReturnBook sets the return_date field to now indicating the book has been returned. +func (s *store) ReturnBook(renterId uuid.UUID, title string) error { + r := models.Rental{} + err := s.Where(fmt.Sprintf("renter_id = '%s'", renterId)). + Where(fmt.Sprintf("book_title = '%s'", title)). + Where("return_date IS NULL"). + First(&r) + if err != nil { + return ErrNotFound + } + + r.ReturnDate = nulls.Time{Time: time.Now(), Valid: true} + + err = s.Save(&r) + return err +} diff --git a/bookish-server/mysql/renter.go b/bookish-server/mysql/renter.go index 3045a6d..7738485 100644 --- a/bookish-server/mysql/renter.go +++ b/bookish-server/mysql/renter.go @@ -4,63 +4,43 @@ import ( "database/sql" "errors" - bookish "github.com/davidlick/bookish/bookish-server" - "github.com/go-sql-driver/mysql" + "github.com/davidlick/bookish/bookish-server/models" + "github.com/gofrs/uuid" ) // ListAll returns all renter records. -func (s *store) ListAll() (rr []bookish.Renter, err error) { - q := ` - SELECT - * - FROM - renters - ` - - err = s.Select(&rr, q) +func (s *store) ListAll() (rr []models.Renter, err error) { + err = s.Eager().All(&rr) if err != nil && errors.Is(sql.ErrNoRows, err) { return nil, ErrNotFound } + return } // FetchDetails queries the database for a specific renter. -func (s *store) FetchDetails(id int) (r bookish.Renter, err error) { - q := ` - SELECT - * - FROM - renters - WHERE - id = ? - ` - - err = s.Get(&r, q, id) - if err != nil && errors.Is(sql.ErrNoRows, err) { - return bookish.Renter{}, ErrNotFound - } +func (s *store) FetchDetails(id string) (r models.Renter, err error) { + err = s.Eager().Find(&r, id) return } // New inserts a record for a new renter. If that renter already exists it returns an ErrAlreadyExists. -func (s *store) New(name string, address string, email string, phoneNumber string) (id int, err error) { - q := ` - INSERT INTO - renters (name, address, email, phone_number) - VALUES - (?, ?, ?, ?) - ` - - result, err := s.Exec(q, name, address, email, phoneNumber) - if err != nil { - if _, ok := err.(*mysql.MySQLError); ok { - return 0, ErrAlreadyExists - } +func (s *store) New(name string, address string, email string, phoneNumber string) (id uuid.UUID, err error) { + r := models.Renter{ + Name: name, + Address: address, + Email: email, + PhoneNumber: phoneNumber, } - lastId, err := result.LastInsertId() + // Create the new renter. + err = s.Create(&r) if err != nil { - return id, err + return } - return int(lastId), nil + + // Get the last renter created. + r = models.Renter{} + err = s.Last(&r) + return r.ID, err } diff --git a/bookish-server/inventory/errors.go b/bookish-server/rental/errors.go similarity index 90% rename from bookish-server/inventory/errors.go rename to bookish-server/rental/errors.go index 6d3379c..b1fab7e 100644 --- a/bookish-server/inventory/errors.go +++ b/bookish-server/rental/errors.go @@ -1,4 +1,4 @@ -package inventory +package rental import "errors" diff --git a/bookish-server/inventory/inventory.go b/bookish-server/rental/rental.go similarity index 68% rename from bookish-server/inventory/inventory.go rename to bookish-server/rental/rental.go index 7fea191..28b7001 100644 --- a/bookish-server/inventory/inventory.go +++ b/bookish-server/rental/rental.go @@ -1,11 +1,17 @@ -package inventory +package rental + +import ( + "fmt" + + "github.com/gofrs/uuid" +) // Storage defines behavior for interacting with the inventory store. type Storage interface { IsBookAvailable(title string) (available bool, err error) - RenterAlreadyCheckedOut(renterId int, title string) (checkedOut bool, err error) - CheckoutBook(renterId int, title string) error - ReturnBook(renterId int, title string) error + RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (checkedOut bool, err error) + CheckoutBook(renterId uuid.UUID, title string) error + ReturnBook(renterId uuid.UUID, title string) error } type service struct { @@ -15,8 +21,8 @@ type service struct { // Service defines behavior for interacting with the inventory service. type Service interface { IsBookAvailable(title string) (available bool, err error) - CheckoutBook(renterId int, title string) error - ReturnBook(renterId int, title string) error + CheckoutBook(renterId uuid.UUID, title string) error + ReturnBook(renterId uuid.UUID, title string) error } // NewService creates a new inventory service. @@ -32,7 +38,7 @@ func (s *service) IsBookAvailable(title string) (available bool, err error) { } // CheckoutBook checks a book out to a renter. -func (s *service) CheckoutBook(renterId int, title string) error { +func (s *service) CheckoutBook(renterId uuid.UUID, title string) error { available, err := s.IsBookAvailable(title) if err != nil { return err @@ -46,6 +52,7 @@ func (s *service) CheckoutBook(renterId int, title string) error { // If there was an error or the book is checked out by the renter already we'll // say the book is unavailable. if err != nil || checkedOut { + fmt.Println("unavailable book", err, checkedOut) return ErrUnavailableBook } @@ -53,6 +60,6 @@ func (s *service) CheckoutBook(renterId int, title string) error { } // ReturnBook returns a book for a renter. -func (s *service) ReturnBook(renterId int, title string) error { +func (s *service) ReturnBook(renterId uuid.UUID, title string) error { return s.store.ReturnBook(renterId, title) } diff --git a/bookish-server/inventory/inventory_mock.go b/bookish-server/rental/rental_mock.go similarity index 90% rename from bookish-server/inventory/inventory_mock.go rename to bookish-server/rental/rental_mock.go index 1a1c7c3..c0a3f54 100644 --- a/bookish-server/inventory/inventory_mock.go +++ b/bookish-server/rental/rental_mock.go @@ -1,10 +1,11 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: inventory.go +// Source: rental.go -// Package inventory is a generated GoMock package. -package inventory +// Package rental is a generated GoMock package. +package rental import ( + uuid "github.com/gofrs/uuid" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -48,7 +49,7 @@ func (mr *MockStorageMockRecorder) IsBookAvailable(title interface{}) *gomock.Ca } // RenterAlreadyCheckedOut mocks base method -func (m *MockStorage) RenterAlreadyCheckedOut(renterId int, title string) (bool, error) { +func (m *MockStorage) RenterAlreadyCheckedOut(renterId uuid.UUID, title string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RenterAlreadyCheckedOut", renterId, title) ret0, _ := ret[0].(bool) @@ -63,7 +64,7 @@ func (mr *MockStorageMockRecorder) RenterAlreadyCheckedOut(renterId, title inter } // CheckoutBook mocks base method -func (m *MockStorage) CheckoutBook(renterId int, title string) error { +func (m *MockStorage) CheckoutBook(renterId uuid.UUID, title string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckoutBook", renterId, title) ret0, _ := ret[0].(error) @@ -77,7 +78,7 @@ func (mr *MockStorageMockRecorder) CheckoutBook(renterId, title interface{}) *go } // ReturnBook mocks base method -func (m *MockStorage) ReturnBook(renterId int, title string) error { +func (m *MockStorage) ReturnBook(renterId uuid.UUID, title string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReturnBook", renterId, title) ret0, _ := ret[0].(error) @@ -129,7 +130,7 @@ func (mr *MockServiceMockRecorder) IsBookAvailable(title interface{}) *gomock.Ca } // CheckoutBook mocks base method -func (m *MockService) CheckoutBook(renterId int, title string) error { +func (m *MockService) CheckoutBook(renterId uuid.UUID, title string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckoutBook", renterId, title) ret0, _ := ret[0].(error) @@ -143,7 +144,7 @@ func (mr *MockServiceMockRecorder) CheckoutBook(renterId, title interface{}) *go } // ReturnBook mocks base method -func (m *MockService) ReturnBook(renterId int, title string) error { +func (m *MockService) ReturnBook(renterId uuid.UUID, title string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReturnBook", renterId, title) ret0, _ := ret[0].(error) diff --git a/bookish-server/inventory/inventory_test.go b/bookish-server/rental/rental_test.go similarity index 79% rename from bookish-server/inventory/inventory_test.go rename to bookish-server/rental/rental_test.go index 22fc0d0..0c2c903 100644 --- a/bookish-server/inventory/inventory_test.go +++ b/bookish-server/rental/rental_test.go @@ -1,18 +1,18 @@ -package inventory +package rental import ( "errors" "testing" + "github.com/gofrs/uuid" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) -func TestInventory_IsBookAvailable(t *testing.T) { +func TestRental_IsBookAvailable(t *testing.T) { testCases := []struct { TestName string Title string - RenterId int Available bool Error error }{ @@ -50,10 +50,9 @@ func TestInventory_IsBookAvailable(t *testing.T) { } } -func TestInventory_CheckoutBook(t *testing.T) { +func TestRental_CheckoutBook(t *testing.T) { testCases := []struct { TestName string - RenterId int Title string Available bool AvailableError error @@ -63,27 +62,23 @@ func TestInventory_CheckoutBook(t *testing.T) { }{ { TestName: "success", - RenterId: 1, Title: "Book #1", Available: true, }, { TestName: "unavailable", - RenterId: 2, Title: "Book #2", Available: false, Error: ErrUnavailableBook, }, { TestName: "availability_error", - RenterId: 3, Title: "Book #3", AvailableError: errors.New("test error"), Error: errors.New("test error"), }, { TestName: "checked-out", - RenterId: 4, Title: "Book #4", Available: true, CheckedOut: true, @@ -91,7 +86,6 @@ func TestInventory_CheckoutBook(t *testing.T) { }, { TestName: "failure", - RenterId: 4, Title: "Book #4", Available: true, Error: errors.New("test error"), @@ -103,38 +97,40 @@ func TestInventory_CheckoutBook(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + renterId, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + mockStorage := NewMockStorage(ctrl) mockStorage.EXPECT().IsBookAvailable(tc.Title).Return(tc.Available, tc.AvailableError) if tc.Available && tc.AvailableError == nil { - mockStorage.EXPECT().RenterAlreadyCheckedOut(tc.RenterId, tc.Title).Return(tc.CheckedOut, tc.CheckedOutError) + mockStorage.EXPECT().RenterAlreadyCheckedOut(renterId, tc.Title).Return(tc.CheckedOut, tc.CheckedOutError) if !tc.CheckedOut && tc.CheckedOutError == nil { - mockStorage.EXPECT().CheckoutBook(tc.RenterId, tc.Title).Return(tc.Error) + mockStorage.EXPECT().CheckoutBook(renterId, tc.Title).Return(tc.Error) } } service := NewService(mockStorage) - err := service.CheckoutBook(tc.RenterId, tc.Title) + err = service.CheckoutBook(renterId, tc.Title) assert.Equal(t, tc.Error, err) }) } } -func TestInventory_ReturnBook(t *testing.T) { +func TestRental_ReturnBook(t *testing.T) { testCases := []struct { TestName string - RenterId int Title string Error error }{ { TestName: "success", - RenterId: 1, Title: "Book #1", }, { TestName: "failure", - RenterId: 2, Title: "Book #2", Error: errors.New("test error"), }, @@ -145,11 +141,16 @@ func TestInventory_ReturnBook(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + renterId, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + mockStorage := NewMockStorage(ctrl) - mockStorage.EXPECT().ReturnBook(tc.RenterId, tc.Title).Return(tc.Error) + mockStorage.EXPECT().ReturnBook(renterId, tc.Title).Return(tc.Error) service := NewService(mockStorage) - err := service.ReturnBook(tc.RenterId, tc.Title) + err = service.ReturnBook(renterId, tc.Title) assert.Equal(t, tc.Error, err) }) diff --git a/bookish-server/renter.go b/bookish-server/renter.go index 6ced311..5b28ce3 100644 --- a/bookish-server/renter.go +++ b/bookish-server/renter.go @@ -2,9 +2,10 @@ package bookish_server // Renter is an entity in the Bookish API wish rents books. type Renter struct { - ID int `db:"id" json:"id"` - Name string `db:"name" json:"name"` - Address string `db:"address" json:"address"` - Email string `db:"email" json:"email"` - PhoneNumber string `db:"phone_number" json:"phoneNumber"` + ID string `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Address string `db:"address" json:"address"` + Email string `db:"email" json:"email"` + PhoneNumber string `db:"phone_number" json:"phoneNumber"` + Rentals []string `json:"rentals"` } diff --git a/bookish-server/renter/renter.go b/bookish-server/renter/renter.go index 2fd3363..c3ddefc 100644 --- a/bookish-server/renter/renter.go +++ b/bookish-server/renter/renter.go @@ -2,13 +2,15 @@ package renter import ( bookish "github.com/davidlick/bookish/bookish-server" + "github.com/davidlick/bookish/bookish-server/models" + "github.com/gobuffalo/uuid" ) // Storage defines behavior for interacting with the renter store. type Storage interface { - ListAll() (rr []bookish.Renter, err error) - FetchDetails(id int) (r bookish.Renter, err error) - New(name string, address string, email string, phoneNumber string) (id int, err error) + ListAll() (rr []models.Renter, err error) + FetchDetails(id string) (r models.Renter, err error) + New(name string, address string, email string, phoneNumber string) (id uuid.UUID, err error) } type service struct { @@ -18,8 +20,8 @@ type service struct { // Service defines behavior for interacting with the renter service. type Service interface { ListRenters() (rr []bookish.Renter, err error) - FetchRenter(id int) (r bookish.Renter, err error) - RegisterRenter(fullName string, address string, email string, phoneNumber string) (id int, err error) + FetchRenter(id string) (r bookish.Renter, err error) + RegisterRenter(fullName string, address string, email string, phoneNumber string) (id uuid.UUID, err error) } // NewService creates a new renter service. @@ -31,15 +33,50 @@ func NewService(s Storage) *service { // ListRenters returns all renters from the renter store. func (s *service) ListRenters() (rr []bookish.Renter, err error) { - return s.store.ListAll() + renters, err := s.store.ListAll() + if err != nil { + return nil, err + } + + for _, renter := range renters { + r := MutateRenterModel(renter) + rr = append(rr, r) + } + + return rr, nil } // FetchRenter returns a specific renter from the renter store. -func (s *service) FetchRenter(id int) (r bookish.Renter, err error) { - return s.store.FetchDetails(id) +func (s *service) FetchRenter(id string) (r bookish.Renter, err error) { + renter, err := s.store.FetchDetails(id) + if err != nil { + return r, err + } + + r = MutateRenterModel(renter) + return r, nil } // RegisterRenter registers a new renter in the renter store. -func (s *service) RegisterRenter(fullName string, address string, email string, phoneNumber string) (id int, err error) { +func (s *service) RegisterRenter(fullName string, address string, email string, phoneNumber string) (id uuid.UUID, err error) { return s.store.New(fullName, address, email, phoneNumber) } + +// MutateRenterModel is a convenience function to mutate a models.Renter into a bookish.Renter. +func MutateRenterModel(m models.Renter) bookish.Renter { + r := bookish.Renter{} + + r.ID = m.ID.String() + r.Name = m.Name + r.Address = m.Address + r.Email = m.Email + r.PhoneNumber = m.PhoneNumber + + for _, rental := range m.Rentals { + if !rental.ReturnDate.Valid { + r.Rentals = append(r.Rentals, rental.BookTitle) + } + } + + return r +} diff --git a/bookish-server/renter/renter_mock.go b/bookish-server/renter/renter_mock.go index bad9578..56de75e 100644 --- a/bookish-server/renter/renter_mock.go +++ b/bookish-server/renter/renter_mock.go @@ -1,11 +1,13 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ./renter.go +// Source: renter.go // Package renter is a generated GoMock package. package renter import ( bookish_server "github.com/davidlick/bookish/bookish-server" + models "github.com/davidlick/bookish/bookish-server/models" + uuid "github.com/gobuffalo/uuid" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -34,10 +36,10 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder { } // ListAll mocks base method -func (m *MockStorage) ListAll() ([]bookish_server.Renter, error) { +func (m *MockStorage) ListAll() ([]models.Renter, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAll") - ret0, _ := ret[0].([]bookish_server.Renter) + ret0, _ := ret[0].([]models.Renter) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -49,10 +51,10 @@ func (mr *MockStorageMockRecorder) ListAll() *gomock.Call { } // FetchDetails mocks base method -func (m *MockStorage) FetchDetails(id int) (bookish_server.Renter, error) { +func (m *MockStorage) FetchDetails(id string) (models.Renter, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FetchDetails", id) - ret0, _ := ret[0].(bookish_server.Renter) + ret0, _ := ret[0].(models.Renter) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -64,10 +66,10 @@ func (mr *MockStorageMockRecorder) FetchDetails(id interface{}) *gomock.Call { } // New mocks base method -func (m *MockStorage) New(name, address, email, phoneNumber string) (int, error) { +func (m *MockStorage) New(name, address, email, phoneNumber string) (uuid.UUID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "New", name, address, email, phoneNumber) - ret0, _ := ret[0].(int) + ret0, _ := ret[0].(uuid.UUID) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -77,3 +79,71 @@ func (mr *MockStorageMockRecorder) New(name, address, email, phoneNumber interfa mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStorage)(nil).New), name, address, email, phoneNumber) } + +// MockService is a mock of Service interface +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder +} + +// MockServiceMockRecorder is the mock recorder for MockService +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// ListRenters mocks base method +func (m *MockService) ListRenters() ([]bookish_server.Renter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRenters") + ret0, _ := ret[0].([]bookish_server.Renter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListRenters indicates an expected call of ListRenters +func (mr *MockServiceMockRecorder) ListRenters() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRenters", reflect.TypeOf((*MockService)(nil).ListRenters)) +} + +// FetchRenter mocks base method +func (m *MockService) FetchRenter(id string) (bookish_server.Renter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchRenter", id) + ret0, _ := ret[0].(bookish_server.Renter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchRenter indicates an expected call of FetchRenter +func (mr *MockServiceMockRecorder) FetchRenter(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchRenter", reflect.TypeOf((*MockService)(nil).FetchRenter), id) +} + +// RegisterRenter mocks base method +func (m *MockService) RegisterRenter(fullName, address, email, phoneNumber string) (uuid.UUID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterRenter", fullName, address, email, phoneNumber) + ret0, _ := ret[0].(uuid.UUID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RegisterRenter indicates an expected call of RegisterRenter +func (mr *MockServiceMockRecorder) RegisterRenter(fullName, address, email, phoneNumber interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRenter", reflect.TypeOf((*MockService)(nil).RegisterRenter), fullName, address, email, phoneNumber) +} diff --git a/bookish-server/renter/renter_test.go b/bookish-server/renter/renter_test.go index b4cf6aa..6f9bde3 100644 --- a/bookish-server/renter/renter_test.go +++ b/bookish-server/renter/renter_test.go @@ -5,6 +5,8 @@ import ( "testing" bookish "github.com/davidlick/bookish/bookish-server" + "github.com/davidlick/bookish/bookish-server/models" + "github.com/gofrs/uuid" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -12,12 +14,12 @@ import ( func TestRenter_ListRenters(t *testing.T) { testCases := []struct { TestName string - Renters []bookish.Renter + Renters []models.Renter Error error }{ { TestName: "success", - Renters: []bookish.Renter{ + Renters: []models.Renter{ { Name: "John Doe", Address: "123 Main Way", @@ -49,7 +51,13 @@ func TestRenter_ListRenters(t *testing.T) { service := NewService(mockStorage) renters, err := service.ListRenters() - assert.Equal(t, tc.Renters, renters) + var expectedRenters []bookish.Renter + for _, renter := range tc.Renters { + r := MutateRenterModel(renter) + expectedRenters = append(expectedRenters, r) + } + + assert.Equal(t, expectedRenters, renters) assert.Equal(t, tc.Error, err) }) } @@ -59,13 +67,12 @@ func TestRenter_FetchRenter(t *testing.T) { testCases := []struct { TestName string ID int - Renter bookish.Renter + Renter models.Renter Error error }{ { TestName: "success", - ID: 1, - Renter: bookish.Renter{ + Renter: models.Renter{ Name: "John Smith", Address: "123 Main Way", Email: "john@smith.com", @@ -74,7 +81,6 @@ func TestRenter_FetchRenter(t *testing.T) { }, { TestName: "error", - ID: 1, Error: errors.New("test error"), }, } @@ -84,13 +90,23 @@ func TestRenter_FetchRenter(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + renterId, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + mockStorage := NewMockStorage(ctrl) - mockStorage.EXPECT().FetchDetails(tc.ID).Return(tc.Renter, tc.Error) + mockStorage.EXPECT().FetchDetails(renterId.String()).Return(tc.Renter, tc.Error) service := NewService(mockStorage) - renter, err := service.FetchRenter(tc.ID) + renter, err := service.FetchRenter(renterId.String()) - assert.Equal(t, tc.Renter, renter) + expectedRenter := MutateRenterModel(tc.Renter) + if tc.Error != nil { + renter.ID = "00000000-0000-0000-0000-000000000000" + } + + assert.Equal(t, expectedRenter, renter) assert.Equal(t, tc.Error, err) }) } @@ -103,7 +119,6 @@ func TestRenter_RegisterRenter(t *testing.T) { Address string Email string PhoneNumber string - ReturnID int Error error }{ { @@ -116,13 +131,18 @@ func TestRenter_RegisterRenter(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() + returnUuid, err := uuid.NewV4() + if err != nil { + t.Error(err) + } + mockStorage := NewMockStorage(ctrl) - mockStorage.EXPECT().New(tc.Name, tc.Address, tc.Email, tc.PhoneNumber).Return(tc.ReturnID, tc.Error) + mockStorage.EXPECT().New(tc.Name, tc.Address, tc.Email, tc.PhoneNumber).Return(returnUuid, tc.Error) service := NewService(mockStorage) id, err := service.RegisterRenter(tc.Name, tc.Address, tc.Email, tc.PhoneNumber) - assert.Equal(t, tc.ReturnID, id) + assert.Equal(t, returnUuid, id) assert.Equal(t, tc.Error, err) }) } diff --git a/docker-compose.yml b/docker-compose.yml index bdbf9ff..4fcc7b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,14 +14,14 @@ services: - DBUSER=root - DBPASS=123pass - DBPORT=3306 - - BOOKSHOST= + - BOOKSHOST=https://servicepros-test-api.herokuapp.com db: - image: mysql - ports: - - 3306:3306 - restart: always - volumes: - - ./init:/docker-entrypoint-initdb.d - environment: - MYSQL_DATABASE: bookish - MYSQL_ROOT_PASSWORD: 123pass + image: mysql + ports: + - 3306:3306 + restart: always + volumes: + - ./init:/docker-entrypoint-initdb.d + environment: + MYSQL_DATABASE: bookish + MYSQL_ROOT_PASSWORD: 123pass diff --git a/init/bookish-ddl.sql b/init/bookish-ddl.sql deleted file mode 100644 index e664c34..0000000 --- a/init/bookish-ddl.sql +++ /dev/null @@ -1,21 +0,0 @@ -create table renters -( - id int auto_increment primary key, - name varchar(255) not null, - address varchar(255) not null, - email varchar(255) not null, - phone_number varchar(255) not null -); - -create unique index renters_id_uindex - on renters (id); - -create table rentals -( - id int auto_increment primary key, - renter_id int not null, - rental_date datetime not null, - return_date datetime null, - book_title varchar(255) not null, - foreign key (renter_id) references renters(id) -); diff --git a/init/schema.sql b/init/schema.sql new file mode 100644 index 0000000..53e095c --- /dev/null +++ b/init/schema.sql @@ -0,0 +1,22 @@ +create table if not exists renters +( + id varchar(255) not null primary key, + created_at datetime not null, + updated_at datetime not null, + name varchar(255) not null, + address varchar(255) not null, + email varchar(255) not null, + phone_number varchar(255) not null +); + +create table if not exists rentals +( + id varchar(255) not null primary key, + created_at datetime not null, + updated_at datetime not null, + book_title varchar(255) not null, + renter_id varchar(255) not null, + rental_date datetime not null, + return_date datetime null, + foreign key (renter_id) references renters(id) +);