From 3ea8603368eee0b7faf8848bcc6f39ff552ad86e Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 11 Jan 2021 20:15:36 -0600 Subject: [PATCH 01/32] Adds user table and adds support for config flag to drop DB on restart --- config/config-sample.yaml | 1 + config/config.go | 26 ++++---- config/config_test.go | 2 + models/err_model_test.go | 4 ++ models/models.go | 1 + models/models_test.go | 4 ++ models/user.go | 21 ++++++ models/user_test.go | 22 +++++++ routes/route_model_test.go | 12 ++++ .../000001_create_action_table.down.sql | 1 + .../000001_create_action_table.up.sql | 14 ++++ store/postgres.go | 9 +++ store/postgres_user_test.go | 64 +++++++++++++++++++ store/store.go | 6 ++ 14 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 models/user.go create mode 100644 models/user_test.go create mode 100644 store/postgres_user_test.go diff --git a/config/config-sample.yaml b/config/config-sample.yaml index 07a78de..e481575 100644 --- a/config/config-sample.yaml +++ b/config/config-sample.yaml @@ -9,3 +9,4 @@ db: user: USER password: PASSWORD database: g2 + droponstart: true # don't use this in production! diff --git a/config/config.go b/config/config.go index 599550e..25a4ff8 100644 --- a/config/config.go +++ b/config/config.go @@ -15,12 +15,13 @@ type AppConfig struct { // DBConfig is the config for the DB connection. type DBConfig struct { - Type string - Host string - Port string - User string - Password string - Database string + Type string + Host string + Port string + User string + Password string + Database string + DropOnStart bool } // Conf represents the overall configuration of the application. @@ -37,12 +38,13 @@ func createDefaultConf() *Conf { Timezone: "America/New_York", }, Db: DBConfig{ - Type: "postgres", - Host: "localhost", - Port: "5432", - User: "", - Password: "", - Database: "gogmagog", + Type: "postgres", + Host: "localhost", + Port: "5432", + User: "", + Password: "", + Database: "gogmagog", + DropOnStart: false, }, } diff --git a/config/config_test.go b/config/config_test.go index 13c1e83..8697ea0 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -25,6 +25,7 @@ func TestSample(t *testing.T) { assert.Equal("USER", dbConf.User) assert.Equal("PASSWORD", dbConf.Password) assert.Equal("g2", dbConf.Database) + assert.True(dbConf.DropOnStart) } func TestDefault(t *testing.T) { @@ -45,6 +46,7 @@ func TestDefault(t *testing.T) { assert.Equal("", dbConf.User) assert.Equal("", dbConf.Password) assert.Equal("gogmagog", dbConf.Database) + assert.False(dbConf.DropOnStart) } func TestMissingFile(t *testing.T) { diff --git a/models/err_model_test.go b/models/err_model_test.go index 27c357a..04639f3 100644 --- a/models/err_model_test.go +++ b/models/err_model_test.go @@ -36,6 +36,10 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, return nil, e.error } +func (e *errorStore) SelectUserByID(id int) (*models.User, error) { + return nil, e.error +} + func (e *errorStore) ConnectionLive() error { return e.error } diff --git a/models/models.go b/models/models.go index d25a1ac..8f3842f 100644 --- a/models/models.go +++ b/models/models.go @@ -15,6 +15,7 @@ type Store interface { SelectPlanByID(id int) (*Plan, error) InsertPlan(plan *Plan) (int, error) SelectActionsByPlanID(plan *Plan) ([]*Action, error) + SelectUserByID(id int) (*User, error) } // Model represents a current model item. diff --git a/models/models_test.go b/models/models_test.go index 7cb62e8..c9bc9f5 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -43,6 +43,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action return ms.actions, nil } +func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { + return nil, nil +} + func (ms *multiStore) ConnectionLive() error { return nil } diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..f3e1de3 --- /dev/null +++ b/models/user.go @@ -0,0 +1,21 @@ +package models + +// 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 { + UserID int64 `json:"user_id"` + Username string `json:"username"` + DisplayName string `json:"display_name"` + Password []byte `json:"password"` +} + +// User returns a single plan from the store by plan_id. +func (m *Model) User(id int) (*User, error) { + user, err := m.SelectUserByID(id) + return user, wrapNotFound(err) +} + +// AddUser inserts a user into the store. +// func (m *Model) AddUser(u *User) (int, error) { +// return m.InsertUser(u) +// } diff --git a/models/user_test.go b/models/user_test.go new file mode 100644 index 0000000..b76f0ae --- /dev/null +++ b/models/user_test.go @@ -0,0 +1,22 @@ +package models_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestModelUsers(t *testing.T) { + assert := assert.New(t) + a1 := &models.Action{ActionID: 3} + a2 := &models.Action{ActionID: 4} + p := &models.Plan{PlanID: 6} + ss := &multiStore{ + []*models.Action{a1, a2}, + []*models.Plan{p}} + m := models.New(ss) + + user, err := m.User(3) + assert.Nil(err) + assert.Nil(user) +} diff --git a/routes/route_model_test.go b/routes/route_model_test.go index d0d98f8..59afdf4 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -58,6 +58,10 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action return ms.actions, nil } +func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { + return nil, nil +} + func (ms *multiStore) ConnectionLive() error { return nil } @@ -111,6 +115,10 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, return nil, e.error } +func (e *errorStore) SelectUserByID(id int) (*models.User, error) { + return nil, e.error +} + func (e *errorStore) ConnectionLive() error { return e.error } @@ -156,6 +164,10 @@ func (e *onlyCreateStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Ac return nil, e.error } +func (e *onlyCreateStore) SelectUserByID(id int) (*models.User, error) { + return nil, nil +} + func (e *onlyCreateStore) ConnectionLive() error { return e.error } diff --git a/store/migrations/000001_create_action_table.down.sql b/store/migrations/000001_create_action_table.down.sql index 0b978f2..6a2c85b 100644 --- a/store/migrations/000001_create_action_table.down.sql +++ b/store/migrations/000001_create_action_table.down.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS actions; DROP TABLE IF EXISTS plans; +DROP TABLE IF EXISTS users; DROP FUNCTION IF EXISTS trigger_set_timestamp; diff --git a/store/migrations/000001_create_action_table.up.sql b/store/migrations/000001_create_action_table.up.sql index b00d482..d6e640a 100644 --- a/store/migrations/000001_create_action_table.up.sql +++ b/store/migrations/000001_create_action_table.up.sql @@ -16,6 +16,15 @@ CREATE TABLE IF NOT EXISTS actions( plan_id int REFERENCES plans(plan_id) ); +CREATE TABLE IF NOT EXISTS users( + user_id serial PRIMARY KEY, + username VARCHAR(50) NOT NULL, + display_name VARCHAR (100) NOT NULL, + password bytea, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + CREATE OR REPLACE FUNCTION trigger_set_timestamp() RETURNS TRIGGER AS $set_updated$ @@ -34,3 +43,8 @@ CREATE TRIGGER set_updated BEFORE UPDATE ON plans FOR EACH ROW EXECUTE PROCEDURE trigger_set_timestamp(); + +CREATE TRIGGER set_updated +BEFORE UPDATE ON users +FOR EACH ROW +EXECUTE PROCEDURE trigger_set_timestamp(); diff --git a/store/postgres.go b/store/postgres.go index a443f40..9f83e2d 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -134,3 +134,12 @@ func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) { func (store *postgresStore) ConnectionLive() error { return store.db.Ping() } + +func (store *postgresStore) SelectUserByID(id int) (*models.User, error) { + user := models.User{} + err := store.db.Get(&user, store.db.Rebind("SELECT user_id, username, display_name, password FROM users WHERE user_id = ?"), id) + if err != nil { + return nil, err + } + return &user, nil +} diff --git a/store/postgres_user_test.go b/store/postgres_user_test.go new file mode 100644 index 0000000..ecf9783 --- /dev/null +++ b/store/postgres_user_test.go @@ -0,0 +1,64 @@ +package store_test + +import ( + "fmt" + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSelectUserById(t *testing.T) { + // set up test + assert := assert.New(t) + + id := 1 + username := "test" + displayName := "Tom Est" + password := []byte("ABC€") + + str, mock := getDbMock(t) + + rows := sqlmock.NewRows([]string{ + "user_id", + "username", + "display_name", + "password", + }). + AddRow(id, username, displayName, password) + + mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE user_id = \$1`). + WithArgs(id). + WillReturnRows(rows) + + // function under test + user, err := str.SelectUserByID(1) + + // test results + assert.Nil(err) + assert.EqualValues(id, user.UserID) + assert.Equal(username, user.Username) + assert.Equal(displayName, user.DisplayName) + assert.Equal(password, user.Password) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} + +func TestErrUserByID(t *testing.T) { + assert := assert.New(t) + + idToUse := 1 + + str, mock := getDbMock(t) + + mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE user_id = \$1`).WithArgs(idToUse).WillReturnError(fmt.Errorf("example error")) + + user, err := str.SelectUserByID(idToUse) + assert.NotNil(err) + assert.Nil(user) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} diff --git a/store/store.go b/store/store.go index 97f87fb..3e1036a 100644 --- a/store/store.go +++ b/store/store.go @@ -59,6 +59,10 @@ func createPostgresDB(dbConf *config.DBConfig) (*sqlx.DB, error) { log.Print("Could not perform migration", err) return nil, err } + if dbConf.DropOnStart { + log.Print("Going down") + m.Down() + } if err := m.Up(); err != nil { if err == migrate.ErrNoChange { log.Print("No migration needed.") @@ -66,6 +70,8 @@ func createPostgresDB(dbConf *config.DBConfig) (*sqlx.DB, error) { log.Printf("An error occurred while syncing the database.. %v", err) return nil, err } + } else { + log.Print("Performed database migration") } return db, nil } From 1d456355307b8c382cb7638d5ec3578473539771 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 11 Jan 2021 20:37:35 -0600 Subject: [PATCH 02/32] adds no password return by default, model cares about password but nothing downstream does --- models/models_test.go | 2 +- models/user.go | 26 +++++++++++++++++++------- models/user_test.go | 26 +++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/models/models_test.go b/models/models_test.go index c9bc9f5..c2ff6b4 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -44,7 +44,7 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action } func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { - return nil, nil + return &models.User{UserID: int64(id), Username: "test", DisplayName: "Ted Est", Password: []byte("oh no")}, nil } func (ms *multiStore) ConnectionLive() error { diff --git a/models/user.go b/models/user.go index f3e1de3..03fa471 100644 --- a/models/user.go +++ b/models/user.go @@ -3,19 +3,31 @@ package models // 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 { + UserID int64 + Username string + DisplayName string + Password []byte +} + +type UserNoPassword struct { UserID int64 `json:"user_id"` Username string `json:"username"` DisplayName string `json:"display_name"` - Password []byte `json:"password"` } // User returns a single plan from the store by plan_id. -func (m *Model) User(id int) (*User, error) { +func (m *Model) User(id int) (*UserNoPassword, error) { user, err := m.SelectUserByID(id) - return user, wrapNotFound(err) + if user == nil { + return nil, wrapNotFound(err) + } + return user.NoPassword(), wrapNotFound(err) } -// AddUser inserts a user into the store. -// func (m *Model) AddUser(u *User) (int, error) { -// return m.InsertUser(u) -// } +func (u *User) NoPassword() *UserNoPassword { + return &UserNoPassword{ + UserID: u.UserID, + Username: u.Username, + DisplayName: u.DisplayName, + } +} diff --git a/models/user_test.go b/models/user_test.go index b76f0ae..bab25c6 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -3,6 +3,7 @@ package models_test import ( "gitea.deepak.science/deepak/gogmagog/models" "github.com/stretchr/testify/assert" + "fmt" "testing" ) @@ -18,5 +19,28 @@ func TestModelUsers(t *testing.T) { user, err := m.User(3) assert.Nil(err) - assert.Nil(user) + assert.NotNil(user) +} + +func TestErrorUsers(t *testing.T) { + assert := assert.New(t) + m := getErrorModel(fmt.Errorf("err")) + + user, err := m.User(3) + assert.Nil(user) + assert.NotNil(err) +} + +func TestUserNoPassword(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} + + unp := u.NoPassword() + assert.EqualValues(id, unp.UserID) + assert.Equal(username, unp.Username) + assert.Equal(displayName, unp.DisplayName) } From 55c8c739b02ee896c41d5a76c332a89b45a297df Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 11 Jan 2021 20:38:11 -0600 Subject: [PATCH 03/32] fmt A --- models/user_test.go | 2 +- routes/users.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 routes/users.go diff --git a/models/user_test.go b/models/user_test.go index bab25c6..bcd2678 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -1,9 +1,9 @@ package models_test import ( + "fmt" "gitea.deepak.science/deepak/gogmagog/models" "github.com/stretchr/testify/assert" - "fmt" "testing" ) diff --git a/routes/users.go b/routes/users.go new file mode 100644 index 0000000..3c3ca77 --- /dev/null +++ b/routes/users.go @@ -0,0 +1,40 @@ +package routes + +import ( + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/go-chi/chi" + "net/http" + "strconv" +) + +func newUserRouter(m *models.Model) http.Handler { + router := chi.NewRouter() + // router.Post("/", postUserFunc(m)) + router.Get("/{userid}", getUserByIDFunc(m)) + return router +} + +func getUserByIDFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(chi.URLParam(r, "userid")) + if err != nil { + notFoundHandler(w, r) + return + } + user, err := m.User(id) + if err != nil { + if models.IsNotFoundError(err) { + notFoundHandler(w, r) + return + } + serverError(w, err) + return + + } + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(user); err != nil { + serverError(w, err) + } + } +} From c8e33884c07767dfeecb66c19d5ba864f819c086 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 11 Jan 2021 20:39:29 -0600 Subject: [PATCH 04/32] fixes lint issues --- models/user.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/user.go b/models/user.go index 03fa471..bc35ae6 100644 --- a/models/user.go +++ b/models/user.go @@ -9,6 +9,8 @@ type User struct { Password []byte } +// UserNoPassword contains the non password user fields. +// This is preferred outside of the model / store. type UserNoPassword struct { UserID int64 `json:"user_id"` Username string `json:"username"` @@ -24,6 +26,7 @@ func (m *Model) User(id int) (*UserNoPassword, error) { return user.NoPassword(), wrapNotFound(err) } +// NoPassword strips the user of password. func (u *User) NoPassword() *UserNoPassword { return &UserNoPassword{ UserID: u.UserID, From f59593e9e8089c7e7c82fdea9161bd98357d03bd Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 11:17:03 -0600 Subject: [PATCH 05/32] 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) + } + +} From c8b8f87f6c552a82772e8c7ccaa7706737a993ec Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 11:36:44 -0600 Subject: [PATCH 06/32] Change to select by username because that makes more sense --- models/err_model_test.go | 2 +- models/models.go | 2 +- models/models_test.go | 4 ++-- models/user.go | 6 +++--- models/user_test.go | 4 ++-- routes/route_model_test.go | 6 +++--- routes/users.go | 14 +++++--------- .../migrations/000001_create_action_table.up.sql | 2 +- store/postgres.go | 4 ++-- store/postgres_user_test.go | 16 +++++++--------- 10 files changed, 27 insertions(+), 33 deletions(-) diff --git a/models/err_model_test.go b/models/err_model_test.go index c48aa9e..db1f899 100644 --- a/models/err_model_test.go +++ b/models/err_model_test.go @@ -36,7 +36,7 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, return nil, e.error } -func (e *errorStore) SelectUserByID(id int) (*models.User, error) { +func (e *errorStore) SelectUserByUsername(username string) (*models.User, error) { return nil, e.error } diff --git a/models/models.go b/models/models.go index 31f346e..586141f 100644 --- a/models/models.go +++ b/models/models.go @@ -15,7 +15,7 @@ type Store interface { SelectPlanByID(id int) (*Plan, error) InsertPlan(plan *Plan) (int, error) SelectActionsByPlanID(plan *Plan) ([]*Action, error) - SelectUserByID(id int) (*User, error) + SelectUserByUsername(username string) (*User, error) InsertUser(user *User) (int, error) } diff --git a/models/models_test.go b/models/models_test.go index 00a5990..b9693b2 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -43,8 +43,8 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action return ms.actions, nil } -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) SelectUserByUsername(username string) (*models.User, error) { + return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("oh no")}, nil } func (ms *multiStore) InsertUser(user *models.User) (int, error) { diff --git a/models/user.go b/models/user.go index 3f4aebe..f19c882 100644 --- a/models/user.go +++ b/models/user.go @@ -22,9 +22,9 @@ type UserNoPassword struct { DisplayName string `json:"display_name"` } -// User returns a single plan from the store by plan_id. -func (m *Model) User(id int) (*UserNoPassword, error) { - user, err := m.SelectUserByID(id) +// UserByUsername returns a single user by the unique username. +func (m *Model) UserByUsername(username string) (*UserNoPassword, error) { + user, err := m.SelectUserByUsername(username) if user == nil { return nil, wrapNotFound(err) } diff --git a/models/user_test.go b/models/user_test.go index 4a55968..be4226e 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -17,7 +17,7 @@ func TestModelUsers(t *testing.T) { []*models.Plan{p}} m := models.New(ss) - user, err := m.User(3) + user, err := m.UserByUsername("test") assert.Nil(err) assert.NotNil(user) } @@ -26,7 +26,7 @@ func TestErrorUsers(t *testing.T) { assert := assert.New(t) m := getErrorModel(fmt.Errorf("err")) - user, err := m.User(3) + user, err := m.UserByUsername("snth") assert.Nil(user) assert.NotNil(err) } diff --git a/routes/route_model_test.go b/routes/route_model_test.go index 599712f..368e80e 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -58,7 +58,7 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action return ms.actions, nil } -func (ms *multiStore) SelectUserByID(id int) (*models.User, error) { +func (ms *multiStore) SelectUserByUsername(name string) (*models.User, error) { return nil, nil } @@ -119,7 +119,7 @@ func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, return nil, e.error } -func (e *errorStore) SelectUserByID(id int) (*models.User, error) { +func (e *errorStore) SelectUserByUsername(name string) (*models.User, error) { return nil, e.error } @@ -172,7 +172,7 @@ func (e *onlyCreateStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Ac return nil, e.error } -func (e *onlyCreateStore) SelectUserByID(id int) (*models.User, error) { +func (e *onlyCreateStore) SelectUserByUsername(name string) (*models.User, error) { return nil, nil } diff --git a/routes/users.go b/routes/users.go index 3c3ca77..e59bd39 100644 --- a/routes/users.go +++ b/routes/users.go @@ -5,24 +5,20 @@ import ( "gitea.deepak.science/deepak/gogmagog/models" "github.com/go-chi/chi" "net/http" - "strconv" ) func newUserRouter(m *models.Model) http.Handler { router := chi.NewRouter() // router.Post("/", postUserFunc(m)) - router.Get("/{userid}", getUserByIDFunc(m)) + router.Get("/{username}", getUserByUsernameFunc(m)) return router } -func getUserByIDFunc(m *models.Model) http.HandlerFunc { +func getUserByUsernameFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - id, err := strconv.Atoi(chi.URLParam(r, "userid")) - if err != nil { - notFoundHandler(w, r) - return - } - user, err := m.User(id) + username := chi.URLParam(r, "username") + + user, err := m.UserByUsername(username) if err != nil { if models.IsNotFoundError(err) { notFoundHandler(w, r) diff --git a/store/migrations/000001_create_action_table.up.sql b/store/migrations/000001_create_action_table.up.sql index d6e640a..9b48f0b 100644 --- a/store/migrations/000001_create_action_table.up.sql +++ b/store/migrations/000001_create_action_table.up.sql @@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS actions( CREATE TABLE IF NOT EXISTS users( user_id serial PRIMARY KEY, - username VARCHAR(50) NOT NULL, + username VARCHAR(50) NOT NULL UNIQUE, display_name VARCHAR (100) NOT NULL, password bytea, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, diff --git a/store/postgres.go b/store/postgres.go index 0c25a5d..271afc5 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -135,9 +135,9 @@ func (store *postgresStore) ConnectionLive() error { return store.db.Ping() } -func (store *postgresStore) SelectUserByID(id int) (*models.User, error) { +func (store *postgresStore) SelectUserByUsername(username string) (*models.User, error) { user := models.User{} - err := store.db.Get(&user, store.db.Rebind("SELECT user_id, username, display_name, password FROM users WHERE user_id = ?"), id) + err := store.db.Get(&user, store.db.Rebind("SELECT user_id, username, display_name, password FROM users WHERE username = ?"), username) if err != nil { return nil, err } diff --git a/store/postgres_user_test.go b/store/postgres_user_test.go index 9fb70c4..83a5d55 100644 --- a/store/postgres_user_test.go +++ b/store/postgres_user_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func TestSelectUserById(t *testing.T) { +func TestSelectUserByUsername(t *testing.T) { // set up test assert := assert.New(t) @@ -27,12 +27,12 @@ func TestSelectUserById(t *testing.T) { }). AddRow(id, username, displayName, password) - mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE user_id = \$1`). - WithArgs(id). + mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE username = \$1`). + WithArgs(username). WillReturnRows(rows) // function under test - user, err := str.SelectUserByID(1) + user, err := str.SelectUserByUsername(username) // test results assert.Nil(err) @@ -49,13 +49,11 @@ func TestSelectUserById(t *testing.T) { func TestErrUserByID(t *testing.T) { assert := assert.New(t) - idToUse := 1 - str, mock := getDbMock(t) + username := "snth" + mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE username = \$1`).WithArgs(username).WillReturnError(fmt.Errorf("example error")) - mock.ExpectQuery(`^SELECT user_id, username, display_name, password FROM users WHERE user_id = \$1`).WithArgs(idToUse).WillReturnError(fmt.Errorf("example error")) - - user, err := str.SelectUserByID(idToUse) + user, err := str.SelectUserByUsername(username) assert.NotNil(err) assert.Nil(user) From 262321a1e2c503ac2b16611d1aef8fe6d7dd1088 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 12:43:45 -0600 Subject: [PATCH 07/32] Adds auth route that checks username and password --- models/errors.go | 25 +++++++++++ models/errors_test.go | 7 ++++ models/models_test.go | 3 +- models/user.go | 18 +++++--- models/user_test.go | 8 +++- routes/auth.go | 96 +++++++++++++++++++++++++++++++++++++++++++ routes/errors.go | 5 +++ routes/routes.go | 1 + routes/users.go | 36 ---------------- 9 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 routes/auth.go delete mode 100644 routes/users.go diff --git a/models/errors.go b/models/errors.go index 8274dc4..6d071fb 100644 --- a/models/errors.go +++ b/models/errors.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "golang.org/x/crypto/bcrypt" ) type notFoundError struct { @@ -27,3 +28,27 @@ func wrapNotFound(err error) error { } return err } + +type invalidLoginError struct { + error +} + +func (e *invalidLoginError) InvalidLogin() bool { + return true +} + +// IsInvalidLoginError returns true if the model deems it an invalid login error. +func IsInvalidLoginError(err error) bool { + type invalidLogin interface { + InvalidLogin() bool + } + te, ok := err.(invalidLogin) + return ok && te.InvalidLogin() +} + +func wrapInvalidLogin(err error) error { + if err == sql.ErrNoRows || err == bcrypt.ErrMismatchedHashAndPassword { + return &invalidLoginError{error: err} + } + return err +} diff --git a/models/errors_test.go b/models/errors_test.go index 88baab0..20b0749 100644 --- a/models/errors_test.go +++ b/models/errors_test.go @@ -40,3 +40,10 @@ func TestErrorModelWrapping(t *testing.T) { _, err = m.Action(0) assert.True(models.IsNotFoundError(err)) } +func TestErrorModelInvalidLogin(t *testing.T) { + assert := assert.New(t) + m := getErrorModel(sql.ErrNoRows) + + _, err := m.VerifyUserByUsernamePassword("duck", "duck") + assert.True(models.IsInvalidLoginError(err)) +} diff --git a/models/models_test.go b/models/models_test.go index b9693b2..3c4fc7f 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -44,7 +44,8 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action } func (ms *multiStore) SelectUserByUsername(username string) (*models.User, error) { - return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("oh no")}, nil + // password is "password" + return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")}, nil } func (ms *multiStore) InsertUser(user *models.User) (int, error) { diff --git a/models/user.go b/models/user.go index f19c882..bcadb10 100644 --- a/models/user.go +++ b/models/user.go @@ -22,13 +22,21 @@ type UserNoPassword struct { DisplayName string `json:"display_name"` } -// UserByUsername returns a single user by the unique username. -func (m *Model) UserByUsername(username string) (*UserNoPassword, error) { +// VerifyUserByUsernamePassword returns a single user by the unique username, if the provided password is correct. +func (m *Model) VerifyUserByUsernamePassword(username string, password string) (*UserNoPassword, error) { user, err := m.SelectUserByUsername(username) - if user == nil { - return nil, wrapNotFound(err) + if err != nil { + // throwaway to pad time + hashPassword(username) + return nil, wrapInvalidLogin(err) } - return user.NoPassword(), wrapNotFound(err) + + err = bcrypt.CompareHashAndPassword(user.Password, []byte(password)) + if err != nil { + return nil, wrapInvalidLogin(err) + } + + return user.NoPassword(), nil } // NoPassword strips the user of password. diff --git a/models/user_test.go b/models/user_test.go index be4226e..eb58432 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -17,16 +17,20 @@ func TestModelUsers(t *testing.T) { []*models.Plan{p}} m := models.New(ss) - user, err := m.UserByUsername("test") + user, err := m.VerifyUserByUsernamePassword("test", "password") assert.Nil(err) assert.NotNil(user) + + user, err = m.VerifyUserByUsernamePassword("test", "wrong_password") + assert.NotNil(err) + assert.Nil(user) } func TestErrorUsers(t *testing.T) { assert := assert.New(t) m := getErrorModel(fmt.Errorf("err")) - user, err := m.UserByUsername("snth") + user, err := m.VerifyUserByUsernamePassword("snth", "aoeu") assert.Nil(user) assert.NotNil(err) } diff --git a/routes/auth.go b/routes/auth.go new file mode 100644 index 0000000..232846a --- /dev/null +++ b/routes/auth.go @@ -0,0 +1,96 @@ +package routes + +import ( + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/go-chi/chi" + "io" + "net/http" +) + +func newAuthRouter(m *models.Model) http.Handler { + router := chi.NewRouter() + router.Post("/register", postUserFunc(m)) + router.Post("/tokens", createTokenFunc(m)) + return router +} + +type createUserResponse struct { + Username string `json:"username"` +} + +func postUserFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + r.Body = http.MaxBytesReader(w, r.Body, 1024) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + var req models.CreateUserRequest + err := dec.Decode(&req) + if err != nil { + badRequestError(w, err) + return + } + err = dec.Decode(&struct{}{}) + if err != io.EOF { + badRequestError(w, err) + return + } + + _, err = m.CreateUser(&req) + if err != nil { + serverError(w, err) + return + } + + response := &createUserResponse{ + Username: req.Username, + } + w.WriteHeader(http.StatusCreated) + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(response); err != nil { + serverError(w, err) + } + + } +} + +type loginCreds struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func createTokenFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + r.Body = http.MaxBytesReader(w, r.Body, 1024) + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + var creds loginCreds + err := dec.Decode(&creds) + if err != nil { + badRequestError(w, err) + return + } + err = dec.Decode(&struct{}{}) + if err != io.EOF { + badRequestError(w, err) + return + } + + user, err := m.VerifyUserByUsernamePassword(creds.Username, creds.Password) + if err != nil { + if models.IsInvalidLoginError(err) { + unauthorizedHandler(w, r) + return + } + serverError(w, err) + return + + } + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(user); err != nil { + serverError(w, err) + } + } +} diff --git a/routes/errors.go b/routes/errors.go index a43793a..43a457a 100644 --- a/routes/errors.go +++ b/routes/errors.go @@ -25,3 +25,8 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) { code := http.StatusNotFound http.Error(w, http.StatusText(code), code) } + +func unauthorizedHandler(w http.ResponseWriter, r *http.Request) { + code := http.StatusUnauthorized + http.Error(w, http.StatusText(code), code) +} diff --git a/routes/routes.go b/routes/routes.go index 57e189e..e1b6c22 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -14,6 +14,7 @@ func NewRouter(m *models.Model) http.Handler { router.NotFound(notFoundHandler) router.Mount("/plans", newPlanRouter(m)) router.Mount("/actions", newActionRouter(m)) + router.Mount("/auth", newAuthRouter(m)) router.Mount("/health", newHealthRouter(m)) router.Get("/ping", ping) return router diff --git a/routes/users.go b/routes/users.go deleted file mode 100644 index e59bd39..0000000 --- a/routes/users.go +++ /dev/null @@ -1,36 +0,0 @@ -package routes - -import ( - "encoding/json" - "gitea.deepak.science/deepak/gogmagog/models" - "github.com/go-chi/chi" - "net/http" -) - -func newUserRouter(m *models.Model) http.Handler { - router := chi.NewRouter() - // router.Post("/", postUserFunc(m)) - router.Get("/{username}", getUserByUsernameFunc(m)) - return router -} - -func getUserByUsernameFunc(m *models.Model) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - username := chi.URLParam(r, "username") - - user, err := m.UserByUsername(username) - if err != nil { - if models.IsNotFoundError(err) { - notFoundHandler(w, r) - return - } - serverError(w, err) - return - - } - w.Header().Add("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(user); err != nil { - serverError(w, err) - } - } -} From ab1dab6619c7ee1bf26e9f167dfe86b2e1926cfa Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 15:52:46 -0600 Subject: [PATCH 08/32] jwt auth token return --- go.mod | 1 + go.sum | 9 +++++++++ main.go | 3 ++- routes/actions_test.go | 23 +++++++++++---------- routes/auth.go | 14 +++++++++---- routes/health_test.go | 7 ++++--- routes/plans_test.go | 19 +++++++++--------- routes/post_action_test.go | 15 +++++++------- routes/post_plan_test.go | 17 ++++++++-------- routes/put_action_test.go | 17 ++++++++-------- routes/routes.go | 5 +++-- routes/routes_test.go | 9 +++++---- tokens/tokens.go | 41 ++++++++++++++++++++++++++++++++++++++ tokens/tokens_test.go | 22 ++++++++++++++++++++ 14 files changed, 145 insertions(+), 57 deletions(-) create mode 100644 tokens/tokens.go create mode 100644 tokens/tokens_test.go diff --git a/go.mod b/go.mod index 796889c..cf2b86e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/go-chi/chi v4.1.2+incompatible + github.com/go-chi/jwtauth v1.1.1 github.com/golang-migrate/migrate/v4 v4.14.1 github.com/jackc/pgx/v4 v4.10.1 github.com/jmoiron/sqlx v1.2.0 diff --git a/go.sum b/go.sum index 0fb2c70..2c2db65 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,11 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/jwtauth v1.1.1 h1:CtUHwzvXUfZeZSbASLgzaTZQ8mL7p+vitX59NBTL1vY= +github.com/go-chi/jwtauth v1.1.1/go.mod h1:znOWz9e5/GfBOKiZlOUoEfjSjUF+cLZO3GcpkoGXvFI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -307,6 +310,11 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029 h1:+HTAqhgKkKqizghOYb4uEpZ7wK8tl3Y48ZbUTHF521c= +github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= +github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -651,6 +659,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/main.go b/main.go index acc2a24..9719c9f 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" "gitea.deepak.science/deepak/gogmagog/store" + "gitea.deepak.science/deepak/gogmagog/tokens" "log" "net/http" "os" @@ -38,7 +39,7 @@ func main() { log.Println("created model") } - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("my secret")) log.Println("Running server on " + port) diff --git a/routes/actions_test.go b/routes/actions_test.go index 2008eef..fd2b325 100644 --- a/routes/actions_test.go +++ b/routes/actions_test.go @@ -3,6 +3,7 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -15,7 +16,7 @@ func TestEmptyActions(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions", nil) rr := httptest.NewRecorder() @@ -41,7 +42,7 @@ func TestOneAction(t *testing.T) { completedDate, _ := time.Parse("2006-01-02", "2021-01-03") a1 := &models.Action{ActionID: 3, ActionDescription: "testing", CompletedChunks: 1, CompletedOn: &completedDate, CreatedAt: &createdDate, UpdatedAt: &updatedDate, EstimatedChunks: 3, PlanID: 0} m := getModel([]*models.Plan{}, []*models.Action{a1}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions", nil) rr := httptest.NewRecorder() @@ -76,7 +77,7 @@ func TestErrorAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions", nil) rr := httptest.NewRecorder() @@ -98,7 +99,7 @@ func TestEmptyActionErrorWriter(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions", nil) rr := NewBadWriter() @@ -119,7 +120,7 @@ func TestOneActionByID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions/6", nil) rr := httptest.NewRecorder() @@ -151,7 +152,7 @@ func TestErrorActionByID(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions/5", nil) rr := httptest.NewRecorder() @@ -174,7 +175,7 @@ func TestEmptyActionErrorWriterByID(t *testing.T) { a := &models.Action{ActionID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions/6", nil) rr := NewBadWriter() @@ -194,7 +195,7 @@ func TestNotFoundActionByIDText(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions/wo", nil) rr := httptest.NewRecorder() @@ -213,7 +214,7 @@ func TestNotFoundActionByIDEmpty(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions/1", nil) rr := httptest.NewRecorder() @@ -234,7 +235,7 @@ func TestActionsByPlanID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions?plan_id=6", nil) rr := httptest.NewRecorder() @@ -269,7 +270,7 @@ func TestActionsByPlanIDInvalidID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/actions?plan_id=aoeu", nil) rr := httptest.NewRecorder() diff --git a/routes/auth.go b/routes/auth.go index 232846a..f97d92a 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -3,15 +3,16 @@ package routes import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" "io" "net/http" ) -func newAuthRouter(m *models.Model) http.Handler { +func newAuthRouter(m *models.Model, tok tokens.Toker) http.Handler { router := chi.NewRouter() router.Post("/register", postUserFunc(m)) - router.Post("/tokens", createTokenFunc(m)) + router.Post("/tokens", createTokenFunc(m, tok)) return router } @@ -59,8 +60,11 @@ type loginCreds struct { Username string `json:"username"` Password string `json:"password"` } +type createdToken struct { + Token string `json:"token"` +} -func createTokenFunc(m *models.Model) http.HandlerFunc { +func createTokenFunc(m *models.Model, tok tokens.Toker) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, 1024) @@ -89,8 +93,10 @@ func createTokenFunc(m *models.Model) http.HandlerFunc { } w.Header().Add("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(user); err != nil { + response := &createdToken{Token: tok.EncodeUser(user)} + if err := json.NewEncoder(w).Encode(response); err != nil { serverError(w, err) + return } } } diff --git a/routes/health_test.go b/routes/health_test.go index 92902b3..30b7100 100644 --- a/routes/health_test.go +++ b/routes/health_test.go @@ -3,6 +3,7 @@ package routes_test import ( "fmt" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -15,7 +16,7 @@ func TestEmptyHeatlhErrorWriter(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/health", nil) rr := NewBadWriter() @@ -33,7 +34,7 @@ func TestEmptyHealth(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/health", nil) rr := httptest.NewRecorder() @@ -62,7 +63,7 @@ func TestUnhealthyDB(t *testing.T) { assert := assert.New(t) errorMsg := "error" m := getErrorModel(errorMsg) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/health", nil) rr := httptest.NewRecorder() diff --git a/routes/plans_test.go b/routes/plans_test.go index fd58b1c..3a800ce 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -3,6 +3,7 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -15,7 +16,7 @@ func TestEmptyPlans(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans", nil) rr := httptest.NewRecorder() @@ -39,7 +40,7 @@ func TestOnePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans", nil) rr := httptest.NewRecorder() @@ -68,7 +69,7 @@ func TestErrorPlan(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans", nil) rr := httptest.NewRecorder() @@ -90,7 +91,7 @@ func TestEmptyPlanErrorWriter(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans", nil) rr := NewBadWriter() @@ -110,7 +111,7 @@ func TestOnePlanByID(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans/6", nil) rr := httptest.NewRecorder() @@ -137,7 +138,7 @@ func TestErrorPlanByID(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans/5", nil) rr := httptest.NewRecorder() @@ -161,7 +162,7 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans/6", nil) rr := NewBadWriter() @@ -181,7 +182,7 @@ func TestNotFoundPlanByIDText(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans/wo", nil) rr := httptest.NewRecorder() @@ -200,7 +201,7 @@ func TestNotFoundPlanByIDEmpty(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/plans/1", nil) rr := httptest.NewRecorder() diff --git a/routes/post_action_test.go b/routes/post_action_test.go index d89aba3..5516335 100644 --- a/routes/post_action_test.go +++ b/routes/post_action_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -25,7 +26,7 @@ func TestPureJSONPostAction(t *testing.T) { ActionDescription: "here's an action", } m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "action_description": "here's an action", "estimated_chunks": 3, @@ -66,7 +67,7 @@ func TestExtraFieldActionPostJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, @@ -92,7 +93,7 @@ func TestEmptyBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(``) req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -115,7 +116,7 @@ func TestTwoBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_id": 5 }, { @@ -142,7 +143,7 @@ func TestErrorCreateAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) @@ -167,7 +168,7 @@ func TestErrorOnRetrieveCreateAction(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) @@ -192,7 +193,7 @@ func TestErrorWriterCreateAction(t *testing.T) { a := &models.Action{PlanID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data, _ := json.Marshal(a) req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 8a194e2..7827db6 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -19,7 +20,7 @@ func TestCreatePlanRoute(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -50,7 +51,7 @@ func TestPureJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5 @@ -84,7 +85,7 @@ func TestExtraFieldJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5, @@ -110,7 +111,7 @@ func TestEmptyBody(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(``) req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -133,7 +134,7 @@ func TestTwoBody(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5 @@ -162,7 +163,7 @@ func TestErrorCreatePlan(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) @@ -188,7 +189,7 @@ func TestErrorOnRetrieveCreatePlan(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) @@ -215,7 +216,7 @@ func TestErrorWriterCreatePlan(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") diff --git a/routes/put_action_test.go b/routes/put_action_test.go index 5dc03b4..8dc8d94 100644 --- a/routes/put_action_test.go +++ b/routes/put_action_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -25,7 +26,7 @@ func TestPureJSONPutAction(t *testing.T) { ActionDescription: "here's an action", } m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "action_description": "here's an action", "estimated_chunks": 3, @@ -66,7 +67,7 @@ func TestExtraFieldActionPutJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, @@ -92,7 +93,7 @@ func TestEmptyBodyActionPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(``) req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -115,7 +116,7 @@ func TestTwoBodyActionPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_id": 5 }, { @@ -142,7 +143,7 @@ func TestBadActionIDPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data := []byte(`{ "plan_id": 5 }, { @@ -169,7 +170,7 @@ func TestErrorUpdateAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) @@ -194,7 +195,7 @@ func TestErrorOnRetrieveUpdateAction(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) @@ -219,7 +220,7 @@ func TestErrorWriterUpdateAction(t *testing.T) { a := &models.Action{PlanID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) data, _ := json.Marshal(a) req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") diff --git a/routes/routes.go b/routes/routes.go index e1b6c22..ddd38d3 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -3,18 +3,19 @@ package routes import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" "net/http" ) // NewRouter returns a router powered by the provided model. -func NewRouter(m *models.Model) http.Handler { +func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { router := chi.NewRouter() router.MethodNotAllowed(methodNotAllowedHandler) router.NotFound(notFoundHandler) router.Mount("/plans", newPlanRouter(m)) router.Mount("/actions", newActionRouter(m)) - router.Mount("/auth", newAuthRouter(m)) + router.Mount("/auth", newAuthRouter(m, tok)) router.Mount("/health", newHealthRouter(m)) router.Get("/ping", ping) return router diff --git a/routes/routes_test.go b/routes/routes_test.go index 0086bd1..b0574b3 100644 --- a/routes/routes_test.go +++ b/routes/routes_test.go @@ -2,6 +2,7 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -13,7 +14,7 @@ func TestPingHandler(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/ping", nil) rr := httptest.NewRecorder() @@ -34,7 +35,7 @@ func TestPingPostHandler(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("POST", "/ping", nil) rr := httptest.NewRecorder() @@ -53,7 +54,7 @@ func TestNotFoundHandler(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("POST", "/null", nil) rr := httptest.NewRecorder() @@ -72,7 +73,7 @@ func TestPingError(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m) + router := routes.NewRouter(m, tokens.New("whatever")) req, _ := http.NewRequest("GET", "/ping", nil) rr := NewBadWriter() diff --git a/tokens/tokens.go b/tokens/tokens.go new file mode 100644 index 0000000..a983181 --- /dev/null +++ b/tokens/tokens.go @@ -0,0 +1,41 @@ +package tokens + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/go-chi/jwtauth" + "time" +) + +// Toker represents a tokenizer, capable of encoding and verifying tokens. +type Toker interface { + EncodeUser(user *models.UserNoPassword) string + VerifyTokenString(tokenString string) error +} + +type jwtToker struct { + tokenAuth *jwtauth.JWTAuth +} + +// New returns a default Toker for a given secret key. +func New(key string) Toker { + return &jwtToker{tokenAuth: jwtauth.New("HS256", []byte(key), nil)} +} + +func (tok *jwtToker) EncodeUser(user *models.UserNoPassword) string { + claims := map[string]interface{}{ + "user_id": user.UserID, + "username": user.Username, + "display_name": user.DisplayName, + "iss": "gogmagog.deepak.science", + "aud": "gogmagog.deepak.science", + } + jwtauth.SetIssuedNow(claims) + jwtauth.SetExpiryIn(claims, 2*time.Hour) + _, tokenString, _ := tok.tokenAuth.Encode(claims) + return tokenString +} + +func (tok *jwtToker) VerifyTokenString(tokenString string) error { + _, err := jwtauth.VerifyToken(tok.tokenAuth, tokenString) + return err +} diff --git a/tokens/tokens_test.go b/tokens/tokens_test.go new file mode 100644 index 0000000..ba5c6e1 --- /dev/null +++ b/tokens/tokens_test.go @@ -0,0 +1,22 @@ +package tokens_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBasic(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + usr := &models.UserNoPassword{ + UserID: 3, + Username: "test", + DisplayName: "Ted Est III", + } + token := toker.EncodeUser(usr) + + assert.Nil(toker.VerifyTokenString(token)) + assert.NotNil(tokens.New("bad secret").VerifyTokenString(token)) +} From 417a7cf982f08d7c380b6a549549152baedf10b1 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 16:15:28 -0600 Subject: [PATCH 09/32] Decouples routers and adds auth for plans and actions call --- routes/actions.go | 3 ++- routes/actions_test.go | 45 +++++++++++++++++++------------------- routes/plans.go | 3 ++- routes/plans_test.go | 37 +++++++++++++++---------------- routes/post_action_test.go | 29 ++++++++++++------------ routes/post_plan_test.go | 33 ++++++++++++++-------------- routes/put_action_test.go | 33 ++++++++++++++-------------- routes/routes.go | 13 +++++++++-- tokens/tokens.go | 6 +++++ 9 files changed, 107 insertions(+), 95 deletions(-) diff --git a/routes/actions.go b/routes/actions.go index d63d294..3526803 100644 --- a/routes/actions.go +++ b/routes/actions.go @@ -9,7 +9,8 @@ import ( "strconv" ) -func newActionRouter(m *models.Model) http.Handler { +// NewActionRouter returns a new action router +func NewActionRouter(m *models.Model) http.Handler { router := chi.NewRouter() router.Get("/", getActionsFunc(m)) router.Post("/", postActionFunc(m)) diff --git a/routes/actions_test.go b/routes/actions_test.go index fd2b325..5fc3e16 100644 --- a/routes/actions_test.go +++ b/routes/actions_test.go @@ -3,7 +3,6 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" - "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -16,8 +15,8 @@ func TestEmptyActions(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -42,8 +41,8 @@ func TestOneAction(t *testing.T) { completedDate, _ := time.Parse("2006-01-02", "2021-01-03") a1 := &models.Action{ActionID: 3, ActionDescription: "testing", CompletedChunks: 1, CompletedOn: &completedDate, CreatedAt: &createdDate, UpdatedAt: &updatedDate, EstimatedChunks: 3, PlanID: 0} m := getModel([]*models.Plan{}, []*models.Action{a1}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -77,8 +76,8 @@ func TestErrorAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -99,8 +98,8 @@ func TestEmptyActionErrorWriter(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := NewBadWriter() @@ -120,8 +119,8 @@ func TestOneActionByID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions/6", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/6", nil) rr := httptest.NewRecorder() @@ -152,8 +151,8 @@ func TestErrorActionByID(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions/5", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/5", nil) rr := httptest.NewRecorder() @@ -175,8 +174,8 @@ func TestEmptyActionErrorWriterByID(t *testing.T) { a := &models.Action{ActionID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions/6", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/6", nil) rr := NewBadWriter() @@ -195,8 +194,8 @@ func TestNotFoundActionByIDText(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions/wo", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/wo", nil) rr := httptest.NewRecorder() @@ -214,8 +213,8 @@ func TestNotFoundActionByIDEmpty(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions/1", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/1", nil) rr := httptest.NewRecorder() @@ -235,8 +234,8 @@ func TestActionsByPlanID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions?plan_id=6", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/?plan_id=6", nil) rr := httptest.NewRecorder() @@ -270,8 +269,8 @@ func TestActionsByPlanIDInvalidID(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/actions?plan_id=aoeu", nil) + router := routes.NewActionRouter(m) + req, _ := http.NewRequest("GET", "/?plan_id=aoeu", nil) rr := httptest.NewRecorder() diff --git a/routes/plans.go b/routes/plans.go index 6d2921c..edcab49 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -9,7 +9,8 @@ import ( "strconv" ) -func newPlanRouter(m *models.Model) http.Handler { +// NewPlanRouter returns the http.Handler for the passed in model to route plan methods. +func NewPlanRouter(m *models.Model) http.Handler { router := chi.NewRouter() router.Get("/", getAllPlansFunc(m)) router.Post("/", postPlanFunc(m)) diff --git a/routes/plans_test.go b/routes/plans_test.go index 3a800ce..e6fd9f1 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -3,7 +3,6 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" - "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -16,8 +15,8 @@ func TestEmptyPlans(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -40,8 +39,8 @@ func TestOnePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -69,8 +68,8 @@ func TestErrorPlan(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -91,8 +90,8 @@ func TestEmptyPlanErrorWriter(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/", nil) rr := NewBadWriter() @@ -111,8 +110,8 @@ func TestOnePlanByID(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans/6", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/6", nil) rr := httptest.NewRecorder() @@ -138,8 +137,8 @@ func TestErrorPlanByID(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans/5", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/5", nil) rr := httptest.NewRecorder() @@ -162,8 +161,8 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans/6", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/6", nil) rr := NewBadWriter() @@ -182,8 +181,8 @@ func TestNotFoundPlanByIDText(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans/wo", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/wo", nil) rr := httptest.NewRecorder() @@ -201,8 +200,8 @@ func TestNotFoundPlanByIDEmpty(t *testing.T) { m := getEmptyModel() - router := routes.NewRouter(m, tokens.New("whatever")) - req, _ := http.NewRequest("GET", "/plans/1", nil) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequest("GET", "/1", nil) rr := httptest.NewRecorder() diff --git a/routes/post_action_test.go b/routes/post_action_test.go index 5516335..2140611 100644 --- a/routes/post_action_test.go +++ b/routes/post_action_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" - "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -26,7 +25,7 @@ func TestPureJSONPostAction(t *testing.T) { ActionDescription: "here's an action", } m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "action_description": "here's an action", "estimated_chunks": 3, @@ -34,7 +33,7 @@ func TestPureJSONPostAction(t *testing.T) { "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5 }`) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -67,13 +66,13 @@ func TestExtraFieldActionPostJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, "sabotage": "omg" }`) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -93,9 +92,9 @@ func TestEmptyBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(``) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -116,13 +115,13 @@ func TestTwoBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 }, { "plan_id": 6 }`) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -143,10 +142,10 @@ func TestErrorCreateAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -168,10 +167,10 @@ func TestErrorOnRetrieveCreateAction(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -193,9 +192,9 @@ func TestErrorWriterCreateAction(t *testing.T) { a := &models.Action{PlanID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data, _ := json.Marshal(a) - req, _ := http.NewRequest("POST", "/actions", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := NewBadWriter() diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 7827db6..7a4f4e5 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" - "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -20,9 +19,9 @@ func TestCreatePlanRoute(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -51,12 +50,12 @@ func TestPureJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5 }`) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -85,13 +84,13 @@ func TestExtraFieldJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5, "plan_sabotage": "omg" }`) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -111,9 +110,9 @@ func TestEmptyBody(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data := []byte(``) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -134,7 +133,7 @@ func TestTwoBody(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", "plan_id": 5 @@ -142,7 +141,7 @@ func TestTwoBody(t *testing.T) { "plan_date": "2021-01-01T00:00:00Z", "plan_id": 6 }`) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -163,11 +162,11 @@ func TestErrorCreatePlan(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -189,11 +188,11 @@ func TestErrorOnRetrieveCreatePlan(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -216,9 +215,9 @@ func TestErrorWriterCreatePlan(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/plans", bytes.NewBuffer(data)) + req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := NewBadWriter() diff --git a/routes/put_action_test.go b/routes/put_action_test.go index 8dc8d94..7aecc56 100644 --- a/routes/put_action_test.go +++ b/routes/put_action_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" - "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -26,7 +25,7 @@ func TestPureJSONPutAction(t *testing.T) { ActionDescription: "here's an action", } m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "action_description": "here's an action", "estimated_chunks": 3, @@ -34,7 +33,7 @@ func TestPureJSONPutAction(t *testing.T) { "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5 }`) - req, _ := http.NewRequest("PUT", "/actions/0", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/0", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -67,13 +66,13 @@ func TestExtraFieldActionPutJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, "sabotage": "omg" }`) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -93,9 +92,9 @@ func TestEmptyBodyActionPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(``) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -116,13 +115,13 @@ func TestTwoBodyActionPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 }, { "plan_id": 6 }`) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -143,13 +142,13 @@ func TestBadActionIDPut(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getModel([]*models.Plan{p}, []*models.Action{}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 }, { "plan_id": 6 }`) - req, _ := http.NewRequest("PUT", "/actions/text", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/text", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -170,10 +169,10 @@ func TestErrorUpdateAction(t *testing.T) { m := getErrorModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -195,10 +194,10 @@ func TestErrorOnRetrieveUpdateAction(t *testing.T) { m := getErrorOnGetModel("Model always errors") - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) a := &models.Action{PlanID: 6} data, _ := json.Marshal(a) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -220,9 +219,9 @@ func TestErrorWriterUpdateAction(t *testing.T) { a := &models.Action{PlanID: 6} m := getModel([]*models.Plan{}, []*models.Action{a}) - router := routes.NewRouter(m, tokens.New("whatever")) + router := routes.NewActionRouter(m) data, _ := json.Marshal(a) - req, _ := http.NewRequest("PUT", "/actions/1", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := NewBadWriter() diff --git a/routes/routes.go b/routes/routes.go index ddd38d3..b5501b3 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -5,6 +5,7 @@ import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" + "github.com/go-chi/jwtauth" "net/http" ) @@ -13,8 +14,16 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { router := chi.NewRouter() router.MethodNotAllowed(methodNotAllowedHandler) router.NotFound(notFoundHandler) - router.Mount("/plans", newPlanRouter(m)) - router.Mount("/actions", newActionRouter(m)) + router.Group(func(r chi.Router) { + r.Use(tok.Verifier()) + // Handle valid / invalid tokens. In this example, we use + // the provided authenticator middleware, but you can write your + // own very easily, look at the Authenticator method in jwtauth.go + // and tweak it, its not scary. + r.Use(jwtauth.Authenticator) + r.Mount("/actions", NewActionRouter(m)) + r.Mount("/plans", NewPlanRouter(m)) + }) router.Mount("/auth", newAuthRouter(m, tok)) router.Mount("/health", newHealthRouter(m)) router.Get("/ping", ping) diff --git a/tokens/tokens.go b/tokens/tokens.go index a983181..6ee2e69 100644 --- a/tokens/tokens.go +++ b/tokens/tokens.go @@ -3,6 +3,7 @@ package tokens import ( "gitea.deepak.science/deepak/gogmagog/models" "github.com/go-chi/jwtauth" + "net/http" "time" ) @@ -10,6 +11,7 @@ import ( type Toker interface { EncodeUser(user *models.UserNoPassword) string VerifyTokenString(tokenString string) error + Verifier() func(http.Handler) http.Handler } type jwtToker struct { @@ -39,3 +41,7 @@ func (tok *jwtToker) VerifyTokenString(tokenString string) error { _, err := jwtauth.VerifyToken(tok.tokenAuth, tokenString) return err } + +func (tok *jwtToker) Verifier() func(http.Handler) http.Handler { + return jwtauth.Verifier(tok.tokenAuth) +} From d8604dc3ccbae66f172a8da126a68e36fb868e94 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 18:16:47 -0600 Subject: [PATCH 10/32] Adds inmemorystore to help refactor tests --- store/inmemory.go | 106 +++++++++++++++++++++++++++++++++++++++++ store/inmemory_test.go | 104 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 store/inmemory.go create mode 100644 store/inmemory_test.go diff --git a/store/inmemory.go b/store/inmemory.go new file mode 100644 index 0000000..bfaac57 --- /dev/null +++ b/store/inmemory.go @@ -0,0 +1,106 @@ +package store + +import ( + "database/sql" + "gitea.deepak.science/deepak/gogmagog/models" +) + +type inMemoryStore struct { + actions []*models.Action + plans []*models.Plan + users []*models.User +} + +// GetInMemoryStore provides a purely in memory store, for testing purposes only, with no persistence. +func GetInMemoryStore() (models.Store, error) { + return &inMemoryStore{ + actions: make([]*models.Action, 0), + plans: make([]*models.Plan, 0), + users: make([]*models.User, 0), + }, nil +} + +func (store *inMemoryStore) SelectActions() ([]*models.Action, error) { + return store.actions, nil +} + +func (store *inMemoryStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { + ret := make([]*models.Action, 0) + for _, action := range store.actions { + if int(plan.PlanID) == int(action.PlanID) { + ret = append(ret, action) + } + } + return ret, nil +} + +func (store *inMemoryStore) SelectActionByID(id int) (*models.Action, error) { + for _, action := range store.actions { + if id == int(action.ActionID) { + return action, nil + } + } + return nil, sql.ErrNoRows +} + +func (store *inMemoryStore) InsertAction(action *models.Action) (int, error) { + id := len(store.actions) + 1 + action.ActionID = int64(id) + store.actions = append(store.actions, action) + return id, nil +} + +func (store *inMemoryStore) UpdateAction(action *models.Action) error { + currentAction, err := store.SelectActionByID(int(action.ActionID)) + if err != nil { + return err + } + currentAction.ActionDescription = action.ActionDescription + currentAction.EstimatedChunks = action.EstimatedChunks + currentAction.CompletedChunks = action.CompletedChunks + currentAction.PlanID = action.PlanID + + return nil + +} + +func (store *inMemoryStore) SelectPlans() ([]*models.Plan, error) { + return store.plans, nil +} + +func (store *inMemoryStore) SelectPlanByID(id int) (*models.Plan, error) { + for _, plan := range store.plans { + if id == int(plan.PlanID) { + return plan, nil + } + } + return nil, sql.ErrNoRows +} + +func (store *inMemoryStore) InsertPlan(plan *models.Plan) (int, error) { + id := len(store.plans) + 1 + plan.PlanID = int64(id) + store.plans = append(store.plans, plan) + return id, nil +} + +func (store *inMemoryStore) ConnectionLive() error { + return nil +} + +func (store *inMemoryStore) SelectUserByUsername(username string) (*models.User, error) { + for _, user := range store.users { + if username == user.Username { + return user, nil + } + } + return nil, sql.ErrNoRows +} + +// inMemoryStore.InsertUser will not enforce unique usernames, which is ok. +func (store *inMemoryStore) InsertUser(user *models.User) (int, error) { + id := len(store.users) + 1 + user.UserID = int64(id) + store.users = append(store.users, user) + return id, nil +} diff --git a/store/inmemory_test.go b/store/inmemory_test.go new file mode 100644 index 0000000..3617e76 --- /dev/null +++ b/store/inmemory_test.go @@ -0,0 +1,104 @@ +package store_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestInMemoryActionMethods(t *testing.T) { + assert := assert.New(t) + str, _ := store.GetInMemoryStore() + + sampleplanid := 8 + + act := &models.Action{} + a2 := &models.Action{PlanID: sampleplanid} + + id, _ := str.InsertAction(act) + assert.EqualValues(1, id) + + receivedAction, err := str.SelectActionByID(id) + assert.Nil(err) + assert.EqualValues(act, receivedAction) + + allactions, err := str.SelectActions() + assert.Nil(err) + assert.EqualValues(1, len(allactions)) + + str.InsertAction(a2) + allactions, err = str.SelectActions() + assert.Nil(err) + assert.EqualValues(2, len(allactions)) + + planactions, err := str.SelectActionsByPlanID(&models.Plan{PlanID: int64(sampleplanid)}) + assert.Nil(err) + assert.EqualValues(1, len(planactions)) + assert.Equal(a2, planactions[0]) + + _, err = str.SelectActionByID(151) + assert.NotNil(err) + + sampleDescription := "snth" + replacementAction := &models.Action{ActionID: 1, ActionDescription: sampleDescription} + err = str.UpdateAction(replacementAction) + assert.Nil(err) + assert.Equal(sampleDescription, act.ActionDescription) + + replacementAction = &models.Action{ActionID: 1235122, ActionDescription: sampleDescription} + err = str.UpdateAction(replacementAction) + assert.NotNil(err) + +} + +func TestInMemoryPlanMethods(t *testing.T) { + assert := assert.New(t) + str, _ := store.GetInMemoryStore() + + p := &models.Plan{} + plans, err := str.SelectPlans() + + assert.Nil(err) + assert.EqualValues(0, len(plans)) + + id, err := str.InsertPlan(p) + plans, err = str.SelectPlans() + + assert.Nil(err) + assert.EqualValues(1, len(plans)) + + retrievedPlan, err := str.SelectPlanByID(id) + assert.Nil(err) + assert.Equal(retrievedPlan, p) + + _, err = str.SelectPlanByID(135135) + assert.NotNil(err) +} + +func TestLive(t *testing.T) { + assert := assert.New(t) + str, _ := store.GetInMemoryStore() + + err := str.ConnectionLive() + assert.Nil(err) +} + +func TestInMemoryUserMethods(t *testing.T) { + assert := assert.New(t) + str, _ := store.GetInMemoryStore() + + uname := "hiimauser" + + u := &models.User{Username: uname} + + id, err := str.InsertUser(u) + assert.Nil(err) + + retrievedUser, err := str.SelectUserByUsername(uname) + assert.Nil(err) + assert.EqualValues(id, retrievedUser.UserID) + + _, err = str.SelectUserByUsername("bad username") + assert.NotNil(err) +} From 1c3555d8b7793da547f0c2c72fe5c836418eebda Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 18:46:24 -0600 Subject: [PATCH 11/32] Removed multistore --- models/models_test.go | 101 +++++++++---------------------------- models/user_test.go | 45 ++++++++++------- routes/actions_test.go | 30 ++++++----- routes/plans_test.go | 19 ++++--- routes/post_action_test.go | 19 ++++--- routes/post_plan_test.go | 31 +++++++----- routes/put_action_test.go | 34 +++++-------- routes/route_model_test.go | 74 ++------------------------- 8 files changed, 127 insertions(+), 226 deletions(-) diff --git a/models/models_test.go b/models/models_test.go index 3c4fc7f..acb9502 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -2,81 +2,33 @@ package models_test import ( "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" "github.com/stretchr/testify/assert" "testing" ) -type multiStore struct { - actions []*models.Action - plans []*models.Plan -} - -func (ms *multiStore) SelectActions() ([]*models.Action, error) { - return ms.actions, nil -} - -func (ms *multiStore) SelectActionByID(id int) (*models.Action, error) { - return ms.actions[0], nil -} - -func (ms *multiStore) InsertAction(action *models.Action) (int, error) { - return int(action.ActionID), nil -} - -func (ms *multiStore) UpdateAction(action *models.Action) error { - return nil -} - -func (ms *multiStore) SelectPlans() ([]*models.Plan, error) { - return ms.plans, nil -} - -func (ms *multiStore) SelectPlanByID(id int) (*models.Plan, error) { - return ms.plans[0], nil -} - -func (ms *multiStore) InsertPlan(plan *models.Plan) (int, error) { - return int(plan.PlanID), nil -} - -func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { - return ms.actions, nil -} - -func (ms *multiStore) SelectUserByUsername(username string) (*models.User, error) { - // password is "password" - return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")}, nil -} - -func (ms *multiStore) InsertUser(user *models.User) (int, error) { - return int(user.UserID), nil -} - -func (ms *multiStore) ConnectionLive() error { - return nil -} - func TestModelActions(t *testing.T) { assert := assert.New(t) a1 := &models.Action{ActionID: 3} a2 := &models.Action{ActionID: 4} p := &models.Plan{PlanID: 6} - ss := &multiStore{ - []*models.Action{a1, a2}, - []*models.Plan{p}} - m := models.New(ss) + + str, _ := store.GetInMemoryStore() + str.InsertAction(a1) + str.InsertPlan(p) + m := models.New(str) actions, err := m.Actions() assert.Nil(err) - assert.Equal(2, len(actions)) + assert.Equal(1, len(actions)) - firstAction, err := m.Action(3) + firstAction, err := m.Action(1) assert.Nil(err) - assert.EqualValues(3, firstAction.ActionID) + assert.EqualValues(1, firstAction.ActionID) - actionID, err := m.AddAction(a1) + actionID, err := m.AddAction(a2) assert.Nil(err) - assert.EqualValues(3, actionID) + assert.EqualValues(2, actionID) err = m.SaveAction(a1) assert.Nil(err) @@ -84,41 +36,38 @@ func TestModelActions(t *testing.T) { func TestModelPlanMethods(t *testing.T) { assert := assert.New(t) - a1 := &models.Action{ActionID: 3} + a1 := &models.Action{ActionID: 3, PlanID: 1} a2 := &models.Action{ActionID: 4} - p := &models.Plan{PlanID: 6} + p := &models.Plan{} - ss := &multiStore{ - []*models.Action{a1, a2}, - []*models.Plan{p}, - } - m := models.New(ss) + str, _ := store.GetInMemoryStore() + str.InsertPlan(p) + str.InsertAction(a1) + str.InsertAction(a2) + m := models.New(str) plans, err := m.Plans() assert.Nil(err) assert.Equal(1, len(plans)) - firstPlan, err := m.Plan(6) + firstPlan, err := m.Plan(1) assert.Nil(err) - assert.EqualValues(6, firstPlan.PlanID) + assert.EqualValues(1, firstPlan.PlanID) actions, err := m.GetActions(firstPlan) assert.Nil(err) - assert.Equal(2, len(actions)) + assert.Equal(1, len(actions)) - planId, err := m.AddPlan(p) + planId, err := m.AddPlan(&models.Plan{}) assert.Nil(err) - assert.EqualValues(6, planId) + assert.EqualValues(2, planId) } func TestModelHealthy(t *testing.T) { assert := assert.New(t) - ss := &multiStore{ - []*models.Action{}, - []*models.Plan{}, - } - m := models.New(ss) + str, _ := store.GetInMemoryStore() + m := models.New(str) err := m.Healthy() assert.Nil(err) diff --git a/models/user_test.go b/models/user_test.go index eb58432..05ca5db 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -3,25 +3,37 @@ package models_test import ( "fmt" "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" "github.com/stretchr/testify/assert" "testing" ) func TestModelUsers(t *testing.T) { assert := assert.New(t) + a1 := &models.Action{ActionID: 3} a2 := &models.Action{ActionID: 4} p := &models.Plan{PlanID: 6} - ss := &multiStore{ - []*models.Action{a1, a2}, - []*models.Plan{p}} - m := models.New(ss) - user, err := m.VerifyUserByUsernamePassword("test", "password") + username := "test1" + // password := password + user1 := &models.User{Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")} + str, _ := store.GetInMemoryStore() + str.InsertPlan(p) + str.InsertAction(a1) + str.InsertAction(a2) + str.InsertUser(user1) + m := models.New(str) + + user, err := m.VerifyUserByUsernamePassword("test1", "password") assert.Nil(err) assert.NotNil(user) - user, err = m.VerifyUserByUsernamePassword("test", "wrong_password") + user, err = m.VerifyUserByUsernamePassword("test1", "wrong_password") + assert.NotNil(err) + assert.Nil(user) + + user, err = m.VerifyUserByUsernamePassword("test2", "password") assert.NotNil(err) assert.Nil(user) } @@ -42,13 +54,12 @@ func TestCreateUser(t *testing.T) { pass := "abc" u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} - ss := &multiStore{ - []*models.Action{}, - []*models.Plan{}} - m := models.New(ss) + str, _ := store.GetInMemoryStore() + m := models.New(str) - _, err := m.CreateUser(u) + id, err := m.CreateUser(u) assert.Nil(err) + assert.EqualValues(1, id) } func TestCreateUserFailValidation(t *testing.T) { assert := assert.New(t) @@ -57,10 +68,8 @@ func TestCreateUserFailValidation(t *testing.T) { pass := "abc" u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} - ss := &multiStore{ - []*models.Action{}, - []*models.Plan{}} - m := models.New(ss) + str, _ := store.GetInMemoryStore() + m := models.New(str) _, err := m.CreateUser(u) assert.NotNil(err) @@ -73,10 +82,8 @@ func TestCreateUserFailValidationPassword(t *testing.T) { pass := "" u := &models.CreateUserRequest{Username: username, DisplayName: displayName, Password: pass} - ss := &multiStore{ - []*models.Action{}, - []*models.Plan{}} - m := models.New(ss) + str, _ := store.GetInMemoryStore() + m := models.New(str) _, err := m.CreateUser(u) assert.NotNil(err) diff --git a/routes/actions_test.go b/routes/actions_test.go index 5fc3e16..66a1fc5 100644 --- a/routes/actions_test.go +++ b/routes/actions_test.go @@ -40,7 +40,9 @@ func TestOneAction(t *testing.T) { updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") completedDate, _ := time.Parse("2006-01-02", "2021-01-03") a1 := &models.Action{ActionID: 3, ActionDescription: "testing", CompletedChunks: 1, CompletedOn: &completedDate, CreatedAt: &createdDate, UpdatedAt: &updatedDate, EstimatedChunks: 3, PlanID: 0} - m := getModel([]*models.Plan{}, []*models.Action{a1}) + m := getEmptyModel() + m.AddAction(a1) + router := routes.NewActionRouter(m) req, _ := http.NewRequest("GET", "/", nil) @@ -55,7 +57,7 @@ func TestOneAction(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `[ { - "action_id": 3, + "action_id": 1, "action_description": "testing", "estimated_chunks": 3, "completed_chunks": 1, @@ -118,9 +120,10 @@ func TestOneActionByID(t *testing.T) { createdDate, _ := time.Parse("2006-01-02", "2021-01-01") updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.InsertAction(a) router := routes.NewActionRouter(m) - req, _ := http.NewRequest("GET", "/6", nil) + req, _ := http.NewRequest("GET", "/1", nil) rr := httptest.NewRecorder() @@ -132,7 +135,7 @@ func TestOneActionByID(t *testing.T) { assert.Equal(http.StatusOK, status) // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `{ - "action_id": 6, + "action_id": 1, "action_description": "howdy", "estimated_chunks": 54, "completed_chunks": 0, @@ -172,10 +175,11 @@ func TestEmptyActionErrorWriterByID(t *testing.T) { assert := assert.New(t) a := &models.Action{ActionID: 6} - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) - req, _ := http.NewRequest("GET", "/6", nil) + req, _ := http.NewRequest("GET", "/1", nil) rr := NewBadWriter() @@ -232,8 +236,9 @@ func TestActionsByPlanID(t *testing.T) { assert := assert.New(t) createdDate, _ := time.Parse("2006-01-02", "2021-01-01") updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") - a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} - m := getModel([]*models.Plan{}, []*models.Action{a}) + a := &models.Action{ActionID: 1, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 6} + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) req, _ := http.NewRequest("GET", "/?plan_id=6", nil) @@ -248,13 +253,13 @@ func TestActionsByPlanID(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `[ { - "action_id": 6, + "action_id": 1, "action_description": "howdy", "estimated_chunks": 54, "completed_chunks": 0, "updated_at": "2021-01-02T00:00:00Z", "created_at": "2021-01-01T00:00:00Z", - "plan_id": 3 + "plan_id": 6 } ]` assert.JSONEq(expected, rr.Body.String()) @@ -268,7 +273,8 @@ func TestActionsByPlanIDInvalidID(t *testing.T) { createdDate, _ := time.Parse("2006-01-02", "2021-01-01") updatedDate, _ := time.Parse("2006-01-02", "2021-01-02") a := &models.Action{ActionID: 6, ActionDescription: "howdy", CompletedOn: nil, CreatedAt: &createdDate, UpdatedAt: &updatedDate, CompletedChunks: 0, EstimatedChunks: 54, PlanID: 3} - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) req, _ := http.NewRequest("GET", "/?plan_id=aoeu", nil) diff --git a/routes/plans_test.go b/routes/plans_test.go index e6fd9f1..03fd375 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -38,7 +38,8 @@ func TestOnePlan(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) req, _ := http.NewRequest("GET", "/", nil) @@ -53,7 +54,7 @@ func TestOnePlan(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `[ { - "plan_id": 6, + "plan_id": 1, "plan_date": "2021-01-01T00:00:00Z" } ]` @@ -109,9 +110,10 @@ func TestOnePlanByID(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/6", nil) + req, _ := http.NewRequest("GET", "/1", nil) rr := httptest.NewRecorder() @@ -123,7 +125,7 @@ func TestOnePlanByID(t *testing.T) { assert.Equal(http.StatusOK, status) // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `{ - "plan_id": 6, + "plan_id": 1, "plan_date": "2021-01-01T00:00:00Z" }` assert.JSONEq(expected, rr.Body.String()) @@ -158,11 +160,12 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + p := &models.Plan{PlanID: 1, PlanDate: &planDate} + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/6", nil) + req, _ := http.NewRequest("GET", "/1", nil) rr := NewBadWriter() diff --git a/routes/post_action_test.go b/routes/post_action_test.go index 2140611..49261c9 100644 --- a/routes/post_action_test.go +++ b/routes/post_action_test.go @@ -24,7 +24,8 @@ func TestPureJSONPostAction(t *testing.T) { CompletedChunks: 2, ActionDescription: "here's an action", } - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) data := []byte(`{ "action_description": "here's an action", @@ -51,9 +52,9 @@ func TestPureJSONPostAction(t *testing.T) { "completed_chunks": 2, "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, - "action_id": 0 + "action_id": 2 }, - "id": 0 + "id": 2 }` assert.JSONEq(expected, rr.Body.String()) contentType := rr.Header().Get("Content-Type") @@ -65,7 +66,8 @@ func TestExtraFieldActionPostJSON(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewActionRouter(m) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", @@ -91,7 +93,8 @@ func TestEmptyBodyActionPost(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewActionRouter(m) data := []byte(``) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -114,7 +117,8 @@ func TestTwoBodyActionPost(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 @@ -190,7 +194,8 @@ func TestErrorWriterCreateAction(t *testing.T) { assert := assert.New(t) a := &models.Action{PlanID: 6} - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) data, _ := json.Marshal(a) diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 7a4f4e5..77ec78a 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -18,7 +18,8 @@ func TestCreatePlanRoute(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -34,10 +35,10 @@ func TestCreatePlanRoute(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `{ "created_plan": { - "plan_id": 6, + "plan_id": 2, "plan_date": "2021-01-01T00:00:00Z" }, - "id": 0 + "id": 2 }` assert.JSONEq(expected, rr.Body.String()) contentType := rr.Header().Get("Content-Type") @@ -48,12 +49,13 @@ func TestPureJSON(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + p := &models.Plan{PlanID: 1, PlanDate: &planDate} + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", - "plan_id": 5 + "plan_id": 1 }`) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -68,10 +70,10 @@ func TestPureJSON(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `{ "created_plan": { - "plan_id": 6, + "plan_id": 2, "plan_date": "2021-01-01T00:00:00Z" }, - "id": 0 + "id": 2 }` assert.JSONEq(expected, rr.Body.String()) contentType := rr.Header().Get("Content-Type") @@ -83,7 +85,8 @@ func TestExtraFieldJSON(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", @@ -109,7 +112,8 @@ func TestEmptyBody(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data := []byte(``) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -132,7 +136,8 @@ func TestTwoBody(t *testing.T) { assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", @@ -213,8 +218,8 @@ func TestErrorWriterCreatePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) - + m := getEmptyModel() + m.AddPlan(p) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) diff --git a/routes/put_action_test.go b/routes/put_action_test.go index 7aecc56..3599f1d 100644 --- a/routes/put_action_test.go +++ b/routes/put_action_test.go @@ -20,11 +20,12 @@ func TestPureJSONPutAction(t *testing.T) { a := &models.Action{ PlanID: 5, CompletedOn: &compOn, - EstimatedChunks: 3, - CompletedChunks: 2, - ActionDescription: "here's an action", + EstimatedChunks: 1, + CompletedChunks: 1, + ActionDescription: "hn", } - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) data := []byte(`{ "action_description": "here's an action", @@ -33,7 +34,7 @@ func TestPureJSONPutAction(t *testing.T) { "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5 }`) - req, _ := http.NewRequest("PUT", "/0", bytes.NewBuffer(data)) + req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -51,9 +52,9 @@ func TestPureJSONPutAction(t *testing.T) { "completed_chunks": 2, "completed_on": "2021-01-01T00:00:00Z", "plan_id": 5, - "action_id": 0 + "action_id": 1 }, - "id": 0 + "id": 1 }` assert.JSONEq(expected, rr.Body.String()) contentType := rr.Header().Get("Content-Type") @@ -63,9 +64,7 @@ func TestPureJSONPutAction(t *testing.T) { func TestExtraFieldActionPutJSON(t *testing.T) { // set up assert := assert.New(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() router := routes.NewActionRouter(m) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", @@ -89,9 +88,7 @@ func TestExtraFieldActionPutJSON(t *testing.T) { func TestEmptyBodyActionPut(t *testing.T) { // set up assert := assert.New(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() router := routes.NewActionRouter(m) data := []byte(``) req, _ := http.NewRequest("PUT", "/1", bytes.NewBuffer(data)) @@ -112,9 +109,7 @@ func TestEmptyBodyActionPut(t *testing.T) { func TestTwoBodyActionPut(t *testing.T) { // set up assert := assert.New(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 @@ -139,9 +134,7 @@ func TestTwoBodyActionPut(t *testing.T) { func TestBadActionIDPut(t *testing.T) { // set up assert := assert.New(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} - m := getModel([]*models.Plan{p}, []*models.Action{}) + m := getEmptyModel() router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 @@ -217,7 +210,8 @@ func TestErrorWriterUpdateAction(t *testing.T) { assert := assert.New(t) a := &models.Action{PlanID: 6} - m := getModel([]*models.Plan{}, []*models.Action{a}) + m := getEmptyModel() + m.AddAction(a) router := routes.NewActionRouter(m) data, _ := json.Marshal(a) diff --git a/routes/route_model_test.go b/routes/route_model_test.go index 368e80e..233e67d 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -3,13 +3,9 @@ package routes_test import ( "fmt" "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" ) -type multiStore struct { - actions []*models.Action - plans []*models.Plan -} - type testNotFoundError struct { error } @@ -18,75 +14,11 @@ func (t *testNotFoundError) NotFound() bool { return true } -func (ms *multiStore) SelectActions() ([]*models.Action, error) { - return ms.actions, nil -} - -func (ms *multiStore) SelectActionByID(id int) (*models.Action, error) { - if len(ms.actions) < 1 { - err := &testNotFoundError{fmt.Errorf("too small")} - return nil, err - } - return ms.actions[0], nil -} - -func (ms *multiStore) InsertAction(action *models.Action) (int, error) { - return int(action.ActionID), nil -} - -func (ms *multiStore) UpdateAction(action *models.Action) error { - return nil -} - -func (ms *multiStore) SelectPlans() ([]*models.Plan, error) { - return ms.plans, nil -} - -func (ms *multiStore) SelectPlanByID(id int) (*models.Plan, error) { - if len(ms.plans) < 1 { - err := &testNotFoundError{fmt.Errorf("too small")} - return nil, err - } - return ms.plans[0], nil -} - -func (ms *multiStore) InsertPlan(plan *models.Plan) (int, error) { - return int(plan.PlanID), nil -} - -func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { - return ms.actions, nil -} - -func (ms *multiStore) SelectUserByUsername(name string) (*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 -} - func getEmptyModel() *models.Model { - ss := &multiStore{ - []*models.Action{}, - []*models.Plan{}, - } - m := models.New(ss) + str, _ := store.GetInMemoryStore() + m := models.New(str) return m } -func getModel(plns []*models.Plan, acts []*models.Action) *models.Model { - ss := &multiStore{ - actions: acts, - plans: plns, - } - m := models.New(ss) - return m -} - func (e *errorStore) SelectActions() ([]*models.Action, error) { return nil, e.error } From 3648d5a1cc9fdc59447f408527a52e35e9e3eddf Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 18:53:54 -0600 Subject: [PATCH 12/32] Removing unnecessary type --- routes/route_model_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/routes/route_model_test.go b/routes/route_model_test.go index 233e67d..dff57bc 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -6,14 +6,6 @@ import ( "gitea.deepak.science/deepak/gogmagog/store" ) -type testNotFoundError struct { - error -} - -func (t *testNotFoundError) NotFound() bool { - return true -} - func getEmptyModel() *models.Model { str, _ := store.GetInMemoryStore() m := models.New(str) From 169afed5c964a5dddfd3f442769966aa8b5b2bbd Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 12 Jan 2021 19:12:39 -0600 Subject: [PATCH 13/32] Adds some stuff for only failing on the subsequent retrieve --- routes/route_model_test.go | 106 ++----------------------------------- store/errorStore.go | 70 ++++++++++++++++++++++++ store/errorStore_test.go | 77 +++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 101 deletions(-) create mode 100644 store/errorStore.go create mode 100644 store/errorStore_test.go diff --git a/routes/route_model_test.go b/routes/route_model_test.go index dff57bc..1f50614 100644 --- a/routes/route_model_test.go +++ b/routes/route_model_test.go @@ -1,7 +1,6 @@ package routes_test import ( - "fmt" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/store" ) @@ -11,108 +10,13 @@ func getEmptyModel() *models.Model { m := models.New(str) return m } -func (e *errorStore) SelectActions() ([]*models.Action, error) { - return nil, e.error -} -func (e *errorStore) SelectActionByID(id int) (*models.Action, error) { - return nil, e.error -} - -func (e *errorStore) InsertAction(action *models.Action) (int, error) { - return 0, e.error -} - -func (e *errorStore) UpdateAction(action *models.Action) error { - return e.error -} - -func (e *errorStore) SelectPlans() ([]*models.Plan, error) { - return nil, e.error -} - -func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) { - return nil, e.error -} - -func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) { - return 0, e.error -} - -func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { - return nil, e.error -} - -func (e *errorStore) SelectUserByUsername(name string) (*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 -} - -type errorStore struct { - error error -} - -func getErrorModel(errorMsg string) *models.Model { - e := &errorStore{error: fmt.Errorf(errorMsg)} - return models.New(e) -} - -func (e *onlyCreateStore) SelectActions() ([]*models.Action, error) { - return nil, e.error -} - -func (e *onlyCreateStore) SelectActionByID(id int) (*models.Action, error) { - return nil, e.error -} - -func (e *onlyCreateStore) InsertAction(action *models.Action) (int, error) { - return int(action.ActionID), nil -} - -func (e *onlyCreateStore) UpdateAction(action *models.Action) error { - return nil -} - -func (e *onlyCreateStore) SelectPlans() ([]*models.Plan, error) { - return nil, e.error -} - -func (e *onlyCreateStore) SelectPlanByID(id int) (*models.Plan, error) { - return nil, e.error -} - -func (e *onlyCreateStore) InsertPlan(plan *models.Plan) (int, error) { - return int(plan.PlanID), nil -} - -func (e *onlyCreateStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { - return nil, e.error -} - -func (e *onlyCreateStore) SelectUserByUsername(name string) (*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 -} - -type onlyCreateStore struct { - error error +func getErrorModel(message string) *models.Model { + str := store.GetErrorStore(message, true) + return models.New(str) } func getErrorOnGetModel(errorMsg string) *models.Model { - e := &onlyCreateStore{error: fmt.Errorf(errorMsg)} - return models.New(e) + str := store.GetErrorStore(errorMsg, false) + return models.New(str) } diff --git a/store/errorStore.go b/store/errorStore.go new file mode 100644 index 0000000..d219bf2 --- /dev/null +++ b/store/errorStore.go @@ -0,0 +1,70 @@ +package store + +import ( + "fmt" + "gitea.deepak.science/deepak/gogmagog/models" +) + +func (e *errorStore) SelectActions() ([]*models.Action, error) { + return nil, e.error +} + +func (e *errorStore) SelectActionByID(id int) (*models.Action, error) { + return nil, e.error +} + +func (e *errorStore) InsertAction(action *models.Action) (int, error) { + if e.errorOnInsert { + return 0, e.error + } + return 0, nil +} + +func (e *errorStore) UpdateAction(action *models.Action) error { + return e.error +} + +func (e *errorStore) SelectPlans() ([]*models.Plan, error) { + return nil, e.error +} + +func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) { + return nil, e.error +} + +func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) { + if e.errorOnInsert { + return 0, e.error + } + return 0, nil +} + +func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { + return nil, e.error +} + +func (e *errorStore) SelectUserByUsername(name string) (*models.User, error) { + return nil, e.error +} + +func (e *errorStore) InsertUser(user *models.User) (int, error) { + if e.errorOnInsert { + return 0, e.error + } + return 0, nil +} + +func (e *errorStore) ConnectionLive() error { + return e.error +} + +type errorStore struct { + error error + errorOnInsert bool +} + +// GetErrorStore returns a models.Store that always errors. This is useful for testirng purposes. +func GetErrorStore(errorMsg string, errorOnInsert bool) models.Store { + e := &errorStore{error: fmt.Errorf(errorMsg), errorOnInsert: errorOnInsert} + return e +} diff --git a/store/errorStore_test.go b/store/errorStore_test.go new file mode 100644 index 0000000..9cbbf62 --- /dev/null +++ b/store/errorStore_test.go @@ -0,0 +1,77 @@ +package store_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestErrorActionMethods(t *testing.T) { + assert := assert.New(t) + str := store.GetErrorStore("error message sample", true) + str2 := store.GetErrorStore("error message sample", false) + + _, err := str.InsertAction(&models.Action{}) + assert.NotNil(err) + _, err = str2.InsertAction(&models.Action{}) + assert.Nil(err) + + _, err = str.SelectActionByID(8) + assert.NotNil(err) + _, err = str2.SelectActionByID(8) + assert.NotNil(err) + + _, err = str.SelectActions() + assert.NotNil(err) + + _, err = str.SelectActionsByPlanID(&models.Plan{}) + assert.NotNil(err) + + replacementAction := &models.Action{} + err = str.UpdateAction(replacementAction) + assert.NotNil(err) + +} + +func TestErrorPlanMethods(t *testing.T) { + assert := assert.New(t) + str := store.GetErrorStore("sntahoeu", true) + str2 := store.GetErrorStore("sntahoeu", false) + + _, err := str.SelectPlans() + assert.NotNil(err) + + _, err = str.InsertPlan(&models.Plan{}) + assert.NotNil(err) + _, err = str2.InsertPlan(&models.Plan{}) + assert.Nil(err) + + _, err = str.SelectPlanByID(5) + assert.NotNil(err) + +} + +func TestErrorLive(t *testing.T) { + assert := assert.New(t) + str := store.GetErrorStore("error", true) + + err := str.ConnectionLive() + assert.NotNil(err) +} + +func TestErrorUserMethods(t *testing.T) { + assert := assert.New(t) + str := store.GetErrorStore("error", true) + str2 := store.GetErrorStore("error", false) + + u := &models.User{} + + _, err := str.InsertUser(u) + assert.NotNil(err) + _, err = str2.InsertUser(u) + assert.Nil(err) + + _, err = str.SelectUserByUsername("snth") + assert.NotNil(err) +} From 2a3d789292b358ce7e3b9271fb765458336e0135 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 11:13:29 -0600 Subject: [PATCH 14/32] Makes error store allow update if error only on get --- store/errorStore.go | 5 ++++- store/errorStore_test.go | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/store/errorStore.go b/store/errorStore.go index d219bf2..1b306f4 100644 --- a/store/errorStore.go +++ b/store/errorStore.go @@ -21,7 +21,10 @@ func (e *errorStore) InsertAction(action *models.Action) (int, error) { } func (e *errorStore) UpdateAction(action *models.Action) error { - return e.error + if e.errorOnInsert { + return e.error + } + return nil } func (e *errorStore) SelectPlans() ([]*models.Plan, error) { diff --git a/store/errorStore_test.go b/store/errorStore_test.go index 9cbbf62..2ed5b46 100644 --- a/store/errorStore_test.go +++ b/store/errorStore_test.go @@ -31,6 +31,8 @@ func TestErrorActionMethods(t *testing.T) { replacementAction := &models.Action{} err = str.UpdateAction(replacementAction) assert.NotNil(err) + err = str2.UpdateAction(replacementAction) + assert.Nil(err) } From d02d48e7c81f35e74e7fb9169151e1c65e86459f Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 11:22:14 -0600 Subject: [PATCH 15/32] Adds user id field to plan --- models/plan.go | 1 + routes/plans.go | 3 ++- routes/plans_test.go | 10 ++++++---- routes/post_plan_test.go | 11 +++++++---- store/migrations/000001_create_action_table.up.sql | 1 + 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/models/plan.go b/models/plan.go index 02e0212..b6bb432 100644 --- a/models/plan.go +++ b/models/plan.go @@ -8,6 +8,7 @@ import ( type Plan struct { PlanID int64 `json:"plan_id"` PlanDate *time.Time `json:"plan_date"` + UserID int64 `json:"user_id"` } // Plans returns all plans in the model. diff --git a/routes/plans.go b/routes/plans.go index edcab49..ff1c64d 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -79,7 +79,8 @@ func postPlanFunc(m *models.Model) http.HandlerFunc { return } - plan := &models.Plan{PlanDate: p.PlanDate} + // Map the fields we allow to be set to the plan to be created. + plan := &models.Plan{PlanDate: p.PlanDate, UserID: p.UserID} id, err := m.AddPlan(plan) if err != nil { serverError(w, err) diff --git a/routes/plans_test.go b/routes/plans_test.go index 03fd375..51043be 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -37,7 +37,7 @@ func TestOnePlan(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} m := getEmptyModel() m.AddPlan(p) router := routes.NewPlanRouter(m) @@ -55,7 +55,8 @@ func TestOnePlan(t *testing.T) { expected := `[ { "plan_id": 1, - "plan_date": "2021-01-01T00:00:00Z" + "plan_date": "2021-01-01T00:00:00Z", + "user_id": 3 } ]` assert.JSONEq(expected, rr.Body.String()) @@ -109,7 +110,7 @@ func TestOnePlanByID(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} m := getEmptyModel() m.AddPlan(p) router := routes.NewPlanRouter(m) @@ -126,7 +127,8 @@ func TestOnePlanByID(t *testing.T) { // We pass in the date as a time.time so it makes sense that it comes back with a midnight timestamp. expected := `{ "plan_id": 1, - "plan_date": "2021-01-01T00:00:00Z" + "plan_date": "2021-01-01T00:00:00Z", + "user_id": 3 }` assert.JSONEq(expected, rr.Body.String()) contentType := rr.Header().Get("Content-Type") diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 77ec78a..9ad7902 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -17,7 +17,7 @@ func TestCreatePlanRoute(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} m := getEmptyModel() m.AddPlan(p) router := routes.NewPlanRouter(m) @@ -36,7 +36,8 @@ func TestCreatePlanRoute(t *testing.T) { expected := `{ "created_plan": { "plan_id": 2, - "plan_date": "2021-01-01T00:00:00Z" + "plan_date": "2021-01-01T00:00:00Z", + "user_id": 3 }, "id": 2 }` @@ -49,13 +50,14 @@ func TestPureJSON(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 1, PlanDate: &planDate} + p := &models.Plan{PlanID: 1, PlanDate: &planDate, UserID: 3} m := getEmptyModel() m.AddPlan(p) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", - "plan_id": 1 + "plan_id": 1, + "user_id": 3 }`) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") @@ -71,6 +73,7 @@ func TestPureJSON(t *testing.T) { expected := `{ "created_plan": { "plan_id": 2, + "user_id": 3, "plan_date": "2021-01-01T00:00:00Z" }, "id": 2 diff --git a/store/migrations/000001_create_action_table.up.sql b/store/migrations/000001_create_action_table.up.sql index 9b48f0b..b95dcd0 100644 --- a/store/migrations/000001_create_action_table.up.sql +++ b/store/migrations/000001_create_action_table.up.sql @@ -1,6 +1,7 @@ CREATE TABLE IF NOT EXISTS plans( plan_id serial PRIMARY KEY, plan_date DATE NOT NULL, + user_id int REFERENCES users(user_id), created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL ); From 49898483353144acffe9c4adef519dc61b600850 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 11:25:38 -0600 Subject: [PATCH 16/32] Fixes table create order --- .../000001_create_action_table.up.sql | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/store/migrations/000001_create_action_table.up.sql b/store/migrations/000001_create_action_table.up.sql index b95dcd0..7c13be1 100644 --- a/store/migrations/000001_create_action_table.up.sql +++ b/store/migrations/000001_create_action_table.up.sql @@ -1,3 +1,12 @@ +CREATE TABLE IF NOT EXISTS users( + user_id serial PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + display_name VARCHAR (100) NOT NULL, + password bytea, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL +); + CREATE TABLE IF NOT EXISTS plans( plan_id serial PRIMARY KEY, plan_date DATE NOT NULL, @@ -17,16 +26,6 @@ CREATE TABLE IF NOT EXISTS actions( plan_id int REFERENCES plans(plan_id) ); -CREATE TABLE IF NOT EXISTS users( - user_id serial PRIMARY KEY, - username VARCHAR(50) NOT NULL UNIQUE, - display_name VARCHAR (100) NOT NULL, - password bytea, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL -); - - CREATE OR REPLACE FUNCTION trigger_set_timestamp() RETURNS TRIGGER AS $set_updated$ BEGIN From 4708a2b8ff0cec9218729c55a2b33590002f000d Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 11:37:44 -0600 Subject: [PATCH 17/32] adds user_id as a selectable field --- store/postgres.go | 8 +- store/postgres_plan_test.go | 176 ++++++++++++++++++++++++++++++++++++ store/postgres_test.go | 159 -------------------------------- 3 files changed, 180 insertions(+), 163 deletions(-) create mode 100644 store/postgres_plan_test.go diff --git a/store/postgres.go b/store/postgres.go index 271afc5..fc1f697 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -99,7 +99,7 @@ func (store *postgresStore) UpdateAction(action *models.Action) error { func (store *postgresStore) SelectPlans() ([]*models.Plan, error) { plans := make([]*models.Plan, 0) - err := store.db.Select(&plans, "SELECT plan_id, plan_date FROM plans") + err := store.db.Select(&plans, "SELECT plan_id, plan_date, user_id FROM plans") if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (store *postgresStore) SelectPlans() ([]*models.Plan, error) { func (store *postgresStore) SelectPlanByID(id int) (*models.Plan, error) { plan := models.Plan{} - err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date FROM plans WHERE plan_id = ?"), id) + err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = ?"), id) if err != nil { return nil, err } @@ -116,10 +116,10 @@ func (store *postgresStore) SelectPlanByID(id int) (*models.Plan, error) { } func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) { - queryString := store.db.Rebind("INSERT INTO plans (plan_date) VALUES (?) RETURNING plan_id") + queryString := store.db.Rebind("INSERT INTO plans (plan_date, user_id) VALUES (?, ?) RETURNING plan_id") tx := store.db.MustBegin() var id int - err := tx.Get(&id, queryString, plan.PlanDate) + err := tx.Get(&id, queryString, plan.PlanDate, plan.UserID) if err != nil { tx.Rollback() return -1, err diff --git a/store/postgres_plan_test.go b/store/postgres_plan_test.go new file mode 100644 index 0000000..a02b1d2 --- /dev/null +++ b/store/postgres_plan_test.go @@ -0,0 +1,176 @@ +package store_test + +import ( + "fmt" + "gitea.deepak.science/deepak/gogmagog/models" + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestSelectPlans(t *testing.T) { + assert := assert.New(t) + + currentTime := time.Now() + idToUse := 1 + userIDToUse := 2 + + str, mock := getDbMock(t) + + rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse) + mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans$").WillReturnRows(rows) + + plans, err := str.SelectPlans() + assert.Nil(err) + assert.Equal(1, len(plans)) + plan := plans[0] + assert.EqualValues(idToUse, plan.PlanID) + assert.Equal(currentTime, *plan.PlanDate) + assert.EqualValues(userIDToUse, plan.UserID) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} + +func TestSelectPlanByID(t *testing.T) { + assert := assert.New(t) + + currentTime := time.Now() + idToUse := 1 + userIDToUse := 2 + + str, mock := getDbMock(t) + + rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse) + mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \\$1$").WithArgs(idToUse).WillReturnRows(rows) + + plan, err := str.SelectPlanByID(idToUse) + assert.Nil(err) + assert.EqualValues(idToUse, plan.PlanID) + assert.Equal(currentTime, *plan.PlanDate) + assert.EqualValues(userIDToUse, plan.UserID) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} + +func TestInsertPlan(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + userID := 2 + + plan := &models.Plan{PlanDate: &planDate, UserID: int64(userID)} + + idToUse := 8 + + rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(8) + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO plans \(plan_date, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`). + WithArgs(planDate, userID). + WillReturnRows(rows) + mock.ExpectCommit() + + // function under test + insertedId, err := str.InsertPlan(plan) + // check results + assert.Nil(err) + assert.EqualValues(idToUse, insertedId) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +} + +func TestInsertPlanErr(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + userID := 2 + plan := &models.Plan{PlanDate: &planDate, UserID: 2} + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO plans \(plan_date, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`). + WithArgs(planDate, userID). + WillReturnError(fmt.Errorf("example error")) + mock.ExpectRollback() + + // function under test + _, err := str.InsertPlan(plan) + // check results + assert.NotNil(err) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +} + +func TestInsertPlanCommitErr(t *testing.T) { + // setup + assert := assert.New(t) + + str, mock := getDbMock(t) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + userID := 2 + plan := &models.Plan{PlanDate: &planDate, UserID: int64(userID)} + idToUse := 8 + + rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(idToUse) + + mock.ExpectBegin() + mock.ExpectQuery(`^INSERT INTO plans \(plan_date, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`). + WithArgs(planDate, userID). + WillReturnRows(rows) + mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example")) + + // function under test + _, err := str.InsertPlan(plan) + // check results + assert.NotNil(err) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } + +} + +func TestErrPlanByID(t *testing.T) { + assert := assert.New(t) + + idToUse := 1 + + str, mock := getDbMock(t) + + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1$`).WithArgs(idToUse).WillReturnError(fmt.Errorf("example error")) + + plan, err := str.SelectPlanByID(idToUse) + assert.NotNil(err) + assert.Nil(plan) + + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} + +func TestErrPlans(t *testing.T) { + // set up tests + assert := assert.New(t) + str, mock := getDbMock(t) + + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans$`).WillReturnError(fmt.Errorf("example error")) + // function under test + plans, err := str.SelectPlans() + // test results + assert.Nil(plans) + assert.NotNil(err) + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("unfulfilled expectations: %s", err) + } +} diff --git a/store/postgres_test.go b/store/postgres_test.go index 4fe3371..2a309aa 100644 --- a/store/postgres_test.go +++ b/store/postgres_test.go @@ -24,149 +24,6 @@ func getDbMock(t *testing.T) (models.Store, sqlmock.Sqlmock) { return str, mock } -func TestSelectPlans(t *testing.T) { - assert := assert.New(t) - - currentTime := time.Now() - idToUse := 1 - - str, mock := getDbMock(t) - - rows := sqlmock.NewRows([]string{"plan_id", "plan_date"}).AddRow(idToUse, currentTime) - mock.ExpectQuery("^SELECT plan_id, plan_date FROM plans$").WillReturnRows(rows) - - plans, err := str.SelectPlans() - assert.Nil(err) - assert.Equal(1, len(plans)) - plan := plans[0] - assert.EqualValues(idToUse, plan.PlanID) - assert.Equal(currentTime, *plan.PlanDate) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } -} - -func TestSelectPlanByID(t *testing.T) { - assert := assert.New(t) - - currentTime := time.Now() - idToUse := 1 - - str, mock := getDbMock(t) - - rows := sqlmock.NewRows([]string{"plan_id", "plan_date"}).AddRow(idToUse, currentTime) - mock.ExpectQuery("^SELECT plan_id, plan_date FROM plans WHERE plan_id = \\$1$").WithArgs(idToUse).WillReturnRows(rows) - - plan, err := str.SelectPlanByID(idToUse) - assert.Nil(err) - assert.EqualValues(idToUse, plan.PlanID) - assert.Equal(currentTime, *plan.PlanDate) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } -} - -func TestInsertPlan(t *testing.T) { - // setup - assert := assert.New(t) - - str, mock := getDbMock(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: &planDate} - - idToUse := 8 - - rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(8) - - mock.ExpectBegin() - mock.ExpectQuery("^INSERT INTO plans \\(plan_date\\) VALUES \\(\\$1\\) RETURNING plan_id$"). - WithArgs(planDate). - WillReturnRows(rows) - mock.ExpectCommit() - - // function under test - insertedId, err := str.InsertPlan(plan) - // check results - assert.Nil(err) - assert.EqualValues(idToUse, insertedId) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } - -} - -func TestInsertPlanErr(t *testing.T) { - // setup - assert := assert.New(t) - - str, mock := getDbMock(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: &planDate} - - mock.ExpectBegin() - mock.ExpectQuery("^INSERT INTO plans \\(plan_date\\) VALUES \\(\\$1\\) RETURNING plan_id$"). - WithArgs(planDate). - WillReturnError(fmt.Errorf("example error")) - mock.ExpectRollback() - - // function under test - _, err := str.InsertPlan(plan) - // check results - assert.NotNil(err) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } - -} - -func TestInsertPlanCommitErr(t *testing.T) { - // setup - assert := assert.New(t) - - str, mock := getDbMock(t) - planDate, _ := time.Parse("2006-01-02", "2021-01-01") - plan := &models.Plan{PlanDate: &planDate} - - idToUse := 8 - - rows := sqlmock.NewRows([]string{"plan_id"}).AddRow(idToUse) - - mock.ExpectBegin() - mock.ExpectQuery("^INSERT INTO plans \\(plan_date\\) VALUES \\(\\$1\\) RETURNING plan_id$"). - WithArgs(planDate). - WillReturnRows(rows) - mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example")) - - // function under test - _, err := str.InsertPlan(plan) - // check results - assert.NotNil(err) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } - -} - -func TestErrPlanByID(t *testing.T) { - assert := assert.New(t) - - idToUse := 1 - - str, mock := getDbMock(t) - - mock.ExpectQuery("^SELECT plan_id, plan_date FROM plans WHERE plan_id = \\$1$").WithArgs(idToUse).WillReturnError(fmt.Errorf("example error")) - - plan, err := str.SelectPlanByID(idToUse) - assert.NotNil(err) - assert.Nil(plan) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } -} - func TestSelectActions(t *testing.T) { // set up test assert := assert.New(t) @@ -338,22 +195,6 @@ func TestSelectActionById(t *testing.T) { } } -func TestErrPlans(t *testing.T) { - // set up tests - assert := assert.New(t) - str, mock := getDbMock(t) - - mock.ExpectQuery("^SELECT plan_id, plan_date FROM plans$").WillReturnError(fmt.Errorf("example error")) - // function under test - plans, err := str.SelectPlans() - // test results - assert.Nil(plans) - assert.NotNil(err) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("unfulfilled expectations: %s", err) - } -} - func TestErrActions(t *testing.T) { // set up tests assert := assert.New(t) From 8eff1115c59b7f00cbe03c975032df98458887f1 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 15:10:24 -0600 Subject: [PATCH 18/32] Fixes failing tests, but incompletely --- models/err_model_test.go | 53 +++---------------------------------- models/errors_test.go | 2 +- models/models.go | 6 ++--- models/models_test.go | 12 +++++---- models/plan.go | 12 ++++----- models/user_test.go | 2 +- routes/plans.go | 10 ++++--- routes/plans_test.go | 6 ++--- routes/post_action_test.go | 6 ++--- routes/post_plan_test.go | 22 ++++++++------- store/errorStore.go | 14 +++++++--- store/errorStore_test.go | 8 +++--- store/inmemory.go | 17 ++++++++---- store/inmemory_test.go | 12 ++++----- store/postgres.go | 13 ++++----- store/postgres_plan_test.go | 36 ++++++++++++++++--------- 16 files changed, 108 insertions(+), 123 deletions(-) diff --git a/models/err_model_test.go b/models/err_model_test.go index db1f899..3928c6b 100644 --- a/models/err_model_test.go +++ b/models/err_model_test.go @@ -2,57 +2,10 @@ package models_test import ( "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/store" ) -func (e *errorStore) SelectActions() ([]*models.Action, error) { - return nil, e.error -} - -func (e *errorStore) SelectActionByID(id int) (*models.Action, error) { - return nil, e.error -} - -func (e *errorStore) InsertAction(action *models.Action) (int, error) { - return 0, e.error -} - -func (e *errorStore) UpdateAction(action *models.Action) error { - return e.error -} - -func (e *errorStore) SelectPlans() ([]*models.Plan, error) { - return nil, e.error -} - -func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) { - return nil, e.error -} - -func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) { - return 0, e.error -} - -func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) { - return nil, e.error -} - -func (e *errorStore) SelectUserByUsername(username string) (*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 -} - -type errorStore struct { - error error -} - func getErrorModel(err error) *models.Model { - e := &errorStore{error: err} - return models.New(e) + str := store.GetErrorStoreForError(err, true) + return models.New(str) } diff --git a/models/errors_test.go b/models/errors_test.go index 20b0749..99ed40f 100644 --- a/models/errors_test.go +++ b/models/errors_test.go @@ -35,7 +35,7 @@ func TestErrorModelWrapping(t *testing.T) { assert := assert.New(t) m := getErrorModel(sql.ErrNoRows) - _, err := m.Plan(0) + _, err := m.Plan(0, 0) assert.True(models.IsNotFoundError(err)) _, err = m.Action(0) assert.True(models.IsNotFoundError(err)) diff --git a/models/models.go b/models/models.go index 586141f..02c4e4f 100644 --- a/models/models.go +++ b/models/models.go @@ -11,9 +11,9 @@ type Store interface { SelectActionByID(id int) (*Action, error) InsertAction(action *Action) (int, error) UpdateAction(action *Action) error - SelectPlans() ([]*Plan, error) - SelectPlanByID(id int) (*Plan, error) - InsertPlan(plan *Plan) (int, error) + SelectPlans(userID int) ([]*Plan, error) + SelectPlanByID(id int, userID int) (*Plan, error) + InsertPlan(plan *Plan, userID int) (int, error) SelectActionsByPlanID(plan *Plan) ([]*Action, error) SelectUserByUsername(username string) (*User, error) InsertUser(user *User) (int, error) diff --git a/models/models_test.go b/models/models_test.go index acb9502..a82acc8 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -11,11 +11,12 @@ func TestModelActions(t *testing.T) { assert := assert.New(t) a1 := &models.Action{ActionID: 3} a2 := &models.Action{ActionID: 4} + userID := 3 p := &models.Plan{PlanID: 6} str, _ := store.GetInMemoryStore() str.InsertAction(a1) - str.InsertPlan(p) + str.InsertPlan(p, userID) m := models.New(str) actions, err := m.Actions() @@ -36,21 +37,22 @@ func TestModelActions(t *testing.T) { func TestModelPlanMethods(t *testing.T) { assert := assert.New(t) + userID := 3 a1 := &models.Action{ActionID: 3, PlanID: 1} a2 := &models.Action{ActionID: 4} p := &models.Plan{} str, _ := store.GetInMemoryStore() - str.InsertPlan(p) + str.InsertPlan(p, userID) str.InsertAction(a1) str.InsertAction(a2) m := models.New(str) - plans, err := m.Plans() + plans, err := m.Plans(userID) assert.Nil(err) assert.Equal(1, len(plans)) - firstPlan, err := m.Plan(1) + firstPlan, err := m.Plan(1, userID) assert.Nil(err) assert.EqualValues(1, firstPlan.PlanID) @@ -58,7 +60,7 @@ func TestModelPlanMethods(t *testing.T) { assert.Nil(err) assert.Equal(1, len(actions)) - planId, err := m.AddPlan(&models.Plan{}) + planId, err := m.AddPlan(&models.Plan{}, userID) assert.Nil(err) assert.EqualValues(2, planId) } diff --git a/models/plan.go b/models/plan.go index b6bb432..f661b2d 100644 --- a/models/plan.go +++ b/models/plan.go @@ -12,19 +12,19 @@ type Plan struct { } // Plans returns all plans in the model. -func (m *Model) Plans() ([]*Plan, error) { - return m.SelectPlans() +func (m *Model) Plans(userID int) ([]*Plan, error) { + return m.SelectPlans(userID) } // Plan returns a single plan from the store by plan_id. -func (m *Model) Plan(id int) (*Plan, error) { - plan, err := m.SelectPlanByID(id) +func (m *Model) Plan(id int, userID int) (*Plan, error) { + plan, err := m.SelectPlanByID(id, userID) return plan, wrapNotFound(err) } // AddPlan inserts a given plan into the store, returning the generated PlanID. The provided PlanID is ignored. -func (m *Model) AddPlan(plan *Plan) (int, error) { - return m.InsertPlan(plan) +func (m *Model) AddPlan(plan *Plan, userID int) (int, error) { + return m.InsertPlan(plan, userID) } // GetActions returns the actions associated with a particular plan. diff --git a/models/user_test.go b/models/user_test.go index 05ca5db..a161293 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -19,7 +19,7 @@ func TestModelUsers(t *testing.T) { // password := password user1 := &models.User{Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")} str, _ := store.GetInMemoryStore() - str.InsertPlan(p) + str.InsertPlan(p, 3) str.InsertAction(a1) str.InsertAction(a2) str.InsertUser(user1) diff --git a/routes/plans.go b/routes/plans.go index ff1c64d..5e0262e 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -20,7 +20,8 @@ func NewPlanRouter(m *models.Model) http.Handler { func getAllPlansFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - plans, err := m.Plans() + // todo enough to get build + plans, err := m.Plans(3) if err != nil { serverError(w, err) return @@ -39,7 +40,8 @@ func getPlanByIDFunc(m *models.Model) http.HandlerFunc { notFoundHandler(w, r) return } - plan, err := m.Plan(id) + // todo get real user id + plan, err := m.Plan(id, 3) if err != nil { if models.IsNotFoundError(err) { notFoundHandler(w, r) @@ -81,12 +83,12 @@ func postPlanFunc(m *models.Model) http.HandlerFunc { // Map the fields we allow to be set to the plan to be created. plan := &models.Plan{PlanDate: p.PlanDate, UserID: p.UserID} - id, err := m.AddPlan(plan) + id, err := m.AddPlan(plan, 3) if err != nil { serverError(w, err) return } - plan, err = m.Plan(id) + plan, err = m.Plan(id, 3) if err != nil { serverError(w, err) return diff --git a/routes/plans_test.go b/routes/plans_test.go index 51043be..3ac622a 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -39,7 +39,7 @@ func TestOnePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewPlanRouter(m) req, _ := http.NewRequest("GET", "/", nil) @@ -112,7 +112,7 @@ func TestOnePlanByID(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewPlanRouter(m) req, _ := http.NewRequest("GET", "/1", nil) @@ -164,7 +164,7 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { p := &models.Plan{PlanID: 1, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewPlanRouter(m) req, _ := http.NewRequest("GET", "/1", nil) diff --git a/routes/post_action_test.go b/routes/post_action_test.go index 49261c9..0e87b07 100644 --- a/routes/post_action_test.go +++ b/routes/post_action_test.go @@ -67,7 +67,7 @@ func TestExtraFieldActionPostJSON(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewActionRouter(m) data := []byte(`{ "completed_on": "2021-01-01T00:00:00Z", @@ -94,7 +94,7 @@ func TestEmptyBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewActionRouter(m) data := []byte(``) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -118,7 +118,7 @@ func TestTwoBodyActionPost(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewActionRouter(m) data := []byte(`{ "plan_id": 5 diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 9ad7902..3320b21 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -17,9 +17,10 @@ func TestCreatePlanRoute(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} + userID := 3 + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: int64(userID)} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -50,9 +51,10 @@ func TestPureJSON(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 1, PlanDate: &planDate, UserID: 3} + userID := 3 + p := &models.Plan{PlanID: 1, PlanDate: &planDate, UserID: int64(userID)} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", @@ -87,9 +89,10 @@ func TestExtraFieldJSON(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") - p := &models.Plan{PlanID: 6, PlanDate: &planDate} + userID := 3 + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: int64(userID)} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", @@ -114,9 +117,10 @@ func TestEmptyBody(t *testing.T) { // set up assert := assert.New(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") + userID := 3 p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data := []byte(``) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) @@ -140,7 +144,7 @@ func TestTwoBody(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewPlanRouter(m) data := []byte(`{ "plan_date": "2021-01-01T00:00:00Z", @@ -222,7 +226,7 @@ func TestErrorWriterCreatePlan(t *testing.T) { p := &models.Plan{PlanID: 6, PlanDate: &planDate} m := getEmptyModel() - m.AddPlan(p) + m.AddPlan(p, 3) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) diff --git a/store/errorStore.go b/store/errorStore.go index 1b306f4..8cadfcf 100644 --- a/store/errorStore.go +++ b/store/errorStore.go @@ -27,15 +27,15 @@ func (e *errorStore) UpdateAction(action *models.Action) error { return nil } -func (e *errorStore) SelectPlans() ([]*models.Plan, error) { +func (e *errorStore) SelectPlans(userID int) ([]*models.Plan, error) { return nil, e.error } -func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) { +func (e *errorStore) SelectPlanByID(id int, userID int) (*models.Plan, error) { return nil, e.error } -func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) { +func (e *errorStore) InsertPlan(plan *models.Plan, userID int) (int, error) { if e.errorOnInsert { return 0, e.error } @@ -66,8 +66,14 @@ type errorStore struct { errorOnInsert bool } -// GetErrorStore returns a models.Store that always errors. This is useful for testirng purposes. +// GetErrorStore returns a models.Store that always errors. This is useful for testing purposes. func GetErrorStore(errorMsg string, errorOnInsert bool) models.Store { e := &errorStore{error: fmt.Errorf(errorMsg), errorOnInsert: errorOnInsert} return e } + +// GetErrorStoreForError returns a models.Store that always errors with the provided error. +func GetErrorStoreForError(err error, errorOnInsert bool) models.Store { + e := &errorStore{error: err, errorOnInsert: errorOnInsert} + return e +} diff --git a/store/errorStore_test.go b/store/errorStore_test.go index 2ed5b46..6102421 100644 --- a/store/errorStore_test.go +++ b/store/errorStore_test.go @@ -41,15 +41,15 @@ func TestErrorPlanMethods(t *testing.T) { str := store.GetErrorStore("sntahoeu", true) str2 := store.GetErrorStore("sntahoeu", false) - _, err := str.SelectPlans() + _, err := str.SelectPlans(3) assert.NotNil(err) - _, err = str.InsertPlan(&models.Plan{}) + _, err = str.InsertPlan(&models.Plan{}, 3) assert.NotNil(err) - _, err = str2.InsertPlan(&models.Plan{}) + _, err = str2.InsertPlan(&models.Plan{}, 3) assert.Nil(err) - _, err = str.SelectPlanByID(5) + _, err = str.SelectPlanByID(5, 3) assert.NotNil(err) } diff --git a/store/inmemory.go b/store/inmemory.go index bfaac57..26c49bf 100644 --- a/store/inmemory.go +++ b/store/inmemory.go @@ -64,22 +64,29 @@ func (store *inMemoryStore) UpdateAction(action *models.Action) error { } -func (store *inMemoryStore) SelectPlans() ([]*models.Plan, error) { - return store.plans, nil +func (store *inMemoryStore) SelectPlans(userID int) ([]*models.Plan, error) { + ret := make([]*models.Plan, 0) + for _, plan := range store.plans { + if int(plan.UserID) == userID { + ret = append(ret, plan) + } + } + return ret, nil } -func (store *inMemoryStore) SelectPlanByID(id int) (*models.Plan, error) { +func (store *inMemoryStore) SelectPlanByID(id int, userID int) (*models.Plan, error) { for _, plan := range store.plans { - if id == int(plan.PlanID) { + if id == int(plan.PlanID) && (userID == int(plan.UserID)) { return plan, nil } } return nil, sql.ErrNoRows } -func (store *inMemoryStore) InsertPlan(plan *models.Plan) (int, error) { +func (store *inMemoryStore) InsertPlan(plan *models.Plan, userID int) (int, error) { id := len(store.plans) + 1 plan.PlanID = int64(id) + plan.UserID = int64(userID) store.plans = append(store.plans, plan) return id, nil } diff --git a/store/inmemory_test.go b/store/inmemory_test.go index 3617e76..7b3bf51 100644 --- a/store/inmemory_test.go +++ b/store/inmemory_test.go @@ -55,24 +55,24 @@ func TestInMemoryActionMethods(t *testing.T) { func TestInMemoryPlanMethods(t *testing.T) { assert := assert.New(t) str, _ := store.GetInMemoryStore() - + userID := 1 p := &models.Plan{} - plans, err := str.SelectPlans() + plans, err := str.SelectPlans(userID) assert.Nil(err) assert.EqualValues(0, len(plans)) - id, err := str.InsertPlan(p) - plans, err = str.SelectPlans() + id, err := str.InsertPlan(p, userID) + plans, err = str.SelectPlans(userID) assert.Nil(err) assert.EqualValues(1, len(plans)) - retrievedPlan, err := str.SelectPlanByID(id) + retrievedPlan, err := str.SelectPlanByID(id, userID) assert.Nil(err) assert.Equal(retrievedPlan, p) - _, err = str.SelectPlanByID(135135) + _, err = str.SelectPlanByID(135135, userID) assert.NotNil(err) } diff --git a/store/postgres.go b/store/postgres.go index fc1f697..667b4d7 100644 --- a/store/postgres.go +++ b/store/postgres.go @@ -97,29 +97,30 @@ func (store *postgresStore) UpdateAction(action *models.Action) error { } -func (store *postgresStore) SelectPlans() ([]*models.Plan, error) { +func (store *postgresStore) SelectPlans(userID int) ([]*models.Plan, error) { + queryString := store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = ?") plans := make([]*models.Plan, 0) - err := store.db.Select(&plans, "SELECT plan_id, plan_date, user_id FROM plans") + err := store.db.Select(&plans, queryString, userID) if err != nil { return nil, err } return plans, nil } -func (store *postgresStore) SelectPlanByID(id int) (*models.Plan, error) { +func (store *postgresStore) SelectPlanByID(id int, userID int) (*models.Plan, error) { plan := models.Plan{} - err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = ?"), id) + err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = ? AND user_id = ?"), id, userID) if err != nil { return nil, err } return &plan, nil } -func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) { +func (store *postgresStore) InsertPlan(plan *models.Plan, userID int) (int, error) { queryString := store.db.Rebind("INSERT INTO plans (plan_date, user_id) VALUES (?, ?) RETURNING plan_id") tx := store.db.MustBegin() var id int - err := tx.Get(&id, queryString, plan.PlanDate, plan.UserID) + err := tx.Get(&id, queryString, plan.PlanDate, userID) if err != nil { tx.Rollback() return -1, err diff --git a/store/postgres_plan_test.go b/store/postgres_plan_test.go index a02b1d2..fdf1ed8 100644 --- a/store/postgres_plan_test.go +++ b/store/postgres_plan_test.go @@ -19,9 +19,11 @@ func TestSelectPlans(t *testing.T) { str, mock := getDbMock(t) rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse) - mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans$").WillReturnRows(rows) + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = \$1`). + WithArgs(userIDToUse). + WillReturnRows(rows) - plans, err := str.SelectPlans() + plans, err := str.SelectPlans(userIDToUse) assert.Nil(err) assert.Equal(1, len(plans)) plan := plans[0] @@ -44,9 +46,11 @@ func TestSelectPlanByID(t *testing.T) { str, mock := getDbMock(t) rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse) - mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \\$1$").WithArgs(idToUse).WillReturnRows(rows) + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`). + WithArgs(idToUse, userIDToUse). + WillReturnRows(rows) - plan, err := str.SelectPlanByID(idToUse) + plan, err := str.SelectPlanByID(idToUse, userIDToUse) assert.Nil(err) assert.EqualValues(idToUse, plan.PlanID) assert.Equal(currentTime, *plan.PlanDate) @@ -64,8 +68,9 @@ func TestInsertPlan(t *testing.T) { str, mock := getDbMock(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") userID := 2 + badUserID := 7 - plan := &models.Plan{PlanDate: &planDate, UserID: int64(userID)} + plan := &models.Plan{PlanDate: &planDate, UserID: int64(badUserID)} idToUse := 8 @@ -78,7 +83,7 @@ func TestInsertPlan(t *testing.T) { mock.ExpectCommit() // function under test - insertedId, err := str.InsertPlan(plan) + insertedId, err := str.InsertPlan(plan, userID) // check results assert.Nil(err) assert.EqualValues(idToUse, insertedId) @@ -95,7 +100,8 @@ func TestInsertPlanErr(t *testing.T) { str, mock := getDbMock(t) planDate, _ := time.Parse("2006-01-02", "2021-01-01") userID := 2 - plan := &models.Plan{PlanDate: &planDate, UserID: 2} + badUserID := 7 + plan := &models.Plan{PlanDate: &planDate, UserID: int64(badUserID)} mock.ExpectBegin() mock.ExpectQuery(`^INSERT INTO plans \(plan_date, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`). @@ -104,7 +110,7 @@ func TestInsertPlanErr(t *testing.T) { mock.ExpectRollback() // function under test - _, err := str.InsertPlan(plan) + _, err := str.InsertPlan(plan, userID) // check results assert.NotNil(err) if err := mock.ExpectationsWereMet(); err != nil { @@ -132,7 +138,7 @@ func TestInsertPlanCommitErr(t *testing.T) { mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example")) // function under test - _, err := str.InsertPlan(plan) + _, err := str.InsertPlan(plan, userID) // check results assert.NotNil(err) if err := mock.ExpectationsWereMet(); err != nil { @@ -148,9 +154,11 @@ func TestErrPlanByID(t *testing.T) { str, mock := getDbMock(t) - mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1$`).WithArgs(idToUse).WillReturnError(fmt.Errorf("example error")) + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`). + WithArgs(idToUse, 8). + WillReturnError(fmt.Errorf("example error")) - plan, err := str.SelectPlanByID(idToUse) + plan, err := str.SelectPlanByID(idToUse, 8) assert.NotNil(err) assert.Nil(plan) @@ -164,9 +172,11 @@ func TestErrPlans(t *testing.T) { assert := assert.New(t) str, mock := getDbMock(t) - mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans$`).WillReturnError(fmt.Errorf("example error")) + mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = \$1$`). + WithArgs(8). + WillReturnError(fmt.Errorf("example error")) // function under test - plans, err := str.SelectPlans() + plans, err := str.SelectPlans(8) // test results assert.Nil(plans) assert.NotNil(err) From c1ae0706f9c1d0eafeb051dc4b7757471ec39ded Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 17 Jan 2021 17:33:40 -0600 Subject: [PATCH 19/32] Route now gets user ID from context with custom middleware --- go.mod | 1 + routes/plans.go | 33 ++++++++++++++---- routes/plans_test.go | 21 +++++++----- routes/post_plan_test.go | 16 ++++----- routes/routes.go | 8 +---- tokens/middleware.go | 71 +++++++++++++++++++++++++++++++++++++++ tokens/middleware_test.go | 56 ++++++++++++++++++++++++++++++ tokens/tokens.go | 39 ++++++++++++++++----- tokens/tokens_test.go | 6 ++-- 9 files changed, 211 insertions(+), 40 deletions(-) create mode 100644 tokens/middleware.go create mode 100644 tokens/middleware_test.go diff --git a/go.mod b/go.mod index cf2b86e..cd079e2 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.14.1 github.com/jackc/pgx/v4 v4.10.1 github.com/jmoiron/sqlx v1.2.0 + github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029 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/routes/plans.go b/routes/plans.go index 5e0262e..8d9c373 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -3,8 +3,10 @@ package routes import ( "encoding/json" "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" "io" + "log" "net/http" "strconv" ) @@ -20,8 +22,14 @@ func NewPlanRouter(m *models.Model) http.Handler { func getAllPlansFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // todo enough to get build - plans, err := m.Plans(3) + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + log.Print(err) + unauthorizedHandler(w, r) + return + } + + plans, err := m.Plans(userID) if err != nil { serverError(w, err) return @@ -35,13 +43,20 @@ func getAllPlansFunc(m *models.Model) http.HandlerFunc { func getPlanByIDFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + log.Print(err) + unauthorizedHandler(w, r) + return + } + id, err := strconv.Atoi(chi.URLParam(r, "planid")) if err != nil { notFoundHandler(w, r) return } // todo get real user id - plan, err := m.Plan(id, 3) + plan, err := m.Plan(id, userID) if err != nil { if models.IsNotFoundError(err) { notFoundHandler(w, r) @@ -65,12 +80,18 @@ type createPlanResponse struct { func postPlanFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + log.Print(err) + unauthorizedHandler(w, r) + return + } r.Body = http.MaxBytesReader(w, r.Body, 1024) dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() var p models.Plan - err := dec.Decode(&p) + err = dec.Decode(&p) if err != nil { badRequestError(w, err) return @@ -83,12 +104,12 @@ func postPlanFunc(m *models.Model) http.HandlerFunc { // Map the fields we allow to be set to the plan to be created. plan := &models.Plan{PlanDate: p.PlanDate, UserID: p.UserID} - id, err := m.AddPlan(plan, 3) + id, err := m.AddPlan(plan, userID) if err != nil { serverError(w, err) return } - plan, err = m.Plan(id, 3) + plan, err = m.Plan(id, userID) if err != nil { serverError(w, err) return diff --git a/routes/plans_test.go b/routes/plans_test.go index 3ac622a..16c2e13 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -3,6 +3,7 @@ package routes_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" @@ -11,12 +12,14 @@ import ( "time" ) +var sampleContext = tokens.GetContextForUserID(3) + func TestEmptyPlans(t *testing.T) { // set up assert := assert.New(t) m := getEmptyModel() router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil) rr := httptest.NewRecorder() @@ -41,7 +44,7 @@ func TestOnePlan(t *testing.T) { m := getEmptyModel() m.AddPlan(p, 3) router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil) rr := httptest.NewRecorder() @@ -71,7 +74,7 @@ func TestErrorPlan(t *testing.T) { m := getErrorModel("Model always errors") router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil) rr := httptest.NewRecorder() @@ -93,7 +96,7 @@ func TestEmptyPlanErrorWriter(t *testing.T) { m := getEmptyModel() router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil) rr := NewBadWriter() @@ -114,7 +117,7 @@ func TestOnePlanByID(t *testing.T) { m := getEmptyModel() m.AddPlan(p, 3) router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/1", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil) rr := httptest.NewRecorder() @@ -142,7 +145,7 @@ func TestErrorPlanByID(t *testing.T) { m := getErrorModel("Model always errors") router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/5", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/5", nil) rr := httptest.NewRecorder() @@ -167,7 +170,7 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) { m.AddPlan(p, 3) router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/1", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil) rr := NewBadWriter() @@ -187,7 +190,7 @@ func TestNotFoundPlanByIDText(t *testing.T) { m := getEmptyModel() router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/wo", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/wo", nil) rr := httptest.NewRecorder() @@ -206,7 +209,7 @@ func TestNotFoundPlanByIDEmpty(t *testing.T) { m := getEmptyModel() router := routes.NewPlanRouter(m) - req, _ := http.NewRequest("GET", "/1", nil) + req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil) rr := httptest.NewRecorder() diff --git a/routes/post_plan_test.go b/routes/post_plan_test.go index 3320b21..699c948 100644 --- a/routes/post_plan_test.go +++ b/routes/post_plan_test.go @@ -23,7 +23,7 @@ func TestCreatePlanRoute(t *testing.T) { m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -61,7 +61,7 @@ func TestPureJSON(t *testing.T) { "plan_id": 1, "user_id": 3 }`) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -99,7 +99,7 @@ func TestExtraFieldJSON(t *testing.T) { "plan_id": 5, "plan_sabotage": "omg" }`) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -123,7 +123,7 @@ func TestEmptyBody(t *testing.T) { m.AddPlan(p, userID) router := routes.NewPlanRouter(m) data := []byte(``) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -153,7 +153,7 @@ func TestTwoBody(t *testing.T) { "plan_date": "2021-01-01T00:00:00Z", "plan_id": 6 }`) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -178,7 +178,7 @@ func TestErrorCreatePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -204,7 +204,7 @@ func TestErrorOnRetrieveCreatePlan(t *testing.T) { planDate, _ := time.Parse("2006-01-02", "2021-01-01") p := &models.Plan{PlanID: 6, PlanDate: &planDate} data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() @@ -229,7 +229,7 @@ func TestErrorWriterCreatePlan(t *testing.T) { m.AddPlan(p, 3) router := routes.NewPlanRouter(m) data, _ := json.Marshal(p) - req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data)) + req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data)) req.Header.Set("Content-Type", "application/json") rr := NewBadWriter() diff --git a/routes/routes.go b/routes/routes.go index b5501b3..078a1fd 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -5,7 +5,6 @@ import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" - "github.com/go-chi/jwtauth" "net/http" ) @@ -15,12 +14,7 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { router.MethodNotAllowed(methodNotAllowedHandler) router.NotFound(notFoundHandler) router.Group(func(r chi.Router) { - r.Use(tok.Verifier()) - // Handle valid / invalid tokens. In this example, we use - // the provided authenticator middleware, but you can write your - // own very easily, look at the Authenticator method in jwtauth.go - // and tweak it, its not scary. - r.Use(jwtauth.Authenticator) + r.Use(tok.Authenticator) r.Mount("/actions", NewActionRouter(m)) r.Mount("/plans", NewPlanRouter(m)) }) diff --git a/tokens/middleware.go b/tokens/middleware.go new file mode 100644 index 0000000..04529a1 --- /dev/null +++ b/tokens/middleware.go @@ -0,0 +1,71 @@ +package tokens + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" +) + +type contextKey struct { + name string +} + +var userIDCtxKey = &contextKey{"UserID"} + +func unauthorized(w http.ResponseWriter, r *http.Request) { + code := http.StatusUnauthorized + http.Error(w, http.StatusText(code), code) +} + +// TokenFromHeader tries to retreive the token string from the +// "Authorization" reqeust header: "Authorization: BEARER T". +func TokenFromHeader(r *http.Request) string { + // Get token from authorization header. + bearer := r.Header.Get("Authorization") + if len(bearer) > 7 && strings.ToUpper(bearer[0:6]) == "BEARER" { + return bearer[7:] + } + return "" +} + +func (tok *jwtToker) Authenticator(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tokenString := TokenFromHeader(r) + if tokenString == "" { + log.Print("No valid token found") + unauthorized(w, r) + return + } + + userID, err := tok.DecodeTokenString(tokenString) + if err != nil { + log.Printf("Error while verifying token: %s", err) + unauthorized(w, r) + return + } + + log.Printf("Got user with ID: [%d]", userID) + ctx := context.WithValue(r.Context(), userIDCtxKey, userID) + // Authenticated + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// GetUserID is a convenience method that gets the user ID from the context. +// I hate the fact that we're passing user ID on the context, but it is more +// idiomatic Go than any type shenanigans. +func GetUserID(ctx context.Context) (int, error) { + userID, ok := ctx.Value(userIDCtxKey).(int64) + if !ok { + return -1, fmt.Errorf("Could not parse user ID [%s] from context", ctx.Value(userIDCtxKey)) + + } + return int(userID), nil +} + +// GetContextForUserID is a test helper method that creates a context with user ID set. +func GetContextForUserID(userID int) context.Context { + return context.WithValue(context.Background(), userIDCtxKey, int64(userID)) +} diff --git a/tokens/middleware_test.go b/tokens/middleware_test.go new file mode 100644 index 0000000..f2ba184 --- /dev/null +++ b/tokens/middleware_test.go @@ -0,0 +1,56 @@ +package tokens_test + +import ( + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +var ( + url = "" + authKey = "Authorization" +) + +func requestWithAuth(header string) *http.Request { + req, _ := http.NewRequest("GET", url, nil) + req.Header.Add(authKey, header) + + return req +} + +func TestHeaderParseBasic(t *testing.T) { + assert := assert.New(t) + + header := "Bearer testing" + req := requestWithAuth(header) + + assert.Equal("testing", tokens.TokenFromHeader(req)) +} + +func TestHeaderParseNoSpace(t *testing.T) { + assert := assert.New(t) + + header := "Bearerxtesting" + req := requestWithAuth(header) + + assert.Equal("testing", tokens.TokenFromHeader(req)) +} + +func TestHeaderParseUnicode(t *testing.T) { + assert := assert.New(t) + + header := "Bearer 🌸" + req := requestWithAuth(header) + + assert.Equal("🌸", tokens.TokenFromHeader(req)) +} + +func TestHeaderParseMalformed(t *testing.T) { + assert := assert.New(t) + + header := "testing" + req := requestWithAuth(header) + + assert.Equal("", tokens.TokenFromHeader(req)) +} diff --git a/tokens/tokens.go b/tokens/tokens.go index 6ee2e69..a0d3e45 100644 --- a/tokens/tokens.go +++ b/tokens/tokens.go @@ -1,8 +1,10 @@ package tokens import ( + "fmt" "gitea.deepak.science/deepak/gogmagog/models" "github.com/go-chi/jwtauth" + "github.com/lestrrat-go/jwx/jwt" "net/http" "time" ) @@ -10,8 +12,8 @@ import ( // Toker represents a tokenizer, capable of encoding and verifying tokens. type Toker interface { EncodeUser(user *models.UserNoPassword) string - VerifyTokenString(tokenString string) error - Verifier() func(http.Handler) http.Handler + DecodeTokenString(tokenString string) (int64, error) + Authenticator(http.Handler) http.Handler } type jwtToker struct { @@ -37,11 +39,32 @@ func (tok *jwtToker) EncodeUser(user *models.UserNoPassword) string { return tokenString } -func (tok *jwtToker) VerifyTokenString(tokenString string) error { - _, err := jwtauth.VerifyToken(tok.tokenAuth, tokenString) - return err -} +func (tok *jwtToker) DecodeTokenString(tokenString string) (int64, error) { + token, err := tok.tokenAuth.Decode(tokenString) + if err != nil { + return -1, fmt.Errorf("Error decoding token") + } -func (tok *jwtToker) Verifier() func(http.Handler) http.Handler { - return jwtauth.Verifier(tok.tokenAuth) + if token == nil { + return -1, fmt.Errorf("Token was nil") + } + + err = jwt.Validate( + token, + jwt.WithIssuer("gogmagog.deepak.science"), + jwt.WithAudience("gogmagog.deepak.science"), + ) + if err != nil { + return -1, err + } + + userIDRaw, ok := token.Get("user_id") + if !ok { + return -1, fmt.Errorf("error finding user_id claim") + } + userID, ok := userIDRaw.(float64) + if !ok { + return -1, fmt.Errorf("Could not parse [%s] as userID", userIDRaw) + } + return int64(userID), nil } diff --git a/tokens/tokens_test.go b/tokens/tokens_test.go index ba5c6e1..277e69e 100644 --- a/tokens/tokens_test.go +++ b/tokens/tokens_test.go @@ -17,6 +17,8 @@ func TestBasic(t *testing.T) { } token := toker.EncodeUser(usr) - assert.Nil(toker.VerifyTokenString(token)) - assert.NotNil(tokens.New("bad secret").VerifyTokenString(token)) + _, err := toker.DecodeTokenString(token) + assert.Nil(err) + _, err = tokens.New("bad secret").DecodeTokenString(token) + assert.NotNil(err) } From 96b22a225487256d2017948b54d1bbb61072f9f6 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 24 Jan 2021 19:26:47 -0600 Subject: [PATCH 20/32] Adds user by username model method --- models/user.go | 12 ++++++++++++ models/user_test.go | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/models/user.go b/models/user.go index bcadb10..1db7a0e 100644 --- a/models/user.go +++ b/models/user.go @@ -77,6 +77,18 @@ func (m *Model) CreateUser(req *CreateUserRequest) (int, error) { return m.InsertUser(desiredUser) } +// UserByUsername retrieves a single username from the store, verifying the passed in userID. +func (m *Model) UserByUsername(username string, userID int) (*UserNoPassword, error) { + user, err := m.SelectUserByUsername(username) + if user == nil { + return nil, wrapNotFound(err) + } + if int(user.UserID) != userID { + return nil, ¬FoundError{error: fmt.Errorf("provided userID does not match the retrieved user")} + } + return user.NoPassword(), wrapNotFound(err) +} + // hashPassword hashes a password func hashPassword(password string) ([]byte, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 11) diff --git a/models/user_test.go b/models/user_test.go index a161293..fa5a9bd 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -25,6 +25,20 @@ func TestModelUsers(t *testing.T) { str.InsertUser(user1) m := models.New(str) + userNoPass, err := m.UserByUsername("test1", 1) + assert.Nil(err) + assert.NotNil(userNoPass) + + userNoPass, err = m.UserByUsername("test1", 2) + assert.NotNil(err) + assert.True(models.IsNotFoundError(err)) + assert.Nil(userNoPass) + + userNoPass, err = m.UserByUsername("test2", 2) + assert.NotNil(err) + assert.True(models.IsNotFoundError(err)) + assert.Nil(userNoPass) + user, err := m.VerifyUserByUsernamePassword("test1", "password") assert.Nil(err) assert.NotNil(user) From 42d808165b0b7694408ffca5d14c0b96d0a3d131 Mon Sep 17 00:00:00 2001 From: Deepak Date: Sun, 24 Jan 2021 19:45:56 -0600 Subject: [PATCH 21/32] Adds current user route --- routes/currentUser.go | 44 +++++++++++++++++++++++++++++++++++++++++++ routes/plans_test.go | 2 +- routes/routes.go | 1 + tokens/middleware.go | 24 +++++++++++++++++------ tokens/tokens.go | 31 ++++++++++++++++++++++-------- 5 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 routes/currentUser.go diff --git a/routes/currentUser.go b/routes/currentUser.go new file mode 100644 index 0000000..dc4cdad --- /dev/null +++ b/routes/currentUser.go @@ -0,0 +1,44 @@ +package routes + +import ( + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/go-chi/chi" + "log" + "net/http" +) + +// NewCurrentUserRouter returns a new router for getting the current user. +func NewCurrentUserRouter(m *models.Model) http.Handler { + router := chi.NewRouter() + router.Get("/", getMeFunc(m)) + return router +} + +func getMeFunc(m *models.Model) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + userID, err := tokens.GetUserID(r.Context()) + if err != nil { + log.Print(err) + unauthorizedHandler(w, r) + return + } + username, err := tokens.GetUsername(r.Context()) + if err != nil { + log.Print(err) + unauthorizedHandler(w, r) + return + } + + user, err := m.UserByUsername(username, userID) + if err != nil { + serverError(w, err) + return + } + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(user); err != nil { + serverError(w, err) + } + } +} diff --git a/routes/plans_test.go b/routes/plans_test.go index 16c2e13..04d0254 100644 --- a/routes/plans_test.go +++ b/routes/plans_test.go @@ -12,7 +12,7 @@ import ( "time" ) -var sampleContext = tokens.GetContextForUserID(3) +var sampleContext = tokens.GetContextForUserValues(3, "testing") func TestEmptyPlans(t *testing.T) { // set up diff --git a/routes/routes.go b/routes/routes.go index 078a1fd..554e171 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -17,6 +17,7 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { r.Use(tok.Authenticator) r.Mount("/actions", NewActionRouter(m)) r.Mount("/plans", NewPlanRouter(m)) + r.Mount("/me", NewCurrentUserRouter(m)) }) router.Mount("/auth", newAuthRouter(m, tok)) router.Mount("/health", newHealthRouter(m)) diff --git a/tokens/middleware.go b/tokens/middleware.go index 04529a1..c8d9fa9 100644 --- a/tokens/middleware.go +++ b/tokens/middleware.go @@ -13,6 +13,7 @@ type contextKey struct { } var userIDCtxKey = &contextKey{"UserID"} +var usernameCtxKey = &contextKey{"Username"} func unauthorized(w http.ResponseWriter, r *http.Request) { code := http.StatusUnauthorized @@ -39,15 +40,16 @@ func (tok *jwtToker) Authenticator(next http.Handler) http.Handler { return } - userID, err := tok.DecodeTokenString(tokenString) + userToken, err := tok.DecodeTokenString(tokenString) if err != nil { log.Printf("Error while verifying token: %s", err) unauthorized(w, r) return } - log.Printf("Got user with ID: [%d]", userID) - ctx := context.WithValue(r.Context(), userIDCtxKey, userID) + log.Printf("Got user with ID: [%d]", userToken.ID) + ctx := context.WithValue(r.Context(), userIDCtxKey, userToken.ID) + ctx = context.WithValue(ctx, usernameCtxKey, userToken.Username) // Authenticated next.ServeHTTP(w, r.WithContext(ctx)) }) @@ -65,7 +67,17 @@ func GetUserID(ctx context.Context) (int, error) { return int(userID), nil } -// GetContextForUserID is a test helper method that creates a context with user ID set. -func GetContextForUserID(userID int) context.Context { - return context.WithValue(context.Background(), userIDCtxKey, int64(userID)) +// GetUsername does something similar to GetUserID. +func GetUsername(ctx context.Context) (string, error) { + username, ok := ctx.Value(usernameCtxKey).(string) + if !ok { + return "", fmt.Errorf("Could not parse username [%s] from context", ctx.Value(usernameCtxKey)) + } + return username, nil +} + +// GetContextForUserValues is a test helper method that creates a context with user ID set. +func GetContextForUserValues(userID int, username string) context.Context { + ctx := context.WithValue(context.Background(), userIDCtxKey, int64(userID)) + return context.WithValue(ctx, usernameCtxKey, username) } diff --git a/tokens/tokens.go b/tokens/tokens.go index a0d3e45..a66ebd0 100644 --- a/tokens/tokens.go +++ b/tokens/tokens.go @@ -12,7 +12,7 @@ import ( // Toker represents a tokenizer, capable of encoding and verifying tokens. type Toker interface { EncodeUser(user *models.UserNoPassword) string - DecodeTokenString(tokenString string) (int64, error) + DecodeTokenString(tokenString string) (*UserToken, error) Authenticator(http.Handler) http.Handler } @@ -39,14 +39,20 @@ func (tok *jwtToker) EncodeUser(user *models.UserNoPassword) string { return tokenString } -func (tok *jwtToker) DecodeTokenString(tokenString string) (int64, error) { +// UserToken represents a decoded jwt token. +type UserToken struct { + ID int64 + Username string +} + +func (tok *jwtToker) DecodeTokenString(tokenString string) (*UserToken, error) { token, err := tok.tokenAuth.Decode(tokenString) if err != nil { - return -1, fmt.Errorf("Error decoding token") + return nil, fmt.Errorf("Error decoding token") } if token == nil { - return -1, fmt.Errorf("Token was nil") + return nil, fmt.Errorf("Token was nil") } err = jwt.Validate( @@ -55,16 +61,25 @@ func (tok *jwtToker) DecodeTokenString(tokenString string) (int64, error) { jwt.WithAudience("gogmagog.deepak.science"), ) if err != nil { - return -1, err + return nil, err } userIDRaw, ok := token.Get("user_id") if !ok { - return -1, fmt.Errorf("error finding user_id claim") + return nil, fmt.Errorf("error finding user_id claim") } userID, ok := userIDRaw.(float64) if !ok { - return -1, fmt.Errorf("Could not parse [%s] as userID", userIDRaw) + return nil, fmt.Errorf("Could not parse [%s] as userID", userIDRaw) } - return int64(userID), nil + usernameRaw, ok := token.Get("username") + if !ok { + return nil, fmt.Errorf("error finding username claim") + } + username, ok := usernameRaw.(string) + if !ok { + return nil, fmt.Errorf("Could not parse [%s] as username", usernameRaw) + } + + return &UserToken{ID: int64(userID), Username: username}, nil } From f84c9b6ea2e05622b9dc48bc9ffb354e5762ac35 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 12:30:38 -0600 Subject: [PATCH 22/32] Adds error store test for missing store --- store/errorStore_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/store/errorStore_test.go b/store/errorStore_test.go index 6102421..31c4322 100644 --- a/store/errorStore_test.go +++ b/store/errorStore_test.go @@ -1,6 +1,7 @@ package store_test import ( + "fmt" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/store" "github.com/stretchr/testify/assert" @@ -11,11 +12,14 @@ func TestErrorActionMethods(t *testing.T) { assert := assert.New(t) str := store.GetErrorStore("error message sample", true) str2 := store.GetErrorStore("error message sample", false) + str3 := store.GetErrorStoreForError(fmt.Errorf("test error"), false) _, err := str.InsertAction(&models.Action{}) assert.NotNil(err) _, err = str2.InsertAction(&models.Action{}) assert.Nil(err) + _, err = str3.InsertAction(&models.Action{}) + assert.Nil(err) _, err = str.SelectActionByID(8) assert.NotNil(err) From cfe5d89b221f336a93d03a99aec7d0b15fbd4ac5 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 12:51:19 -0600 Subject: [PATCH 23/32] Adds test for token encode/decode --- tokens/tokens.go | 7 +- tokens/tokens_test.go | 147 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 6 deletions(-) diff --git a/tokens/tokens.go b/tokens/tokens.go index a66ebd0..72ed0af 100644 --- a/tokens/tokens.go +++ b/tokens/tokens.go @@ -51,9 +51,10 @@ func (tok *jwtToker) DecodeTokenString(tokenString string) (*UserToken, error) { return nil, fmt.Errorf("Error decoding token") } - if token == nil { - return nil, fmt.Errorf("Token was nil") - } + // Should never happen, remove soon. + // if token == nil { + // return nil, fmt.Errorf("Token was nil") + // } err = jwt.Validate( token, diff --git a/tokens/tokens_test.go b/tokens/tokens_test.go index 277e69e..76265c1 100644 --- a/tokens/tokens_test.go +++ b/tokens/tokens_test.go @@ -3,22 +3,163 @@ package tokens_test import ( "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/go-chi/jwtauth" "github.com/stretchr/testify/assert" "testing" + "time" ) func TestBasic(t *testing.T) { assert := assert.New(t) toker := tokens.New("secret") + idToUse := int64(3) + usernameToUse := "test" usr := &models.UserNoPassword{ - UserID: 3, - Username: "test", + UserID: idToUse, + Username: usernameToUse, DisplayName: "Ted Est III", } token := toker.EncodeUser(usr) - _, err := toker.DecodeTokenString(token) + userToken, err := toker.DecodeTokenString(token) assert.Nil(err) + assert.Equal(usernameToUse, userToken.Username) + assert.Equal(idToUse, userToken.ID) _, err = tokens.New("bad secret").DecodeTokenString(token) assert.NotNil(err) } + +func getTokenString(claims map[string]interface{}) string { + auth := jwtauth.New("HS256", []byte("secret"), nil) + + jwtauth.SetIssuedNow(claims) + jwtauth.SetExpiryIn(claims, 2*time.Hour) + _, tokenString, _ := auth.Encode(claims) + + return tokenString +} + +func TestDecodeBadIssuer(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + idToUse := 3 + username := "test" + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "user_id": int64(idToUse), + "username": username, + "display_name": "display_name", + "iss": gog, + "aud": "bad", + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} + +func TestDecodeBadAudience(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + idToUse := 3 + username := "test" + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "user_id": int64(idToUse), + "username": username, + "display_name": "display_name", + "iss": "bad", + "aud": gog, + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} + +func TestDecodeMissingUserID(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + username := "test" + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "username": username, + "display_name": "display_name", + "iss": gog, + "aud": gog, + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} + +func TestDecodeBadUserID(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + username := "test" + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "username": username, + "user_id": "id", + "display_name": "display_name", + "iss": gog, + "aud": gog, + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} + +func TestDecodeMissingUsername(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + idToUse := 3 + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "user_id": int64(idToUse), + "display_name": "display_name", + "iss": gog, + "aud": gog, + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} + +func TestDecodeBadUsername(t *testing.T) { + assert := assert.New(t) + toker := tokens.New("secret") + + gog := "gogmagog.deepak.science" + + claims := map[string]interface{}{ + "username": 5, + "user_id": 3, + "display_name": "display_name", + "iss": gog, + "aud": gog, + } + + token := getTokenString(claims) + _, err := toker.DecodeTokenString(token) + assert.NotNil(err) + +} From c28939d2b8aeba2b74009ce8c64a0295d3016a9f Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 13:33:52 -0600 Subject: [PATCH 24/32] Adds middleware testing methods --- tokens/middleware_http_test.go | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tokens/middleware_http_test.go diff --git a/tokens/middleware_http_test.go b/tokens/middleware_http_test.go new file mode 100644 index 0000000..4ec0f17 --- /dev/null +++ b/tokens/middleware_http_test.go @@ -0,0 +1,78 @@ +package tokens_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +var middlewareURL string = "/" + +func mwRequestAuth(header string) *http.Request { + req, _ := http.NewRequest("GET", middlewareURL, nil) + req.Header.Add(authKey, header) + + return req +} + +func verifyingHandler(t *testing.T, username string, userID int) http.Handler { + assert := assert.New(t) + toker := tokens.New("secret") + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + receivedID, _ := tokens.GetUserID(ctx) + receivedUsername, _ := tokens.GetUsername(ctx) + assert.EqualValues(userID, receivedID) + assert.Equal(username, receivedUsername) + }) + return toker.Authenticator(dummyHandler) +} + +func TestMiddlewareNoToken(t *testing.T) { + assert := assert.New(t) + + req := httptest.NewRequest(http.MethodGet, middlewareURL, nil) + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandler(t, "", 0) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) +} + +func TestMiddlewareBadToken(t *testing.T) { + assert := assert.New(t) + + req := mwRequestAuth("Bearer bad") + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandler(t, "", 0) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) +} + +func TestMiddlewareGoodToken(t *testing.T) { + assert := assert.New(t) + + idToUse := 3 + username := "username" + displayName := "display name" + user := &models.UserNoPassword{UserID: int64(idToUse), Username: username, DisplayName: displayName} + + toker := tokens.New("secret") + validToken := toker.EncodeUser(user) + req := mwRequestAuth("Bearer " + validToken) + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandler(t, username, idToUse) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusOK, status) +} From 77e6e6bc048868bf5fea87adc197531f1a48d678 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 14:25:12 -0600 Subject: [PATCH 25/32] Adds test for context methods --- tokens/middleware_context_test.go | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tokens/middleware_context_test.go diff --git a/tokens/middleware_context_test.go b/tokens/middleware_context_test.go new file mode 100644 index 0000000..f160280 --- /dev/null +++ b/tokens/middleware_context_test.go @@ -0,0 +1,39 @@ +package tokens_test + +import ( + "context" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGoodContext(t *testing.T) { + assert := assert.New(t) + + idToUse := 3 + username := "username" + + ctx := tokens.GetContextForUserValues(idToUse, username) + + receivedID, err := tokens.GetUserID(ctx) + assert.Nil(err) + assert.EqualValues(idToUse, receivedID) + + receivedUsername, err := tokens.GetUsername(ctx) + assert.Nil(err) + assert.Equal(username, receivedUsername) + +} + +func TestBadContext(t *testing.T) { + assert := assert.New(t) + + ctx := context.Background() + + _, err := tokens.GetUserID(ctx) + assert.NotNil(err) + + _, err = tokens.GetUsername(ctx) + assert.NotNil(err) + +} From 4d093ed99af1c3a1ca80df9dd932927ca51657ad Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 14:34:17 -0600 Subject: [PATCH 26/32] Adds plan route unauthorised tests --- routes/plans.go | 4 -- routes/plans_unauthorized_test.go | 95 +++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 routes/plans_unauthorized_test.go diff --git a/routes/plans.go b/routes/plans.go index 8d9c373..c1a26ae 100644 --- a/routes/plans.go +++ b/routes/plans.go @@ -6,7 +6,6 @@ import ( "gitea.deepak.science/deepak/gogmagog/tokens" "github.com/go-chi/chi" "io" - "log" "net/http" "strconv" ) @@ -24,7 +23,6 @@ func getAllPlansFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, err := tokens.GetUserID(r.Context()) if err != nil { - log.Print(err) unauthorizedHandler(w, r) return } @@ -45,7 +43,6 @@ func getPlanByIDFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, err := tokens.GetUserID(r.Context()) if err != nil { - log.Print(err) unauthorizedHandler(w, r) return } @@ -82,7 +79,6 @@ func postPlanFunc(m *models.Model) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, err := tokens.GetUserID(r.Context()) if err != nil { - log.Print(err) unauthorizedHandler(w, r) return } diff --git a/routes/plans_unauthorized_test.go b/routes/plans_unauthorized_test.go new file mode 100644 index 0000000..763daa1 --- /dev/null +++ b/routes/plans_unauthorized_test.go @@ -0,0 +1,95 @@ +package routes_test + +import ( + "bytes" + "context" + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/routes" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestEmptyPlanEmptyContext(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + router := routes.NewPlanRouter(m) + req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestOnePlanEmptyContext(t *testing.T) { + // set up + assert := assert.New(t) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} + m := getEmptyModel() + m.AddPlan(p, 3) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestOnePlanByIDEmptyContext(t *testing.T) { + // set up + assert := assert.New(t) + planDate, _ := time.Parse("2006-01-02", "2021-01-01") + p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3} + m := getEmptyModel() + m.AddPlan(p, 3) + router := routes.NewPlanRouter(m) + req, _ := http.NewRequestWithContext(context.Background(), "GET", "/1", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestPureJSONEmptyContext(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + router := routes.NewPlanRouter(m) + data := []byte(`{ + "plan_date": "2021-01-01T00:00:00Z", + "plan_id": 1, + "user_id": 3 + }`) + req, _ := http.NewRequestWithContext(context.Background(), "POST", "/", bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) +} From 93a5e9c1bad78078ed51e02f595df60fd86e1645 Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 14:50:54 -0600 Subject: [PATCH 27/32] Adds middleware set userid on context method. --- tokens/middleware.go | 5 +++++ tokens/middleware_context_test.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/tokens/middleware.go b/tokens/middleware.go index c8d9fa9..46b971e 100644 --- a/tokens/middleware.go +++ b/tokens/middleware.go @@ -67,6 +67,11 @@ func GetUserID(ctx context.Context) (int, error) { return int(userID), nil } +// SetUserID sets the username field on a context, necessary because the key is an unexported custom type. +func SetUserID(ctx context.Context, id int) context.Context { + return context.WithValue(ctx, userIDCtxKey, int64(id)) +} + // GetUsername does something similar to GetUserID. func GetUsername(ctx context.Context) (string, error) { username, ok := ctx.Value(usernameCtxKey).(string) diff --git a/tokens/middleware_context_test.go b/tokens/middleware_context_test.go index f160280..06e2847 100644 --- a/tokens/middleware_context_test.go +++ b/tokens/middleware_context_test.go @@ -37,3 +37,13 @@ func TestBadContext(t *testing.T) { assert.NotNil(err) } + +func TestSetContext(t *testing.T) { + assert := assert.New(t) + + idToUse := 3 + ctx := tokens.SetUserID(context.Background(), 3) + receivedID, err := tokens.GetUserID(ctx) + assert.Nil(err) + assert.EqualValues(idToUse, receivedID) +} From 28325c8d7be4aa42d369b95047c00ca41b3d6f2a Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 14:51:28 -0600 Subject: [PATCH 28/32] Adds current user tests. --- routes/currentUser.go | 4 +++ routes/currentUser_test.go | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 routes/currentUser_test.go diff --git a/routes/currentUser.go b/routes/currentUser.go index dc4cdad..e65ce13 100644 --- a/routes/currentUser.go +++ b/routes/currentUser.go @@ -33,6 +33,10 @@ func getMeFunc(m *models.Model) http.HandlerFunc { user, err := m.UserByUsername(username, userID) if err != nil { + if models.IsNotFoundError(err) { + notFoundHandler(w, r) + return + } serverError(w, err) return } diff --git a/routes/currentUser_test.go b/routes/currentUser_test.go new file mode 100644 index 0000000..31486df --- /dev/null +++ b/routes/currentUser_test.go @@ -0,0 +1,62 @@ +package routes_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestEmptyCurrentUser(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(3, "testing"), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusNotFound, status) + +} + +func TestSingleUser(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + + idToUse := 1 + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, username), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusOK, status) + expected := `{ + "user_id": 1, + "username": "testing_username", + "display_name": "testing_name" + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) + +} From 63a9e2ff5814540195a72e3f72c4eacfd6cec67d Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 14:58:06 -0600 Subject: [PATCH 29/32] Adds tests for context errors --- routes/currentUser_test.go | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/routes/currentUser_test.go b/routes/currentUser_test.go index 31486df..da62708 100644 --- a/routes/currentUser_test.go +++ b/routes/currentUser_test.go @@ -1,6 +1,7 @@ package routes_test import ( + "context" "gitea.deepak.science/deepak/gogmagog/models" "gitea.deepak.science/deepak/gogmagog/routes" "gitea.deepak.science/deepak/gogmagog/tokens" @@ -60,3 +61,73 @@ func TestSingleUser(t *testing.T) { assert.Equal("application/json", contentType) } + +func TestSingleUserEmptyContext(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestSingleUserContextNoUserID(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + + idToUse := 1 + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(tokens.SetUserID(context.Background(), idToUse), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestErrorUserContextNoUserID(t *testing.T) { + // set up + assert := assert.New(t) + m := getErrorModel("Here's an error.") + + idToUse := 1 + + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, "username"), "GET", "/", nil) + + rr := httptest.NewRecorder() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} From c9675b1573a3e5b13f34c1818e601c7464ec078a Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 15:00:07 -0600 Subject: [PATCH 30/32] Adds last currentUser route tests --- routes/currentUser_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/routes/currentUser_test.go b/routes/currentUser_test.go index da62708..4ac993e 100644 --- a/routes/currentUser_test.go +++ b/routes/currentUser_test.go @@ -131,3 +131,28 @@ func TestErrorUserContextNoUserID(t *testing.T) { assert.Equal(http.StatusInternalServerError, status) } + +func TestSingleUserErrorWriter(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + + idToUse := 1 + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + router := routes.NewCurrentUserRouter(m) + req, _ := http.NewRequestWithContext(tokens.GetContextForUserValues(idToUse, username), "GET", "/", nil) + + rr := NewBadWriter() + + // function under test + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} From 249fabbd7abd7be5860cb01c52ceb97605d3513e Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 15:18:56 -0600 Subject: [PATCH 31/32] Adds tests for register route --- routes/auth.go | 3 +- routes/auth_register_test.go | 139 +++++++++++++++++++++++++++++++++++ routes/routes.go | 2 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 routes/auth_register_test.go diff --git a/routes/auth.go b/routes/auth.go index f97d92a..f487b62 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -9,7 +9,8 @@ import ( "net/http" ) -func newAuthRouter(m *models.Model, tok tokens.Toker) http.Handler { +// NewAuthRouter returns a new auth router. +func NewAuthRouter(m *models.Model, tok tokens.Toker) http.Handler { router := chi.NewRouter() router.Post("/register", postUserFunc(m)) router.Post("/tokens", createTokenFunc(m, tok)) diff --git a/routes/auth_register_test.go b/routes/auth_register_test.go new file mode 100644 index 0000000..1208a22 --- /dev/null +++ b/routes/auth_register_test.go @@ -0,0 +1,139 @@ +package routes_test + +import ( + "bytes" + "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestRegisterAuth(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.New("secret") + data := []byte(`{ + "username": "test", + "password": "pass", + "display_name": "My Display Name" + }`) + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusCreated, status) + expected := `{ + "username": "test" + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) + +} + +func TestRegisterBadRequestAuth(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.New("secret") + data := []byte(`{ + "username": y Display Name" + }`) + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + +} + +func TestRegisterBadRequestTwoBodies(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.New("secret") + data := []byte(`{ + "username": "test", + "password": "pass", + "display_name": "My Display Name" + }, { + "username": "test", + "password": "pass", + "display_name": "My Display Name" + }`) + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + +} + +func TestRegisterErrorModel(t *testing.T) { + // set up + assert := assert.New(t) + m := getErrorModel("here's an error") + toker := tokens.New("secret") + data := []byte(`{ + "username": "test", + "password": "pass", + "display_name": "My Display Name" + }`) + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +func TestRegisterBadWriter(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.New("secret") + data := []byte(`{ + "username": "test", + "password": "pass", + "display_name": "My Display Name" + }`) + req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) + + rr := NewBadWriter() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} diff --git a/routes/routes.go b/routes/routes.go index 554e171..dbcd846 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -19,7 +19,7 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler { r.Mount("/plans", NewPlanRouter(m)) r.Mount("/me", NewCurrentUserRouter(m)) }) - router.Mount("/auth", newAuthRouter(m, tok)) + router.Mount("/auth", NewAuthRouter(m, tok)) router.Mount("/health", newHealthRouter(m)) router.Get("/ping", ping) return router From 6dd91f5a1b58d72e8f26b69f5365568869e7edff Mon Sep 17 00:00:00 2001 From: Deepak Date: Mon, 25 Jan 2021 16:23:42 -0600 Subject: [PATCH 32/32] Adds tests for auth route --- routes/auth_login_test.go | 202 ++++++++++++++++++ tokens/deterministicToker.go | 53 +++++ tokens/deterministic_toker_middleware_test.go | 80 +++++++ 3 files changed, 335 insertions(+) create mode 100644 routes/auth_login_test.go create mode 100644 tokens/deterministicToker.go create mode 100644 tokens/deterministic_toker_middleware_test.go diff --git a/routes/auth_login_test.go b/routes/auth_login_test.go new file mode 100644 index 0000000..db4925a --- /dev/null +++ b/routes/auth_login_test.go @@ -0,0 +1,202 @@ +package routes_test + +import ( + "bytes" + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/routes" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestLoginAuth(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_username", + "password": "pass" + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusOK, status) + expected := `{ + "token": "{\"ID\":1,\"Username\":\"testing_username\"}" + }` + assert.JSONEq(expected, rr.Body.String()) + contentType := rr.Header().Get("Content-Type") + assert.Equal("application/json", contentType) + +} + +func TestLoginBadCreds(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_use + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) + +} + +func TestLoginBadRequestTwoBodies(t *testing.T) { + // set up + assert := assert.New(t) + m := getEmptyModel() + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_username", + "password": "pass" + }{ + "username": "testing_username", + "password": "pass" + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusBadRequest, status) +} + +func TestLoginAuthWrongPass(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_username", + "password": "badpass" + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) + +} + +func TestLoginErrorModel(t *testing.T) { + // set up + assert := assert.New(t) + + m := getErrorModel("error") + + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_username", + "password": "badpass" + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := httptest.NewRecorder() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +func TestLoginBadWriter(t *testing.T) { + // set up + assert := assert.New(t) + + m := getEmptyModel() + username := "testing_username" + displayName := "testing_name" + password := "pass" + m.CreateUser(&models.CreateUserRequest{Username: username, DisplayName: displayName, Password: password}) + + toker := tokens.GetDeterministicToker() + data := []byte(`{ + "username": "testing_username", + "password": "pass" + }`) + req, _ := http.NewRequest("POST", "/tokens", bytes.NewBuffer(data)) + + rr := NewBadWriter() + + // function under test + router := routes.NewAuthRouter(m, toker) + router.ServeHTTP(rr, req) + + // check results + status := rr.Code + assert.Equal(http.StatusInternalServerError, status) + +} + +// +// func TestRegisterBadWriter(t *testing.T) { +// // set up +// assert := assert.New(t) +// m := getEmptyModel() +// toker := tokens.New("secret") +// data := []byte(`{ +// "username": "test", +// "password": "pass", +// "display_name": "My Display Name" +// }`) +// req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(data)) +// +// rr := NewBadWriter() +// +// // function under test +// router := routes.NewAuthRouter(m, toker) +// router.ServeHTTP(rr, req) +// +// // check results +// status := rr.Code +// assert.Equal(http.StatusInternalServerError, status) +// +// } diff --git a/tokens/deterministicToker.go b/tokens/deterministicToker.go new file mode 100644 index 0000000..96cd9ed --- /dev/null +++ b/tokens/deterministicToker.go @@ -0,0 +1,53 @@ +package tokens + +import ( + "context" + "encoding/json" + "gitea.deepak.science/deepak/gogmagog/models" + "log" + "net/http" +) + +type deterministicToker struct{} + +// GetDeterministicToker returns a zero security toker for testing purposes. +// Do not use in production. +func GetDeterministicToker() Toker { + return &deterministicToker{} +} + +func (d *deterministicToker) EncodeUser(user *models.UserNoPassword) string { + tok := &UserToken{ID: user.UserID, Username: user.Username} + ret, _ := json.Marshal(tok) + return string(ret) +} + +func (d *deterministicToker) DecodeTokenString(tokenString string) (*UserToken, error) { + var tok UserToken + err := json.Unmarshal([]byte(tokenString), &tok) + return &tok, err +} + +func (d *deterministicToker) Authenticator(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tokenString := TokenFromHeader(r) + if tokenString == "" { + log.Print("No valid token found") + unauthorized(w, r) + return + } + + userToken, err := d.DecodeTokenString(tokenString) + if err != nil { + log.Printf("Error while verifying token: %s", err) + unauthorized(w, r) + return + } + + log.Printf("Got user with ID: [%d]", userToken.ID) + ctx := context.WithValue(r.Context(), userIDCtxKey, userToken.ID) + ctx = context.WithValue(ctx, usernameCtxKey, userToken.Username) + // Authenticated + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/tokens/deterministic_toker_middleware_test.go b/tokens/deterministic_toker_middleware_test.go new file mode 100644 index 0000000..e3c6f03 --- /dev/null +++ b/tokens/deterministic_toker_middleware_test.go @@ -0,0 +1,80 @@ +package tokens_test + +import ( + "gitea.deepak.science/deepak/gogmagog/models" + "gitea.deepak.science/deepak/gogmagog/tokens" + "github.com/stretchr/testify/assert" + "log" + "net/http" + "net/http/httptest" + "testing" +) + +var dtMiddlewareURL string = "/" + +func dtRequestAuth(header string) *http.Request { + req, _ := http.NewRequest("GET", dtMiddlewareURL, nil) + req.Header.Add(authKey, header) + + return req +} + +func verifyingHandlerdt(t *testing.T, username string, userID int) http.Handler { + assert := assert.New(t) + toker := tokens.GetDeterministicToker() + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + receivedID, _ := tokens.GetUserID(ctx) + receivedUsername, _ := tokens.GetUsername(ctx) + assert.EqualValues(userID, receivedID) + assert.Equal(username, receivedUsername) + }) + return toker.Authenticator(dummyHandler) +} + +func TestMiddlewareNoTokendt(t *testing.T) { + assert := assert.New(t) + + req := httptest.NewRequest(http.MethodGet, dtMiddlewareURL, nil) + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandlerdt(t, "", 0) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) +} + +func TestMiddlewareBadTokendt(t *testing.T) { + assert := assert.New(t) + + req := mwRequestAuth("Bearer bad") + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandlerdt(t, "", 0) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusUnauthorized, status) +} + +func TestMiddlewareGoodTokendt(t *testing.T) { + assert := assert.New(t) + + idToUse := 3 + username := "username" + displayName := "display name" + user := &models.UserNoPassword{UserID: int64(idToUse), Username: username, DisplayName: displayName} + + toker := tokens.GetDeterministicToker() + validToken := toker.EncodeUser(user) + log.Print(validToken) + req := mwRequestAuth("Bearer " + validToken) + rr := httptest.NewRecorder() + + middlewareHandler := verifyingHandlerdt(t, username, idToUse) + middlewareHandler.ServeHTTP(rr, req) + + status := rr.Code + assert.Equal(http.StatusOK, status) +}