Migrate from Gin to Echo framework and add comprehensive integration tests
Major changes: - Migrate all handlers from Gin to Echo framework - Add new apperrors, echohelpers, and validator packages - Update middleware for Echo compatibility - Add ArchivedHandler to task categorization chain (archived tasks go to cancelled_tasks column) - Add 6 new integration tests: - RecurringTaskLifecycle: NextDueDate advancement for weekly/monthly tasks - MultiUserSharing: Complex sharing with user removal - TaskStateTransitions: All state transitions and kanban column changes - DateBoundaryEdgeCases: Threshold boundary testing - CascadeOperations: Residence deletion cascade effects - MultiUserOperations: Shared residence collaboration - Add single-purpose repository functions for kanban columns (GetOverdueTasks, GetDueSoonTasks, etc.) - Fix RemoveUser route param mismatch (userId -> user_id) - Fix determineExpectedColumn helper to correctly prioritize in_progress over overdue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/treytartt/casera-api/internal/testutil"
|
||||
)
|
||||
|
||||
func setupAuthHandler(t *testing.T) (*AuthHandler, *gin.Engine, *repositories.UserRepository) {
|
||||
func setupAuthHandler(t *testing.T) (*AuthHandler, *echo.Echo, *repositories.UserRepository) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{
|
||||
@@ -30,14 +30,14 @@ func setupAuthHandler(t *testing.T) (*AuthHandler, *gin.Engine, *repositories.Us
|
||||
}
|
||||
authService := services.NewAuthService(userRepo, cfg)
|
||||
handler := NewAuthHandler(authService, nil, nil) // No email or cache for tests
|
||||
router := testutil.SetupTestRouter()
|
||||
return handler, router, userRepo
|
||||
e := testutil.SetupTestRouter()
|
||||
return handler, e, userRepo
|
||||
}
|
||||
|
||||
func TestAuthHandler_Register(t *testing.T) {
|
||||
handler, router, _ := setupAuthHandler(t)
|
||||
handler, e, _ := setupAuthHandler(t)
|
||||
|
||||
router.POST("/api/auth/register/", handler.Register)
|
||||
e.POST("/api/auth/register/", handler.Register)
|
||||
|
||||
t.Run("successful registration", func(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
@@ -48,7 +48,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
LastName: "User",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
// Missing email and password
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
Password: "short", // Less than 8 chars
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
@@ -100,13 +100,13 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
Email: "unique1@test.com",
|
||||
Password: "password123",
|
||||
}
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
// Try to register again with same username
|
||||
req.Email = "unique2@test.com"
|
||||
w = testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
w = testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusConflict) // 409 for duplicate resource
|
||||
|
||||
response := testutil.ParseJSON(t, w.Body.Bytes())
|
||||
assert.Contains(t, response["error"], "Username already taken")
|
||||
@@ -119,13 +119,13 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
Email: "duplicate@test.com",
|
||||
Password: "password123",
|
||||
}
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
// Try to register again with same email
|
||||
req.Username = "user2"
|
||||
w = testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
w = testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusConflict) // 409 for duplicate resource
|
||||
|
||||
response := testutil.ParseJSON(t, w.Body.Bytes())
|
||||
assert.Contains(t, response["error"], "Email already registered")
|
||||
@@ -133,10 +133,10 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthHandler_Login(t *testing.T) {
|
||||
handler, router, _ := setupAuthHandler(t)
|
||||
handler, e, _ := setupAuthHandler(t)
|
||||
|
||||
router.POST("/api/auth/register/", handler.Register)
|
||||
router.POST("/api/auth/login/", handler.Login)
|
||||
e.POST("/api/auth/register/", handler.Register)
|
||||
e.POST("/api/auth/login/", handler.Login)
|
||||
|
||||
// Create a test user
|
||||
registerReq := requests.RegisterRequest{
|
||||
@@ -146,7 +146,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
}
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", registerReq, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", registerReq, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
t.Run("successful login with username", func(t *testing.T) {
|
||||
@@ -155,7 +155,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
})
|
||||
@@ -188,7 +188,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
Password: "wrongpassword",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusUnauthorized)
|
||||
|
||||
@@ -202,7 +202,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusUnauthorized)
|
||||
})
|
||||
@@ -213,14 +213,14 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
// Missing password
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthHandler_CurrentUser(t *testing.T) {
|
||||
handler, router, userRepo := setupAuthHandler(t)
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "metest", "me@test.com", "password123")
|
||||
@@ -229,12 +229,12 @@ func TestAuthHandler_CurrentUser(t *testing.T) {
|
||||
userRepo.Update(user)
|
||||
|
||||
// Set up route with mock auth middleware
|
||||
authGroup := router.Group("/api/auth")
|
||||
authGroup := e.Group("/api/auth")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/me/", handler.CurrentUser)
|
||||
|
||||
t.Run("get current user", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(router, "GET", "/api/auth/me/", nil, "test-token")
|
||||
w := testutil.MakeRequest(e, "GET", "/api/auth/me/", nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
@@ -248,13 +248,13 @@ func TestAuthHandler_CurrentUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthHandler_UpdateProfile(t *testing.T) {
|
||||
handler, router, userRepo := setupAuthHandler(t)
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "updatetest", "update@test.com", "password123")
|
||||
userRepo.Update(user)
|
||||
|
||||
authGroup := router.Group("/api/auth")
|
||||
authGroup := e.Group("/api/auth")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.PUT("/profile/", handler.UpdateProfile)
|
||||
|
||||
@@ -266,7 +266,7 @@ func TestAuthHandler_UpdateProfile(t *testing.T) {
|
||||
LastName: &lastName,
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "PUT", "/api/auth/profile/", req, "test-token")
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/auth/profile/", req, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
@@ -280,10 +280,10 @@ func TestAuthHandler_UpdateProfile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthHandler_ForgotPassword(t *testing.T) {
|
||||
handler, router, _ := setupAuthHandler(t)
|
||||
handler, e, _ := setupAuthHandler(t)
|
||||
|
||||
router.POST("/api/auth/register/", handler.Register)
|
||||
router.POST("/api/auth/forgot-password/", handler.ForgotPassword)
|
||||
e.POST("/api/auth/register/", handler.Register)
|
||||
e.POST("/api/auth/forgot-password/", handler.ForgotPassword)
|
||||
|
||||
// Create a test user
|
||||
registerReq := requests.RegisterRequest{
|
||||
@@ -291,14 +291,14 @@ func TestAuthHandler_ForgotPassword(t *testing.T) {
|
||||
Email: "forgot@test.com",
|
||||
Password: "password123",
|
||||
}
|
||||
testutil.MakeRequest(router, "POST", "/api/auth/register/", registerReq, "")
|
||||
testutil.MakeRequest(e, "POST", "/api/auth/register/", registerReq, "")
|
||||
|
||||
t.Run("forgot password with valid email", func(t *testing.T) {
|
||||
req := requests.ForgotPasswordRequest{
|
||||
Email: "forgot@test.com",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/forgot-password/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/forgot-password/", req, "")
|
||||
|
||||
// Always returns 200 to prevent email enumeration
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
@@ -312,7 +312,7 @@ func TestAuthHandler_ForgotPassword(t *testing.T) {
|
||||
Email: "nonexistent@test.com",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/forgot-password/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/forgot-password/", req, "")
|
||||
|
||||
// Still returns 200 to prevent email enumeration
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
@@ -320,18 +320,18 @@ func TestAuthHandler_ForgotPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthHandler_Logout(t *testing.T) {
|
||||
handler, router, userRepo := setupAuthHandler(t)
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "logouttest", "logout@test.com", "password123")
|
||||
userRepo.Update(user)
|
||||
|
||||
authGroup := router.Group("/api/auth")
|
||||
authGroup := e.Group("/api/auth")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/logout/", handler.Logout)
|
||||
|
||||
t.Run("successful logout", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/logout/", nil, "test-token")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/logout/", nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
@@ -341,10 +341,10 @@ func TestAuthHandler_Logout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthHandler_JSONResponses(t *testing.T) {
|
||||
handler, router, _ := setupAuthHandler(t)
|
||||
handler, e, _ := setupAuthHandler(t)
|
||||
|
||||
router.POST("/api/auth/register/", handler.Register)
|
||||
router.POST("/api/auth/login/", handler.Login)
|
||||
e.POST("/api/auth/register/", handler.Register)
|
||||
e.POST("/api/auth/login/", handler.Login)
|
||||
|
||||
t.Run("register response has correct JSON structure", func(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
@@ -355,7 +355,7 @@ func TestAuthHandler_JSONResponses(t *testing.T) {
|
||||
LastName: "Test",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
@@ -393,7 +393,7 @@ func TestAuthHandler_JSONResponses(t *testing.T) {
|
||||
"username": "test",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "")
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user