From f59593e9e8089c7e7c82fdea9161bd98357d03bd Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 11:17:03 -0600 Subject: [PATCH] adds create user func --- go.mod | 1 + models/err_model_test.go | 4 ++ models/models.go | 1 + models/models_test.go | 4 ++ models/user.go | 40 +++++++++++++++++ models/user_test.go | 49 +++++++++++++++++---- routes/route_model_test.go | 12 +++++ store/postgres.go | 16 +++++++ store/postgres_user_test.go | 88 +++++++++++++++++++++++++++++++++++++ 9 files changed, 207 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 52b1f71..796889c 100644 --- a/go.mod +++ b/go.mod @@ -10,4 +10,5 @@ require ( github.com/jmoiron/sqlx v1.2.0 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.5.1 + golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 ) diff --git a/models/err_model_test.go b/models/err_model_test.go index 04639f3..c48aa9e 100644 --- a/models/err_model_test.go +++ b/models/err_model_test.go @@ -40,6 +40,10 @@ func (e *errorStore) SelectUserByID(id int) (*models.User, error) { return nil, e.error } +func (e *errorStore) InsertUser(user *models.User) (int, error) { + return 0, e.error +} + func (e *errorStore) ConnectionLive() error { return e.error } diff --git a/models/models.go b/models/models.go index 8f3842f..31f346e 100644 --- a/models/models.go +++ b/models/models.go @@ -16,6 +16,7 @@ type Store interface { InsertPlan(plan *Plan) (int, error) SelectActionsByPlanID(plan *Plan) ([]*Action, error) SelectUserByID(id int) (*User, error) + InsertUser(user *User) (int, error) } // Model represents a current model item. diff --git a/models/models_test.go b/models/models_test.go index c2ff6b4..00a5990 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -47,6 +47,10 @@ func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { return &models.User{UserID: int64(id), Username: "test", DisplayName: "Ted Est", Password: []byte("oh no")}, nil } +func (ms *multiStore) InsertUser(user *models.User) (int, error) { + return int(user.UserID), nil +} + func (ms *multiStore) ConnectionLive() error { return nil } diff --git a/models/user.go b/models/user.go index bc35ae6..3f4aebe 100644 --- a/models/user.go +++ b/models/user.go @@ -1,5 +1,10 @@ package models +import ( + "fmt" + "golang.org/x/crypto/bcrypt" +) + // User represents the full DB user field, for inserts and compares. // No reason to return the hashed pw on the route though. type User struct { @@ -34,3 +39,38 @@ func (u *User) NoPassword() *UserNoPassword { DisplayName: u.DisplayName, } } + +// CreateUserRequest represents a desired user creation. +type CreateUserRequest struct { + Username string `json:"username"` + DisplayName string `json:"display_name"` + Password string `json:"password"` +} + +// CreateUser takes in a create user request and returns the ID of the newly created user. +func (m *Model) CreateUser(req *CreateUserRequest) (int, error) { + if req.Username == "" { + return -1, fmt.Errorf("No username provided") + } + if req.Password == "" { + return -1, fmt.Errorf("No password provided") + } + hash, err := hashPassword(req.Password) + if err != nil { + return -1, err + } + + desiredUser := &User{ + Username: req.Username, + DisplayName: req.DisplayName, + Password: hash, + } + + return m.InsertUser(desiredUser) +} + +// hashPassword hashes a password +func hashPassword(password string) ([]byte, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11) + return bytes, err +} diff --git a/models/user_test.go b/models/user_test.go index bcd2678..4a55968 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -31,16 +31,49 @@ func TestErrorUsers(t *testing.T) { assert.NotNil(err) } -func TestUserNoPassword(t *testing.T) { +func TestCreateUser(t *testing.T) { assert := assert.New(t) - id := int64(3) username := "test" displayName := "Ted Est" - pass := []byte("abc") - u := &models.User{UserID: id, Username: username, DisplayName: displayName, Password: pass} + pass := "abc" + u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} - unp := u.NoPassword() - assert.EqualValues(id, unp.UserID) - assert.Equal(username, unp.Username) - assert.Equal(displayName, unp.DisplayName) + ss := &multiStore{ + []*models.Action{}, + []*models.Plan{}} + m := models.New(ss) + + _, err := m.CreateUser(u) + assert.Nil(err) +} +func TestCreateUserFailValidation(t *testing.T) { + assert := assert.New(t) + username := "" + displayName := "Ted Est" + pass := "abc" + u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} + + ss := &multiStore{ + []*models.Action{}, + []*models.Plan{}} + m := models.New(ss) + + _, err := m.CreateUser(u) + assert.NotNil(err) +} + +func TestCreateUserFailValidationPassword(t *testing.T) { + assert := assert.New(t) + username := "aoeu" + displayName := "Ted Est" + pass := "" + u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} + + ss := &multiStore{ + []*models.Action{}, + []*models.Plan{}} + m := models.New(ss) + + _, err := m.CreateUser(u) + assert.NotNil(err) } diff --git a/routes/route_model_test.go b/routes/route_model_test.go index 59afdf4..599712f 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -62,6 +62,10 @@ func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { return nil, nil } +func (ms *multiStore) InsertUser(user *models.User) (int, error) { + return int(user.UserID), nil +} + func (ms *multiStore) ConnectionLive() error { return nil } @@ -119,6 +123,10 @@ func (e *errorStore) SelectUserByID(id int) (*models.User, error) { return nil, e.error } +func (e *errorStore) InsertUser(user *models.User) (int, error) { + return 0, e.error +} + func (e *errorStore) ConnectionLive() error { return e.error } @@ -168,6 +176,10 @@ func (e *onlyCreateStore) SelectUserByID(id int) (*models.User, error) { return nil, nil } +func (e *onlyCreateStore) InsertUser(user *models.User) (int, error) { + return 0, e.error +} + func (e *onlyCreateStore) ConnectionLive() error { return e.error } diff --git a/store/postgres.go b/store/postgres.go index 9f83e2d..0c25a5d 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -143,3 +143,19 @@ func (store *postgresStore) SelectUserByID(id int) (*models.User, error) { } return &user, nil } + +func (store *postgresStore) InsertUser(user *models.User) (int, error) { + queryString := store.db.Rebind("INSERT INTO users (username, display_name, password) VALUES (?, ?, ?) RETURNING user_id") + tx := store.db.MustBegin() + var id int + err := tx.Get(&id, queryString, user.Username, user.DisplayName, user.Password) + if err != nil { + tx.Rollback() + return -1, err + } + err = tx.Commit() + if err != nil { + return -1, err + } + return id, nil +} diff --git a/store/postgres_user_test.go b/store/postgres_user_test.go index ecf9783..9fb70c4 100644 --- a/store/postgres_user_test.go +++ b/store/postgres_user_test.go @@ -2,6 +2,7 @@ package store_test import ( "fmt" + "gitea.deepak.science/deepak/gogmagog/models" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" "testing" @@ -62,3 +63,90 @@ func TestErrUserByID(t *testing.T) { t.Errorf("unfulfilled expectations: %s", err) } } + +func TestInsertUser(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + username := "test" + displayName := "Tom Est" + password := []byte("ABC€") + usr := &models.User{Username: username, DisplayName: displayName, Password: password} + + idToUse := 8 + + rows := sqlmock.NewRows([]string{"user_id"}).AddRow(8) + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`). + WithArgs(username, displayName, password). + WillReturnRows(rows) + mock.ExpectCommit() + + // function under test + insertedId, err := str.InsertUser(usr) + // check results + assert.Nil(err) + assert.EqualValues(idToUse, insertedId) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +} + +func TestInsertUserErr(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + username := "test" + displayName := "Tom Est" + password := []byte("ABC€") + usr := &models.User{Username: username, DisplayName: displayName, Password: password} + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`). + WithArgs(username, displayName, password). + WillReturnError(fmt.Errorf("example error")) + mock.ExpectRollback() + + // function under test + _, err := str.InsertUser(usr) + // check results + assert.NotNil(err) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +} + +func TestInsertUserCommitErr(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + username := "test" + displayName := "Tom Est" + password := []byte("ABC€") + usr := &models.User{Username: username, DisplayName: displayName, Password: password} + + idToUse := 8 + + rows := sqlmock.NewRows([]string{"user_id"}).AddRow(idToUse) + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO users \(username, display_name, password\) VALUES \(\$1, \$2, \$3\) RETURNING user_id$`). + WithArgs(username, displayName, password). + WillReturnRows(rows) + mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example")) + + // function under test + _, err := str.InsertUser(usr) + // check results + assert.NotNil(err) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +}