test: re-worked user repo tests
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
|||||||
"git.dittmar.dev/robin/dttmr-api/internal/api/response"
|
"git.dittmar.dev/robin/dttmr-api/internal/api/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VersionResponse struct {
|
type versionResponse struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Commit string `json:"commit"`
|
Commit string `json:"commit"`
|
||||||
BuildTime string `json:"buildTime"`
|
BuildTime string `json:"buildTime"`
|
||||||
@@ -19,11 +19,11 @@ type VersionResponse struct {
|
|||||||
// @Tags Version
|
// @Tags Version
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} VersionResponse
|
// @Success 200 {object} versionResponse
|
||||||
// @Router /version [get]
|
// @Router /version [get]
|
||||||
func VersionHandler(version string, commit string, buildTime string) func(http.ResponseWriter, *http.Request) {
|
func VersionHandler(version string, commit string, buildTime string) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.JSON(r.Context(), w, http.StatusOK, VersionResponse{
|
response.JSON(r.Context(), w, http.StatusOK, versionResponse{
|
||||||
Version: version,
|
Version: version,
|
||||||
Commit: commit,
|
Commit: commit,
|
||||||
BuildTime: buildTime,
|
BuildTime: buildTime,
|
||||||
|
|||||||
+185
-162
@@ -1,185 +1,208 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.dittmar.dev/robin/dttmr-api/internal/domain"
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"git.dittmar.dev/robin/dttmr-api/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newUserRepo(t *testing.T) (*UserRepo, sqlmock.Sqlmock) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
db, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
assert.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
_ = db.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
return &UserRepo{Repo: NewRepo(NewTransactor(db))}, mock
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
insertUserQuery = `INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING id, created_at`
|
||||||
|
deleteUserQuery = `DELETE FROM users WHERE id = $1`
|
||||||
|
updatePassQuery = `UPDATE users SET password_hash = $1 WHERE id = $2`
|
||||||
|
selectUserQuery = `SELECT id, email, name FROM users WHERE email = $1`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserRepo_CreateUser(t *testing.T) {
|
func TestUserRepo_CreateUser(t *testing.T) {
|
||||||
db, mock, err := sqlmock.New()
|
t.Run("success", func(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
repo, mock := newUserRepo(t)
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
//repo := NewUserRepo(db)
|
createdAt := time.Date(2026, 9, 9, 10, 0, 0, 0, time.UTC)
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(insertUserQuery)).
|
||||||
|
WithArgs("robin@dittmar.dev", "Robin", "$2a$10$hash").
|
||||||
|
WillReturnRows(
|
||||||
|
sqlmock.NewRows([]string{"id", "created_at"}).
|
||||||
|
AddRow("2f1c...", createdAt),
|
||||||
|
)
|
||||||
|
|
||||||
//ctx := context.Background()
|
user, err := repo.CreateUser(context.Background(), "robin@dittmar.dev", "Robin", "$2a$10$hash")
|
||||||
email := "test@example.com"
|
|
||||||
name := "Test User"
|
|
||||||
passwordHash := "hashedpassword123"
|
|
||||||
|
|
||||||
now := time.Now()
|
require.NoError(t, err)
|
||||||
expectedUser := &domain.User{
|
require.NotNil(t, user)
|
||||||
ID: "1",
|
assert.Equal(t, &domain.User{
|
||||||
Email: email,
|
ID: "2f1c...",
|
||||||
Name: name,
|
Email: "robin@dittmar.dev",
|
||||||
CreatedAt: now,
|
Name: "Robin",
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
}, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("db error is wrapped", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
dbErr := errors.New("duplicate key value violates unique constraint")
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(insertUserQuery)).
|
||||||
|
WithArgs("robin@dittmar.dev", "Robin", "$2a$10$hash").
|
||||||
|
WillReturnError(dbErr)
|
||||||
|
|
||||||
|
user, err := repo.CreateUser(context.Background(), "robin@dittmar.dev", "Robin", "$2a$10$hash")
|
||||||
|
|
||||||
|
assert.Nil(t, user)
|
||||||
|
assert.ErrorIs(t, err, dbErr)
|
||||||
|
assert.ErrorContains(t, err, "failed to insert user")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserRepo_DeleteUser(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(deleteUserQuery)).
|
||||||
|
WithArgs("user-1").
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
|
|
||||||
|
assert.NoError(t, repo.DeleteUser(context.Background(), "user-1"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown id is not reported", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(deleteUserQuery)).
|
||||||
|
WithArgs("does-not-exist").
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 0))
|
||||||
|
|
||||||
|
assert.NoError(t, repo.DeleteUser(context.Background(), "does-not-exist"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("db error is wrapped", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
dbErr := errors.New("connection reset")
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(deleteUserQuery)).
|
||||||
|
WithArgs("user-1").
|
||||||
|
WillReturnError(dbErr)
|
||||||
|
|
||||||
|
err := repo.DeleteUser(context.Background(), "user-1")
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, dbErr)
|
||||||
|
assert.ErrorContains(t, err, "failed to delete user")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRepo_ChangePassword(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(updatePassQuery)).
|
||||||
|
WithArgs("$2a$10$newhash", "user-1").
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
|
|
||||||
|
assert.NoError(t, repo.ChangePassword(context.Background(), "user-1", "$2a$10$newhash"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("db error is wrapped", func(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
|
dbErr := errors.New("deadlock detected")
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(updatePassQuery)).
|
||||||
|
WithArgs("$2a$10$newhash", "user-1").
|
||||||
|
WillReturnError(dbErr)
|
||||||
|
|
||||||
|
err := repo.ChangePassword(context.Background(), "user-1", "$2a$10$newhash")
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, dbErr)
|
||||||
|
assert.ErrorContains(t, err, "failed to update user")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRepo_UsesTransactionFromContext(t *testing.T) {
|
||||||
|
repo, mock := newUserRepo(t)
|
||||||
|
|
||||||
mock.ExpectBegin()
|
mock.ExpectBegin()
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(deleteUserQuery)).
|
||||||
mock.ExpectQuery(`^INSERT INTO users \(email, name, password_hash\) VALUES \(\$1, \$2, \$3\) RETURNING id, created_at$`).
|
WithArgs("user-1").
|
||||||
WithArgs(email, name, passwordHash).
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at"}).AddRow(expectedUser.ID, expectedUser.CreatedAt))
|
|
||||||
|
|
||||||
mock.ExpectCommit()
|
mock.ExpectCommit()
|
||||||
|
|
||||||
//user, err := repo.CreateUser(ctx, email, name, passwordHash)
|
tx, err := repo.db.BeginTx(context.Background(), nil)
|
||||||
//assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
//assert.Equal(t, expectedUser, user)
|
|
||||||
//assert.NoError(t, mock.ExpectationsWereMet())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("begin_tx_error", func(t *testing.T) {
|
ctx := context.WithValue(context.Background(), txKey{}, tx)
|
||||||
mock.ExpectBegin().WillReturnError(fmt.Errorf("tx error"))
|
require.NoError(t, repo.DeleteUser(ctx, "user-1"))
|
||||||
|
require.NoError(t, tx.Commit())
|
||||||
//user, err := repo.CreateUser(ctx, email, name, passwordHash)
|
|
||||||
//assert.Error(t, err)
|
|
||||||
//assert.Contains(t, err.Error(), "begin transaction")
|
|
||||||
//assert.Nil(t, user)
|
|
||||||
//assert.NoError(t, mock.ExpectationsWereMet())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("insert_error", func(t *testing.T) {
|
|
||||||
mock.ExpectBegin()
|
|
||||||
mock.ExpectQuery(`^INSERT INTO users \(email, name, password_hash\) VALUES \(\$1, \$2, \$3\) RETURNING id, created_at$`).
|
|
||||||
WithArgs(email, name, passwordHash).
|
|
||||||
WillReturnError(fmt.Errorf("insert error"))
|
|
||||||
mock.ExpectRollback()
|
|
||||||
|
|
||||||
//user, err := repo.CreateUser(ctx, email, name, passwordHash)
|
|
||||||
//assert.Error(t, err)
|
|
||||||
//assert.Contains(t, err.Error(), "failed to insert user")
|
|
||||||
//assert.Nil(t, user)
|
|
||||||
//assert.NoError(t, mock.ExpectationsWereMet())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("commit_error", func(t *testing.T) {
|
|
||||||
mock.ExpectBegin()
|
|
||||||
mock.ExpectQuery(`^INSERT INTO users \(email, name, password_hash\) VALUES \(\$1, \$2, \$3\) RETURNING id, created_at$`).
|
|
||||||
WithArgs(email, name, passwordHash).
|
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at"}).AddRow(expectedUser.ID, expectedUser.CreatedAt))
|
|
||||||
mock.ExpectCommit().WillReturnError(fmt.Errorf("commit error"))
|
|
||||||
|
|
||||||
//user, err := repo.CreateUser(ctx, email, name, passwordHash)
|
|
||||||
//assert.Error(t, err)
|
|
||||||
//assert.Contains(t, err.Error(), "commit transaction")
|
|
||||||
//assert.Nil(t, user)
|
|
||||||
//assert.NoError(t, mock.ExpectationsWereMet())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//func TestUserRepo_CreateUser2(t *testing.T) {
|
func TestUserRepo_GetUserByEmail(t *testing.T) {
|
||||||
// email := "test@example.com"
|
t.Run("success", func(t *testing.T) {
|
||||||
// name := "Test User"
|
repo, mock := newUserRepo(t)
|
||||||
// passwordHash := "hashedpassword123"
|
|
||||||
// now := time.Now()
|
mock.ExpectQuery(regexp.QuoteMeta(selectUserQuery)).
|
||||||
// expectedID := "42"
|
WithArgs("robin@dittmar.dev").
|
||||||
//
|
WillReturnRows(
|
||||||
// insertQuery := regexp.QuoteMeta(
|
sqlmock.NewRows([]string{"id", "email", "name"}).
|
||||||
// "INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING id, created_at",
|
AddRow("user-1", "robin@dittmar.dev", "Robin"),
|
||||||
// )
|
)
|
||||||
//
|
|
||||||
// testCases := []struct {
|
user, err := repo.GetUserByEmail(context.Background(), "robin@dittmar.dev")
|
||||||
// name string
|
|
||||||
// setupMock func(mock sqlmock.Sqlmock)
|
require.NoError(t, err)
|
||||||
// expectedError string
|
require.NotNil(t, user)
|
||||||
// }{
|
assert.Equal(t, "user-1", user.ID)
|
||||||
// {
|
assert.Equal(t, "robin@dittmar.dev", user.Email)
|
||||||
// name: "Success: User created perfectly",
|
assert.Equal(t, "Robin", user.Name)
|
||||||
// setupMock: func(mock sqlmock.Sqlmock) {
|
assert.Zero(t, user.CreatedAt) // not selected by this query
|
||||||
// mock.ExpectBegin()
|
})
|
||||||
//
|
|
||||||
// rows := sqlmock.NewRows([]string{"id", "created_at"}).
|
t.Run("not found stays matchable via errors.Is", func(t *testing.T) {
|
||||||
// AddRow(expectedID, now)
|
repo, mock := newUserRepo(t)
|
||||||
//
|
|
||||||
// mock.ExpectQuery(insertQuery).
|
mock.ExpectQuery(regexp.QuoteMeta(selectUserQuery)).
|
||||||
// WithArgs(email, name, passwordHash).
|
WithArgs("nobody@dittmar.dev").
|
||||||
// WillReturnRows(rows)
|
WillReturnError(sql.ErrNoRows)
|
||||||
//
|
|
||||||
// mock.ExpectCommit()
|
user, err := repo.GetUserByEmail(context.Background(), "nobody@dittmar.dev")
|
||||||
// },
|
|
||||||
// expectedError: "",
|
assert.Nil(t, user)
|
||||||
// },
|
assert.ErrorIs(t, err, sql.ErrNoRows)
|
||||||
// {
|
assert.ErrorContains(t, err, "failed to get user")
|
||||||
// name: "Failure: Database connection fails on BeginTx",
|
})
|
||||||
// setupMock: func(mock sqlmock.Sqlmock) {
|
|
||||||
// mock.ExpectBegin().WillReturnError(errors.New("db connection failed"))
|
t.Run("scan error on type mismatch", func(t *testing.T) {
|
||||||
// },
|
repo, mock := newUserRepo(t)
|
||||||
// expectedError: "begin transaction: db connection failed",
|
|
||||||
// },
|
mock.ExpectQuery(regexp.QuoteMeta(selectUserQuery)).
|
||||||
// {
|
WithArgs("robin@dittmar.dev").
|
||||||
// name: "Failure: Query fails (e.g., duplicate email)",
|
WillReturnRows(
|
||||||
// setupMock: func(mock sqlmock.Sqlmock) {
|
sqlmock.NewRows([]string{"id", "email", "name"}).
|
||||||
// mock.ExpectBegin()
|
AddRow(nil, "robin@dittmar.dev", "Robin"),
|
||||||
//
|
)
|
||||||
// mock.ExpectQuery(insertQuery).
|
|
||||||
// WithArgs(email, name, passwordHash).
|
user, err := repo.GetUserByEmail(context.Background(), "robin@dittmar.dev")
|
||||||
// WillReturnError(errors.New("unique constraint violation"))
|
|
||||||
//
|
assert.Nil(t, user)
|
||||||
// mock.ExpectRollback()
|
assert.Error(t, err)
|
||||||
// },
|
})
|
||||||
// expectedError: "failed to insert user: unique constraint violation",
|
}
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Failure: Commit fails (e.g., network timeout)",
|
|
||||||
// setupMock: func(mock sqlmock.Sqlmock) {
|
|
||||||
// mock.ExpectBegin()
|
|
||||||
//
|
|
||||||
// rows := sqlmock.NewRows([]string{"id", "created_at"}).
|
|
||||||
// AddRow(expectedID, now)
|
|
||||||
//
|
|
||||||
// mock.ExpectQuery(insertQuery).
|
|
||||||
// WithArgs(email, name, passwordHash).
|
|
||||||
// WillReturnRows(rows)
|
|
||||||
//
|
|
||||||
// mock.ExpectCommit().WillReturnError(errors.New("commit timeout"))
|
|
||||||
// },
|
|
||||||
// expectedError: "commit transaction: commit timeout",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, tc := range testCases {
|
|
||||||
// t.Run(tc.name, func(t *testing.T) {
|
|
||||||
// db, mock, err := sqlmock.New()
|
|
||||||
// require.NoError(t, err)
|
|
||||||
// defer db.Close()
|
|
||||||
//
|
|
||||||
// tc.setupMock(mock)
|
|
||||||
//
|
|
||||||
// //repo := NewUserRepo(db)
|
|
||||||
// //
|
|
||||||
// //user, err := repo.CreateUser(context.Background(), email, name, passwordHash)
|
|
||||||
//
|
|
||||||
// if tc.expectedError != "" {
|
|
||||||
// require.Error(t, err)
|
|
||||||
// assert.Contains(t, err.Error(), tc.expectedError)
|
|
||||||
// //assert.Nil(t, user)
|
|
||||||
// } else {
|
|
||||||
// require.NoError(t, err)
|
|
||||||
// //require.NotNil(t, user)
|
|
||||||
// //assert.Equal(t, expectedID, user.ID)
|
|
||||||
// //assert.Equal(t, email, user.Email)
|
|
||||||
// //assert.Equal(t, name, user.Name)
|
|
||||||
// //assert.Equal(t, now, user.CreatedAt)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// assert.NoError(t, mock.ExpectationsWereMet())
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user