Coverage priorities 1-5: test pure functions, extract interfaces, mock-based handler tests
- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests) - Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests) - Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests) - Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests) - Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests) - Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ func setupDeleteAccountHandler(t *testing.T) (*AuthHandler, *echo.Echo, *gorm.DB
|
||||
func TestAuthHandler_DeleteAccount_EmailUser(t *testing.T) {
|
||||
handler, e, db := setupDeleteAccountHandler(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "deletetest", "delete@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "deletetest", "delete@test.com", "Password123")
|
||||
|
||||
// Create profile for the user
|
||||
profile := &models.UserProfile{UserID: user.ID, Verified: true}
|
||||
@@ -52,7 +52,7 @@ func TestAuthHandler_DeleteAccount_EmailUser(t *testing.T) {
|
||||
authGroup.DELETE("/account/", handler.DeleteAccount)
|
||||
|
||||
t.Run("successful deletion with correct password", func(t *testing.T) {
|
||||
password := "password123"
|
||||
password := "Password123"
|
||||
req := map[string]interface{}{
|
||||
"password": password,
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func TestAuthHandler_DeleteAccount_EmailUser(t *testing.T) {
|
||||
func TestAuthHandler_DeleteAccount_WrongPassword(t *testing.T) {
|
||||
handler, e, db := setupDeleteAccountHandler(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "wrongpw", "wrongpw@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "wrongpw", "wrongpw@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/auth")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
@@ -105,7 +105,7 @@ func TestAuthHandler_DeleteAccount_WrongPassword(t *testing.T) {
|
||||
func TestAuthHandler_DeleteAccount_MissingPassword(t *testing.T) {
|
||||
handler, e, db := setupDeleteAccountHandler(t)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "nopw", "nopw@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "nopw", "nopw@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/auth")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
@@ -207,7 +207,7 @@ func TestAuthHandler_DeleteAccount_Unauthenticated(t *testing.T) {
|
||||
|
||||
t.Run("unauthenticated request returns 401", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"password": "password123",
|
||||
"password": "Password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/auth/account/", req, "")
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
Username: "newuser",
|
||||
Email: "new@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
FirstName: "New",
|
||||
LastName: "User",
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
Username: "duplicate",
|
||||
Email: "unique1@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
@@ -117,7 +117,7 @@ func TestAuthHandler_Register(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
Username: "user1",
|
||||
Email: "duplicate@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
@@ -142,7 +142,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
registerReq := requests.RegisterRequest{
|
||||
Username: "logintest",
|
||||
Email: "login@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
}
|
||||
@@ -152,7 +152,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
t.Run("successful login with username", func(t *testing.T) {
|
||||
req := requests.LoginRequest{
|
||||
Username: "logintest",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
@@ -174,7 +174,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
t.Run("successful login with email", func(t *testing.T) {
|
||||
req := requests.LoginRequest{
|
||||
Username: "login@test.com", // Using email as username
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
@@ -199,7 +199,7 @@ func TestAuthHandler_Login(t *testing.T) {
|
||||
t.Run("login with non-existent user", func(t *testing.T) {
|
||||
req := requests.LoginRequest{
|
||||
Username: "nonexistent",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")
|
||||
@@ -223,7 +223,7 @@ func TestAuthHandler_CurrentUser(t *testing.T) {
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "metest", "me@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "metest", "me@test.com", "Password123")
|
||||
user.FirstName = "Test"
|
||||
user.LastName = "User"
|
||||
userRepo.Update(user)
|
||||
@@ -251,7 +251,7 @@ func TestAuthHandler_UpdateProfile(t *testing.T) {
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "updatetest", "update@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "updatetest", "update@test.com", "Password123")
|
||||
userRepo.Update(user)
|
||||
|
||||
authGroup := e.Group("/api/auth")
|
||||
@@ -289,7 +289,7 @@ func TestAuthHandler_ForgotPassword(t *testing.T) {
|
||||
registerReq := requests.RegisterRequest{
|
||||
Username: "forgottest",
|
||||
Email: "forgot@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
}
|
||||
testutil.MakeRequest(e, "POST", "/api/auth/register/", registerReq, "")
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestAuthHandler_Logout(t *testing.T) {
|
||||
handler, e, userRepo := setupAuthHandler(t)
|
||||
|
||||
db := testutil.SetupTestDB(t)
|
||||
user := testutil.CreateTestUser(t, db, "logouttest", "logout@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "logouttest", "logout@test.com", "Password123")
|
||||
userRepo.Update(user)
|
||||
|
||||
authGroup := e.Group("/api/auth")
|
||||
@@ -350,7 +350,7 @@ func TestAuthHandler_JSONResponses(t *testing.T) {
|
||||
req := requests.RegisterRequest{
|
||||
Username: "jsontest",
|
||||
Email: "json@test.com",
|
||||
Password: "password123",
|
||||
Password: "Password123",
|
||||
FirstName: "JSON",
|
||||
LastName: "Test",
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -180,3 +181,284 @@ func TestContractorHandler_CreateContractor_100Specialties_Returns400(t *testing
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_ListContractors(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Electrician Bob")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/", handler.ListContractors)
|
||||
|
||||
t.Run("successful list", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response []map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, response, 2)
|
||||
})
|
||||
|
||||
t.Run("user with no contractors returns empty", func(t *testing.T) {
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
|
||||
e2 := testutil.SetupTestRouter()
|
||||
authGroup2 := e2.Group("/api/contractors")
|
||||
authGroup2.Use(testutil.MockAuthMiddleware(otherUser))
|
||||
authGroup2.GET("/", handler.ListContractors)
|
||||
|
||||
w := testutil.MakeRequest(e2, "GET", "/api/contractors/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response []map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, response, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_GetContractor(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/:id/", handler.GetContractor)
|
||||
|
||||
t.Run("successful get", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/contractors/%d/", contractor.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Plumber Joe", response["name"])
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/99999/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_UpdateContractor(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.PUT("/:id/", handler.UpdateContractor)
|
||||
|
||||
t.Run("successful update", func(t *testing.T) {
|
||||
newName := "Plumber Joe Updated"
|
||||
req := requests.UpdateContractorRequest{
|
||||
Name: &newName,
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "PUT", fmt.Sprintf("/api/contractors/%d/", contractor.ID), req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Plumber Joe Updated", response["name"])
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
newName := "Updated"
|
||||
req := requests.UpdateContractorRequest{Name: &newName}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/contractors/invalid/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
newName := "Updated"
|
||||
req := requests.UpdateContractorRequest{Name: &newName}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/contractors/99999/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_DeleteContractor(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.DELETE("/:id/", handler.DeleteContractor)
|
||||
|
||||
t.Run("successful delete", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/contractors/%d/", contractor.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, response, "message")
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/contractors/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/contractors/99999/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_ToggleFavorite(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/:id/toggle-favorite/", handler.ToggleFavorite)
|
||||
|
||||
t.Run("toggle favorite on", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/contractors/%d/toggle-favorite/", contractor.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, response, "is_favorite")
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/contractors/invalid/toggle-favorite/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/contractors/99999/toggle-favorite/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_ListContractorsByResidence(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/by-residence/:residence_id/", handler.ListContractorsByResidence)
|
||||
|
||||
t.Run("successful list by residence", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/contractors/by-residence/%d/", residence.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response []map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, response, 1)
|
||||
})
|
||||
|
||||
t.Run("invalid residence id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/by-residence/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_GetSpecialties(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/specialties/", handler.GetSpecialties)
|
||||
|
||||
t.Run("successful list specialties", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/specialties/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response []map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, len(response), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_GetContractorTasks(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Plumber Joe")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/:id/tasks/", handler.GetContractorTasks)
|
||||
|
||||
t.Run("successful get tasks", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/contractors/%d/tasks/", contractor.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/contractors/invalid/tasks/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContractorHandler_CreateContractor_WithOptionalFields(t *testing.T) {
|
||||
handler, e, db := setupContractorHandler(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
authGroup := e.Group("/api/contractors")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/", handler.CreateContractor)
|
||||
|
||||
t.Run("creation with all optional fields", func(t *testing.T) {
|
||||
rating := 4.5
|
||||
isFavorite := true
|
||||
req := requests.CreateContractorRequest{
|
||||
ResidenceID: &residence.ID,
|
||||
Name: "Full Contractor",
|
||||
Company: "ABC Plumbing",
|
||||
Phone: "555-1234",
|
||||
Email: "contractor@test.com",
|
||||
Notes: "Great work",
|
||||
Rating: &rating,
|
||||
IsFavorite: &isFavorite,
|
||||
}
|
||||
|
||||
w := testutil.MakeRequest(e, "POST", "/api/contractors/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Full Contractor", response["name"])
|
||||
assert.Equal(t, "ABC Plumbing", response["company"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -224,3 +224,235 @@ func TestDocumentHandler_DeleteDocument(t *testing.T) {
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_UpdateDocument(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Original Title")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.PUT("/:id/", handler.UpdateDocument)
|
||||
|
||||
t.Run("successful update", func(t *testing.T) {
|
||||
newTitle := "Updated Title"
|
||||
req := map[string]interface{}{
|
||||
"title": newTitle,
|
||||
}
|
||||
w := testutil.MakeRequest(e, "PUT", fmt.Sprintf("/api/documents/%d/", doc.ID), req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Title", response["title"])
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{"title": "Updated"}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/documents/invalid/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
req := map[string]interface{}{"title": "Updated"}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/documents/99999/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("access denied for other user", func(t *testing.T) {
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
|
||||
e2 := testutil.SetupTestRouter()
|
||||
otherGroup := e2.Group("/api/documents")
|
||||
otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
|
||||
otherGroup.PUT("/:id/", handler.UpdateDocument)
|
||||
|
||||
req := map[string]interface{}{"title": "Hacked"}
|
||||
w := testutil.MakeRequest(e2, "PUT", fmt.Sprintf("/api/documents/%d/", doc.ID), req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_ListDocuments_Filters(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Active Doc")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/", handler.ListDocuments)
|
||||
|
||||
t.Run("filter by residence", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/documents/?residence=%d", residence.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response []map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, response, 1)
|
||||
})
|
||||
|
||||
t.Run("filter by search", func(t *testing.T) {
|
||||
t.Skip("ILIKE is not supported in SQLite; search filter requires PostgreSQL")
|
||||
})
|
||||
|
||||
t.Run("expiring_soon out of range returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/documents/?expiring_soon=5000", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_ListWarranties(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
// Create a warranty document
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Warranty Doc")
|
||||
require.NoError(t, db.Model(doc).Update("document_type", "warranty").Error)
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/warranties/", handler.ListWarranties)
|
||||
|
||||
t.Run("successful list warranties", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/documents/warranties/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_ActivateDeactivateDocument(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Toggle Doc")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/:id/deactivate/", handler.DeactivateDocument)
|
||||
authGroup.POST("/:id/activate/", handler.ActivateDocument)
|
||||
|
||||
t.Run("deactivate document", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/documents/%d/deactivate/", doc.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, response["is_active"])
|
||||
})
|
||||
|
||||
t.Run("activate document", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/documents/%d/activate/", doc.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, response["is_active"])
|
||||
})
|
||||
|
||||
t.Run("activate invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/invalid/activate/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("deactivate invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/invalid/deactivate/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("activate not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/99999/activate/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("deactivate not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/99999/deactivate/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_CreateDocument_ValidationErrors(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/", handler.CreateDocument)
|
||||
|
||||
t.Run("missing title returns 400", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"residence_id": residence.ID,
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/", body, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("missing residence_id returns 400", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"title": "Test Doc",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/", body, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("invalid document_type returns 400", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"title": "Test Doc",
|
||||
"residence_id": residence.ID,
|
||||
"document_type": "invalid_type",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/documents/", body, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_GetDocument_InvalidID(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/:id/", handler.GetDocument)
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/documents/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_DeleteDocument_InvalidID(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.DELETE("/:id/", handler.DeleteDocument)
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/documents/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDocumentHandler_DeleteDocument_AccessDenied(t *testing.T) {
|
||||
handler, e, db := setupDocumentHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
|
||||
|
||||
authGroup := e.Group("/api/documents")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(otherUser))
|
||||
authGroup.DELETE("/:id/", handler.DeleteDocument)
|
||||
|
||||
t.Run("access denied for other user", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", fmt.Sprintf("/api/documents/%d/", doc.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
1869
internal/handlers/handler_coverage_test.go
Normal file
1869
internal/handlers/handler_coverage_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -86,3 +86,323 @@ func TestNotificationHandler_ListNotifications_LimitCappedAt200(t *testing.T) {
|
||||
assert.Equal(t, 50, count, "response should use default limit of 50")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_ListNotifications_Pagination(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
createTestNotifications(t, db, user.ID, 20)
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/", handler.ListNotifications)
|
||||
|
||||
t.Run("offset skips notifications", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=5&offset=15", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
count := int(response["count"].(float64))
|
||||
assert.Equal(t, 5, count, "should return remaining 5 after offset 15")
|
||||
})
|
||||
|
||||
t.Run("response has results array", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=3", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, response, "results")
|
||||
assert.Contains(t, response, "count")
|
||||
results := response["results"].([]interface{})
|
||||
assert.Len(t, results, 3)
|
||||
})
|
||||
|
||||
t.Run("negative limit ignored", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/?limit=-5", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Negative limit should default to 50 (since -5 > 0 is false)
|
||||
count := int(response["count"].(float64))
|
||||
assert.Equal(t, 20, count, "should return all 20 with default limit of 50")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_GetUnreadCount(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create some unread notifications
|
||||
createTestNotifications(t, db, user.ID, 5)
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/unread-count/", handler.GetUnreadCount)
|
||||
|
||||
t.Run("successful unread count", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/unread-count/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, response, "unread_count")
|
||||
unreadCount := int(response["unread_count"].(float64))
|
||||
assert.Equal(t, 5, unreadCount)
|
||||
})
|
||||
|
||||
t.Run("user with no notifications returns zero", func(t *testing.T) {
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
|
||||
e2 := testutil.SetupTestRouter()
|
||||
authGroup2 := e2.Group("/api/notifications")
|
||||
authGroup2.Use(testutil.MockAuthMiddleware(otherUser))
|
||||
authGroup2.GET("/unread-count/", handler.GetUnreadCount)
|
||||
|
||||
w := testutil.MakeRequest(e2, "GET", "/api/notifications/unread-count/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, float64(0), response["unread_count"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_MarkAsRead(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create a notification
|
||||
notif := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Test Notification",
|
||||
Body: "Test Body",
|
||||
}
|
||||
require.NoError(t, db.Create(notif).Error)
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/:id/read/", handler.MarkAsRead)
|
||||
|
||||
t.Run("successful mark as read", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/notifications/%d/read/", notif.ID), nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, response, "message")
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/invalid/read/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("not found returns 404", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/99999/read/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_MarkAllAsRead(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
createTestNotifications(t, db, user.ID, 5)
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/mark-all-read/", handler.MarkAllAsRead)
|
||||
authGroup.GET("/unread-count/", handler.GetUnreadCount)
|
||||
|
||||
t.Run("successful mark all as read", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/mark-all-read/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, response, "message")
|
||||
})
|
||||
|
||||
t.Run("unread count is zero after mark all", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/unread-count/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, float64(0), response["unread_count"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_GetPreferences(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/preferences/", handler.GetPreferences)
|
||||
|
||||
t.Run("successful get preferences", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/preferences/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Default preferences should have standard fields
|
||||
assert.Contains(t, response, "task_due_soon")
|
||||
assert.Contains(t, response, "task_overdue")
|
||||
assert.Contains(t, response, "task_completed")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_UpdatePreferences(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.PUT("/preferences/", handler.UpdatePreferences)
|
||||
|
||||
t.Run("successful update preferences", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"task_due_soon": false,
|
||||
"task_overdue": true,
|
||||
}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/notifications/preferences/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, response["task_due_soon"])
|
||||
assert.Equal(t, true, response["task_overdue"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_RegisterDevice(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/devices/", handler.RegisterDevice)
|
||||
|
||||
t.Run("successful device registration", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"name": "iPhone 15",
|
||||
"device_id": "test-device-id-123",
|
||||
"registration_id": "test-registration-id-abc",
|
||||
"platform": "ios",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
})
|
||||
|
||||
t.Run("missing required fields returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"name": "iPhone 15",
|
||||
// Missing device_id, registration_id, platform
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("invalid platform returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"device_id": "test-device-id-456",
|
||||
"registration_id": "test-registration-id-def",
|
||||
"platform": "windows", // invalid
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_ListDevices(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/devices/", handler.ListDevices)
|
||||
|
||||
t.Run("successful list devices", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/notifications/devices/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_UnregisterDevice(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/devices/unregister/", handler.UnregisterDevice)
|
||||
|
||||
t.Run("missing registration_id returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"platform": "ios",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("missing platform returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"registration_id": "test-id",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("invalid platform returns 400", func(t *testing.T) {
|
||||
req := map[string]interface{}{
|
||||
"registration_id": "test-id",
|
||||
"platform": "windows",
|
||||
}
|
||||
w := testutil.MakeRequest(e, "POST", "/api/notifications/devices/unregister/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationHandler_DeleteDevice(t *testing.T) {
|
||||
handler, e, db := setupNotificationHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/notifications")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.DELETE("/devices/:id/", handler.DeleteDevice)
|
||||
|
||||
t.Run("missing platform query param returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/1/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("invalid platform returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/1/?platform=windows", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/notifications/devices/invalid/?platform=ios", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -567,3 +567,164 @@ func TestResidenceHandler_CreateResidence_NegativeBedrooms_Returns400(t *testing
|
||||
testutil.AssertStatusCode(t, w, http.StatusCreated)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_GetMyResidences(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
testutil.CreateTestResidence(t, db, user.ID, "House 1")
|
||||
testutil.CreateTestResidence(t, db, user.ID, "House 2")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/my-residences/", handler.GetMyResidences)
|
||||
|
||||
t.Run("successful my residences", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/residences/my-residences/", nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
// GetMyResidences returns MyResidencesResponse: {"residences": [...]}
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
residences := response["residences"].([]interface{})
|
||||
assert.Len(t, residences, 2)
|
||||
})
|
||||
|
||||
t.Run("user with no residences returns empty", func(t *testing.T) {
|
||||
noResUser := testutil.CreateTestUser(t, db, "nores", "nores@test.com", "Password123")
|
||||
|
||||
e2 := testutil.SetupTestRouter()
|
||||
authGroup2 := e2.Group("/api/residences")
|
||||
authGroup2.Use(testutil.MockAuthMiddleware(noResUser))
|
||||
authGroup2.GET("/my-residences/", handler.GetMyResidences)
|
||||
|
||||
w := testutil.MakeRequest(e2, "GET", "/api/residences/my-residences/", nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
// GetMyResidences returns MyResidencesResponse: {"residences": [...] or null}
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
if response["residences"] == nil {
|
||||
// null residences means no residences
|
||||
} else {
|
||||
residences := response["residences"].([]interface{})
|
||||
assert.Len(t, residences, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_GetSummary(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
testutil.CreateTestResidence(t, db, user.ID, "House 1")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/summary/", handler.GetSummary)
|
||||
|
||||
t.Run("successful summary", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/residences/summary/", nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, response, "total_residences")
|
||||
assert.Contains(t, response, "total_tasks")
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_UpdateResidence_InvalidID(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.PUT("/:id/", handler.UpdateResidence)
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
newName := "Updated"
|
||||
req := requests.UpdateResidenceRequest{Name: &newName}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/residences/invalid/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("non-existent id returns 403", func(t *testing.T) {
|
||||
newName := "Updated"
|
||||
req := requests.UpdateResidenceRequest{Name: &newName}
|
||||
w := testutil.MakeRequest(e, "PUT", "/api/residences/9999/", req, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_DeleteResidence_InvalidID(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.DELETE("/:id/", handler.DeleteResidence)
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/residences/invalid/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("non-existent id returns 403", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "DELETE", "/api/residences/9999/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_GetShareCode(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Share Code Test")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.GET("/:id/share-code/", handler.GetShareCode)
|
||||
|
||||
t.Run("no share code returns null", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", fmt.Sprintf("/api/residences/%d/share-code/", residence.ID), nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, response["share_code"])
|
||||
})
|
||||
|
||||
t.Run("invalid id returns 400", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "GET", "/api/residences/invalid/share-code/", nil, "test-token")
|
||||
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResidenceHandler_GenerateSharePackage(t *testing.T) {
|
||||
handler, e, db := setupResidenceHandler(t)
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Package Test")
|
||||
|
||||
authGroup := e.Group("/api/residences")
|
||||
authGroup.Use(testutil.MockAuthMiddleware(user))
|
||||
authGroup.POST("/:id/generate-share-package/", handler.GenerateSharePackage)
|
||||
|
||||
t.Run("generate share package", func(t *testing.T) {
|
||||
w := testutil.MakeRequest(e, "POST", fmt.Sprintf("/api/residences/%d/generate-share-package/", residence.ID), nil, "test-token")
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, response, "share_code")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user