From 07a25f8a31319ed4145fe69f3fd2edf0120fe7da Mon Sep 17 00:00:00 2001 From: G Date: Sat, 15 Sep 2018 18:54:13 -0700 Subject: [PATCH 1/3] refactor into Env/datastore format --- databaseutil/databaseutil.go | 191 ---------------------------- handlers/handlers.go | 28 ++-- main.go | 16 ++- models/category.go | 22 ++++ models/datastore.go | 55 ++++++++ models/note.go | 44 +++++++ models/notemap.go | 19 +++ models/user.go | 114 ++++++++++++++++- services/noteservice/noteservice.go | 48 ------- services/userservice/userservice.go | 92 -------------- 10 files changed, 285 insertions(+), 344 deletions(-) delete mode 100644 databaseutil/databaseutil.go create mode 100644 models/category.go create mode 100644 models/datastore.go create mode 100644 models/notemap.go delete mode 100644 services/noteservice/noteservice.go delete mode 100644 services/userservice/userservice.go diff --git a/databaseutil/databaseutil.go b/databaseutil/databaseutil.go deleted file mode 100644 index 02e24cf..0000000 --- a/databaseutil/databaseutil.go +++ /dev/null @@ -1,191 +0,0 @@ -/* -Package databaseutil abstracts away details about sql and postgres. - -These functions only accept and return primitive types. -*/ -package databaseutil - -import ( - "database/sql" - "errors" - "time" - - "github.com/lib/pq" -) - -var db *sql.DB - -// UniqueConstraintError is returned when a uniqueness constraint is violated during an insert. -var UniqueConstraintError = errors.New("postgres: unique constraint violation") - -// QueryResultContainedMultipleRowsError is returned when a query unexpectedly returns more than one row. -var QueryResultContainedMultipleRowsError = errors.New("query result unexpectedly contained multiple rows") - -// QueryResultContainedNoRowsError is returned when a query unexpectedly returns no rows. -var QueryResultContainedNoRowsError = errors.New("query result unexpectedly contained no rows") - -// ConnectToDatabase also pings the database to ensure a working connection. -func ConnectToDatabase(databaseUrl string) error { - { - tempDb, err := sql.Open("postgres", databaseUrl) - if err != nil { - return err - } - - db = tempDb - } - - if err := db.Ping(); err != nil { - return err - } - - return nil -} - -func InsertIntoUserTable( - displayName string, - emailAddress string, - password []byte, - creationTime time.Time, -) error { - sqlQuery := ` - INSERT INTO app_user (display_name, email_address, password, creation_time) - VALUES ($1, $2, $3, $4)` - - rows, err := db.Query(sqlQuery, displayName, emailAddress, password, creationTime) - if err != nil { - return convertPostgresError(err) - } - defer rows.Close() - - if err := rows.Err(); err != nil { - return convertPostgresError(err) - } - - return nil -} - -func GetPasswordForUserWithEmailAddress(emailAddress string) ([]byte, error) { - sqlQuery := ` - SELECT password FROM app_user - WHERE email_address = $1` - - rows, err := db.Query(sqlQuery, emailAddress) - if err != nil { - return nil, convertPostgresError(err) - } - defer rows.Close() - - var password []byte - for rows.Next() { - if password != nil { - return nil, QueryResultContainedMultipleRowsError - } - - if err := rows.Scan(&password); err != nil { - return nil, err - } - } - - if password == nil { - return nil, QueryResultContainedNoRowsError - } - - return password, nil -} - -func InsertNewNote(authorId int64, content string, creationTime time.Time) (int64, error) { - sqlQuery := ` - INSERT INTO note (author_id, content, creation_time) - VALUES ($1, $2, $3) - RETURNING id` - - rows, err := db.Query(sqlQuery, authorId, content, creationTime) - if err != nil { - return 0, convertPostgresError(err) - } - defer rows.Close() - - var noteId int64 = 0 - for rows.Next() { - - if noteId != 0 { - return 0, QueryResultContainedMultipleRowsError - } - - if err := rows.Scan(¬eId); err != nil { - return 0, convertPostgresError(err) - } - } - - if noteId == 0 { - return 0, QueryResultContainedNoRowsError - } - - if err := rows.Err(); err != nil { - return 0, convertPostgresError(err) - } - - return noteId, nil -} - -func InsertNoteCategoryRelationship(noteId int64, category string) error { - sqlQuery := ` - INSERT INTO note_to_category_relationship (note_id, category) - VALUES ($1, $2)` - - rows, err := db.Query(sqlQuery, noteId, category) - if err != nil { - return convertPostgresError(err) - } - defer rows.Close() - - if err := rows.Err(); err != nil { - return convertPostgresError(err) - } - - return nil -} - -func GetIdForUserWithEmailAddress(emailAddress string) (int64, error) { - sqlQuery := ` - SELECT id FROM app_user - WHERE email_address = $1` - - rows, err := db.Query(sqlQuery, emailAddress) - if err != nil { - return 0, convertPostgresError(err) - } - defer rows.Close() - - var userId int64 - for rows.Next() { - if userId != 0 { - return 0, QueryResultContainedMultipleRowsError - } - - if err := rows.Scan(&userId); err != nil { - return 0, err - } - } - - if userId == 0 { - return 0, QueryResultContainedNoRowsError - } - - return userId, nil -} - -// PRIVATE - -func convertPostgresError(err error) error { - const uniqueConstraintErrorCode = "23505" - - if postgresErr, ok := err.(*pq.Error); ok { - if postgresErr.Code == uniqueConstraintErrorCode { - return UniqueConstraintError - } - } - - return err -} diff --git a/handlers/handlers.go b/handlers/handlers.go index dd1a769..3b4cfd1 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -11,8 +11,6 @@ import ( "github.com/atmiguel/cerealnotes/models" "github.com/atmiguel/cerealnotes/paths" - "github.com/atmiguel/cerealnotes/services/noteservice" - "github.com/atmiguel/cerealnotes/services/userservice" "github.com/dgrijalva/jwt-go" ) @@ -28,12 +26,22 @@ type JwtTokenClaim struct { jwt.StandardClaims } +type Environment struct { + Db models.Datastore +} + var tokenSigningKey []byte func SetTokenSigningKey(key []byte) { tokenSigningKey = key } +var environment *Environment + +func SetEnvironment(env *Environment) { + environment = env +} + // UNAUTHENTICATED HANDLERS // HandleLoginOrSignupPageRequest responds to unauthenticated GET requests with the login or signup page. @@ -86,12 +94,12 @@ func HandleUserApiRequest( } var statusCode int - if err := userservice.StoreNewUser( + if err := environment.Db.StoreNewUser( signupForm.DisplayName, models.NewEmailAddress(signupForm.EmailAddress), signupForm.Password, ); err != nil { - if err == userservice.EmailAddressAlreadyInUseError { + if err == models.EmailAddressAlreadyInUseError { statusCode = http.StatusConflict } else { http.Error(responseWriter, err.Error(), http.StatusInternalServerError) @@ -153,12 +161,12 @@ func HandleSessionApiRequest( return } - if err := userservice.AuthenticateUserCredentials( + if err := environment.Db.AuthenticateUserCredentials( models.NewEmailAddress(loginForm.EmailAddress), loginForm.Password, ); err != nil { statusCode := http.StatusInternalServerError - if err == userservice.CredentialsNotAuthorizedError { + if err == models.CredentialsNotAuthorizedError { statusCode = http.StatusUnauthorized } http.Error(responseWriter, err.Error(), statusCode) @@ -167,7 +175,7 @@ func HandleSessionApiRequest( // Set our cookie to have a valid JWT Token as the value { - userId, err := userservice.GetIdForUserWithEmailAddress(models.NewEmailAddress(loginForm.EmailAddress)) + userId, err := environment.Db.GetIdForUserWithEmailAddress(models.NewEmailAddress(loginForm.EmailAddress)) if err != nil { http.Error(responseWriter, err.Error(), http.StatusInternalServerError) return @@ -224,7 +232,7 @@ func HandleNoteApiRequest( switch request.Method { case http.MethodGet: - var notesById noteservice.NoteMap = make(map[models.NoteId]*models.Note, 2) + var notesById models.NoteMap = make(map[models.NoteId]*models.Note, 2) notesById[models.NoteId(1)] = &models.Note{ AuthorId: 1, @@ -272,7 +280,7 @@ func HandleNoteApiRequest( CreationTime: time.Now().UTC(), } - noteId, err := noteservice.StoreNewNote(note) + noteId, err := environment.Db.StoreNewNote(note) if err != nil { http.Error(responseWriter, err.Error(), http.StatusInternalServerError) return @@ -313,7 +321,7 @@ func HandleCategoryApiRequest( return } - if err := noteservice.StoreNewNoteCategoryRelationship(models.NoteId(noteForm.NoteId), category); err != nil { + if err := environment.Db.StoreNewNoteCategoryRelationship(models.NoteId(noteForm.NoteId), category); err != nil { http.Error(responseWriter, err.Error(), http.StatusInternalServerError) return } diff --git a/main.go b/main.go index 60fb3ab..32c31dc 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,8 @@ import ( "net/http" "os" - "github.com/atmiguel/cerealnotes/databaseutil" "github.com/atmiguel/cerealnotes/handlers" + "github.com/atmiguel/cerealnotes/models" "github.com/atmiguel/cerealnotes/routers" ) @@ -53,15 +53,23 @@ func determineTokenSigningKey() ([]byte, error) { func main() { // Set up db + + env := &handlers.Environment{} + { databaseUrl, err := determineDatabaseUrl() if err != nil { log.Fatal(err) } - if err := databaseutil.ConnectToDatabase(databaseUrl); err != nil { + db, err := models.ConnectToDatabase(databaseUrl) + + if err != nil { log.Fatal(err) } + + env.Db = db + } // Set up token signing key @@ -74,6 +82,10 @@ func main() { handlers.SetTokenSigningKey(tokenSigningKey) } + { + handlers.SetEnvironment(env) + } + // Start server { port, err := determineListenPort() diff --git a/models/category.go b/models/category.go new file mode 100644 index 0000000..c8b2571 --- /dev/null +++ b/models/category.go @@ -0,0 +1,22 @@ +package models + +func (db *DB) StoreNewNoteCategoryRelationship( + noteId NoteId, + category Category, +) error { + sqlQuery := ` + INSERT INTO note_to_category_relationship (note_id, category) + VALUES ($1, $2)` + + rows, err := db.Query(sqlQuery, int64(noteId), category.String()) + if err != nil { + return convertPostgresError(err) + } + defer rows.Close() + + if err := rows.Err(); err != nil { + return convertPostgresError(err) + } + + return nil +} diff --git a/models/datastore.go b/models/datastore.go new file mode 100644 index 0000000..88d7a4b --- /dev/null +++ b/models/datastore.go @@ -0,0 +1,55 @@ +package models + +import ( + "database/sql" + "errors" + + "github.com/lib/pq" +) + +// UniqueConstraintError is returned when a uniqueness constraint is violated during an insert. +var UniqueConstraintError = errors.New("postgres: unique constraint violation") + +// QueryResultContainedMultipleRowsError is returned when a query unexpectedly returns more than one row. +var QueryResultContainedMultipleRowsError = errors.New("query result unexpectedly contained multiple rows") + +// QueryResultContainedNoRowsError is returned when a query unexpectedly returns no rows. +var QueryResultContainedNoRowsError = errors.New("query result unexpectedly contained no rows") + +// ConnectToDatabase also pings the database to ensure a working connection. +func ConnectToDatabase(databaseUrl string) (*DB, error) { + tempDb, err := sql.Open("postgres", databaseUrl) + if err != nil { + return nil, err + } + + if err := tempDb.Ping(); err != nil { + return nil, err + } + + return &DB{tempDb}, nil +} + +type Datastore interface { + StoreNewNote(*Note) (NoteId, error) + StoreNewNoteCategoryRelationship(NoteId, Category) error + StoreNewUser(string, *EmailAddress, string) error + AuthenticateUserCredentials(*EmailAddress, string) error + GetIdForUserWithEmailAddress(*EmailAddress) (UserId, error) +} + +type DB struct { + *sql.DB +} + +func convertPostgresError(err error) error { + const uniqueConstraintErrorCode = "23505" + + if postgresErr, ok := err.(*pq.Error); ok { + if postgresErr.Code == uniqueConstraintErrorCode { + return UniqueConstraintError + } + } + + return err +} diff --git a/models/note.go b/models/note.go index 8685a23..abed956 100644 --- a/models/note.go +++ b/models/note.go @@ -48,3 +48,47 @@ type Note struct { Content string `json:"content"` CreationTime time.Time `json:"creationTime"` } + +// DB methods + +func (db *DB) StoreNewNote( + note *Note, +) (NoteId, error) { + + authorId := int64(note.AuthorId) + content := note.Content + creationTime := note.CreationTime + + sqlQuery := ` + INSERT INTO note (author_id, content, creation_time) + VALUES ($1, $2, $3) + RETURNING id` + + rows, err := db.Query(sqlQuery, authorId, content, creationTime) + if err != nil { + return 0, convertPostgresError(err) + } + defer rows.Close() + + var noteId int64 = 0 + for rows.Next() { + + if noteId != 0 { + return 0, QueryResultContainedMultipleRowsError + } + + if err := rows.Scan(¬eId); err != nil { + return 0, convertPostgresError(err) + } + } + + if noteId == 0 { + return 0, QueryResultContainedNoRowsError + } + + if err := rows.Err(); err != nil { + return 0, convertPostgresError(err) + } + + return NoteId(noteId), nil +} diff --git a/models/notemap.go b/models/notemap.go new file mode 100644 index 0000000..1913baa --- /dev/null +++ b/models/notemap.go @@ -0,0 +1,19 @@ +package models + +import ( + "encoding/json" + "fmt" +) + +type NoteMap map[NoteId]*Note + +func (noteMap NoteMap) ToJson() ([]byte, error) { + // json doesn't support int indexed maps + notesByIdString := make(map[string]Note, len(noteMap)) + + for id, note := range noteMap { + notesByIdString[fmt.Sprint(id)] = *note + } + + return json.Marshal(notesByIdString) +} diff --git a/models/user.go b/models/user.go index 0ecac0c..c0b2a31 100644 --- a/models/user.go +++ b/models/user.go @@ -1,6 +1,12 @@ package models -import "strings" +import ( + "errors" + "strings" + "time" + + "golang.org/x/crypto/bcrypt" +) type UserId int64 @@ -20,3 +26,109 @@ func NewEmailAddress(emailAddressAsString string) *EmailAddress { func (emailAddress *EmailAddress) String() string { return emailAddress.emailAddressAsString } + +var EmailAddressAlreadyInUseError = errors.New("Email address already in use") + +var CredentialsNotAuthorizedError = errors.New("The provided credentials were not found") + +// + +func (db *DB) StoreNewUser( + displayName string, + emailAddress *EmailAddress, + password string, +) error { + hashedPassword, err := bcrypt.GenerateFromPassword( + []byte(password), + bcrypt.DefaultCost) + if err != nil { + return err + } + + creationTime := time.Now().UTC() + + sqlQuery := ` + INSERT INTO app_user (display_name, email_address, password, creation_time) + VALUES ($1, $2, $3, $4)` + + rows, err := db.Query(sqlQuery, displayName, emailAddress.String(), hashedPassword, creationTime) + if err != nil { + return convertPostgresError(err) + } + defer rows.Close() + + if err := rows.Err(); err != nil { + return convertPostgresError(err) + } + + return nil +} + +func (db *DB) AuthenticateUserCredentials(emailAddress *EmailAddress, password string) error { + sqlQuery := ` + SELECT password FROM app_user + WHERE email_address = $1` + + rows, err := db.Query(sqlQuery, emailAddress) + if err != nil { + return convertPostgresError(err) + } + defer rows.Close() + + var storedHashedPassword []byte + for rows.Next() { + if storedHashedPassword != nil { + return QueryResultContainedMultipleRowsError + } + + if err := rows.Scan(&storedHashedPassword); err != nil { + return err + } + } + + if storedHashedPassword == nil { + return QueryResultContainedNoRowsError + } + + if err := bcrypt.CompareHashAndPassword( + storedHashedPassword, + []byte(password), + ); err != nil { + if err == bcrypt.ErrMismatchedHashAndPassword { + return CredentialsNotAuthorizedError + } + + return err + } + + return nil +} + +func (db *DB) GetIdForUserWithEmailAddress(emailAddress *EmailAddress) (UserId, error) { + sqlQuery := ` + SELECT id FROM app_user + WHERE email_address = $1` + + rows, err := db.Query(sqlQuery, emailAddress.String()) + if err != nil { + return 0, convertPostgresError(err) + } + defer rows.Close() + + var userId int64 + for rows.Next() { + if userId != 0 { + return 0, QueryResultContainedMultipleRowsError + } + + if err := rows.Scan(&userId); err != nil { + return 0, err + } + } + + if userId == 0 { + return 0, QueryResultContainedNoRowsError + } + + return UserId(userId), nil +} diff --git a/services/noteservice/noteservice.go b/services/noteservice/noteservice.go deleted file mode 100644 index 52a9b77..0000000 --- a/services/noteservice/noteservice.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Package noteservice handles interactions with database layer. -*/ -package noteservice - -import ( - "encoding/json" - "fmt" - - "github.com/atmiguel/cerealnotes/databaseutil" - "github.com/atmiguel/cerealnotes/models" -) - -func StoreNewNote( - note *models.Note, -) (models.NoteId, error) { - - id, err := databaseutil.InsertNewNote(int64(note.AuthorId), note.Content, note.CreationTime) - if err != nil { - return models.NoteId(0), err - } - - return models.NoteId(id), nil -} - -func StoreNewNoteCategoryRelationship( - noteId models.NoteId, - category models.Category, -) error { - if err := databaseutil.InsertNoteCategoryRelationship(int64(noteId), category.String()); err != nil { - return err - } - - return nil -} - -type NoteMap map[models.NoteId]*models.Note - -func (noteMap NoteMap) ToJson() ([]byte, error) { - // json doesn't support int indexed maps - notesByIdString := make(map[string]models.Note, len(noteMap)) - - for id, note := range noteMap { - notesByIdString[fmt.Sprint(id)] = *note - } - - return json.Marshal(notesByIdString) -} diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go deleted file mode 100644 index fab7356..0000000 --- a/services/userservice/userservice.go +++ /dev/null @@ -1,92 +0,0 @@ -/* -Package userservice handles interactions with database layer. -*/ -package userservice - -import ( - "errors" - "time" - - "github.com/atmiguel/cerealnotes/databaseutil" - "github.com/atmiguel/cerealnotes/models" - "golang.org/x/crypto/bcrypt" -) - -var EmailAddressAlreadyInUseError = errors.New("Email address already in use") - -var CredentialsNotAuthorizedError = errors.New("The provided credentials were not found") - -func StoreNewUser( - displayName string, - emailAddress *models.EmailAddress, - password string, -) error { - hashedPassword, err := bcrypt.GenerateFromPassword( - []byte(password), - bcrypt.DefaultCost) - if err != nil { - return err - } - - creationTime := time.Now().UTC() - - if err := databaseutil.InsertIntoUserTable( - displayName, - emailAddress.String(), - hashedPassword, - creationTime, - ); err != nil { - if err == databaseutil.UniqueConstraintError { - return EmailAddressAlreadyInUseError - } - - return err - } - - return nil -} - -func AuthenticateUserCredentials(emailAddress *models.EmailAddress, password string) error { - storedHashedPassword, err := databaseutil.GetPasswordForUserWithEmailAddress(emailAddress.String()) - if err != nil { - if err == databaseutil.QueryResultContainedMultipleRowsError { - return err // would normally throw a runtime here - } - - if err == databaseutil.QueryResultContainedNoRowsError { - return CredentialsNotAuthorizedError - } - - return err - } - - if err := bcrypt.CompareHashAndPassword( - storedHashedPassword, - []byte(password), - ); err != nil { - if err == bcrypt.ErrMismatchedHashAndPassword { - return CredentialsNotAuthorizedError - } - - return err - } - - return nil -} - -func GetIdForUserWithEmailAddress(emailAddress *models.EmailAddress) (models.UserId, error) { - userIdAsInt, err := databaseutil.GetIdForUserWithEmailAddress(emailAddress.String()) - if err != nil { - if err == databaseutil.QueryResultContainedMultipleRowsError { - return 0, err // would normally throw a runtime here - } - - if err == databaseutil.QueryResultContainedNoRowsError { - return 0, err - } - - return 0, err - } - - return models.UserId(userIdAsInt), nil -} From 26c279037632c38b44772f4487df46e57e241bb9 Mon Sep 17 00:00:00 2001 From: G Date: Sat, 15 Sep 2018 18:58:01 -0700 Subject: [PATCH 2/3] fixed small type issue --- models/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user.go b/models/user.go index c0b2a31..b90198f 100644 --- a/models/user.go +++ b/models/user.go @@ -69,7 +69,7 @@ func (db *DB) AuthenticateUserCredentials(emailAddress *EmailAddress, password s SELECT password FROM app_user WHERE email_address = $1` - rows, err := db.Query(sqlQuery, emailAddress) + rows, err := db.Query(sqlQuery, emailAddress.String()) if err != nil { return convertPostgresError(err) } From 23e528c626cabfa2adfc7bb0d295a5c322a4635d Mon Sep 17 00:00:00 2001 From: G Date: Sat, 15 Sep 2018 21:41:49 -0700 Subject: [PATCH 3/3] login test --- integration_test.go | 129 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 integration_test.go diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..0fd1689 --- /dev/null +++ b/integration_test.go @@ -0,0 +1,129 @@ +package main_test + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/atmiguel/cerealnotes/handlers" + "github.com/atmiguel/cerealnotes/models" + "github.com/atmiguel/cerealnotes/paths" + "github.com/atmiguel/cerealnotes/routers" +) + +func TestLoginOrSignUpPage(t *testing.T) { + mockDb := &DiyMockDataStore{} + env := &handlers.Environment{mockDb} + + handlers.SetEnvironment(env) + handlers.SetTokenSigningKey([]byte("")) + + server := httptest.NewServer(routers.DefineRoutes()) + defer server.Close() + + resp, err := http.Get(server.URL) + ok(t, err) + + // fmt.Println(ioutil.ReadAll(resp.Body)) + equals(t, 200, resp.StatusCode) +} + +func TestLoginApi(t *testing.T) { + mockDb := &DiyMockDataStore{} + env := &handlers.Environment{mockDb} + + handlers.SetEnvironment(env) + handlers.SetTokenSigningKey([]byte("")) + + server := httptest.NewServer(routers.DefineRoutes()) + defer server.Close() + + theEmail := "justsomeemail@gmail.com" + thePassword := "worldsBestPassword" + + mockDb.Func_AuthenticateUserCredentials = func(email *models.EmailAddress, password string) error { + if email.String() == theEmail && password == thePassword { + return nil + } + + return models.CredentialsNotAuthorizedError + } + + mockDb.Func_GetIdForUserWithEmailAddress = func(email *models.EmailAddress) (models.UserId, error) { + return models.UserId(1), nil + } + + values := map[string]string{"emailAddress": theEmail, "password": thePassword} + + jsonValue, _ := json.Marshal(values) + + resp, err := http.Post(server.URL+paths.SessionApi, "application/json", bytes.NewBuffer(jsonValue)) + + ok(t, err) + + // fmt.Println(ioutil.ReadAll(resp.Body)) + equals(t, 201, resp.StatusCode) +} + +// Helpers + +type DiyMockDataStore struct { + Func_StoreNewNote func(*models.Note) (models.NoteId, error) + Func_StoreNewNoteCategoryRelationship func(models.NoteId, models.Category) error + Func_StoreNewUser func(string, *models.EmailAddress, string) error + Func_AuthenticateUserCredentials func(*models.EmailAddress, string) error + Func_GetIdForUserWithEmailAddress func(*models.EmailAddress) (models.UserId, error) +} + +func (mock *DiyMockDataStore) StoreNewNote(note *models.Note) (models.NoteId, error) { + return mock.Func_StoreNewNote(note) +} + +func (mock *DiyMockDataStore) StoreNewNoteCategoryRelationship(noteId models.NoteId, cat models.Category) error { + return mock.Func_StoreNewNoteCategoryRelationship(noteId, cat) +} + +func (mock *DiyMockDataStore) StoreNewUser(str1 string, email *models.EmailAddress, str2 string) error { + return mock.Func_StoreNewUser(str1, email, str2) +} + +func (mock *DiyMockDataStore) AuthenticateUserCredentials(email *models.EmailAddress, str string) error { + return mock.Func_AuthenticateUserCredentials(email, str) +} + +func (mock *DiyMockDataStore) GetIdForUserWithEmailAddress(email *models.EmailAddress) (models.UserId, error) { + return mock.Func_GetIdForUserWithEmailAddress(email) +} + +// assert fails the test if the condition is false. +func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { + if !condition { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) + tb.FailNow() + } +} + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +// equals fails the test if exp is not equal to act. +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +}