- Update Go module from mycrib-api to casera-api - Update all import statements across 69 Go files - Update admin panel branding (title, sidebar, login form) - Update email templates (subjects, bodies, signatures) - Update PDF report generation branding - Update Docker container names and network - Update config defaults (database name, email sender, APNS topic) - Update README and documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
667 lines
23 KiB
Go
667 lines
23 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/shopspring/decimal"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/treytartt/casera-api/internal/dto/requests"
|
|
"github.com/treytartt/casera-api/internal/models"
|
|
"github.com/treytartt/casera-api/internal/repositories"
|
|
"github.com/treytartt/casera-api/internal/services"
|
|
"github.com/treytartt/casera-api/internal/testutil"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupTaskHandler(t *testing.T) (*TaskHandler, *gin.Engine, *gorm.DB) {
|
|
db := testutil.SetupTestDB(t)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
taskService := services.NewTaskService(taskRepo, residenceRepo)
|
|
handler := NewTaskHandler(taskService, nil)
|
|
router := testutil.SetupTestRouter()
|
|
return handler, router, db
|
|
}
|
|
|
|
func TestTaskHandler_CreateTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
testutil.SeedLookupData(t, db)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/", handler.CreateTask)
|
|
|
|
t.Run("successful task creation", func(t *testing.T) {
|
|
req := requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Fix leaky faucet",
|
|
Description: "Kitchen faucet is dripping",
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "POST", "/api/tasks/", 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, "Fix leaky faucet", response["title"])
|
|
assert.Equal(t, "Kitchen faucet is dripping", response["description"])
|
|
assert.Equal(t, float64(residence.ID), response["residence_id"])
|
|
assert.Equal(t, false, response["is_cancelled"])
|
|
assert.Equal(t, false, response["is_archived"])
|
|
})
|
|
|
|
t.Run("task creation with optional fields", func(t *testing.T) {
|
|
var category models.TaskCategory
|
|
db.First(&category)
|
|
var priority models.TaskPriority
|
|
db.First(&priority)
|
|
|
|
dueDate := requests.FlexibleDate{Time: time.Now().AddDate(0, 0, 7)}
|
|
estimatedCost := decimal.NewFromFloat(150.50)
|
|
|
|
req := requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Install new lights",
|
|
Description: "Replace old light fixtures",
|
|
CategoryID: &category.ID,
|
|
PriorityID: &priority.ID,
|
|
DueDate: &dueDate,
|
|
EstimatedCost: &estimatedCost,
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "POST", "/api/tasks/", 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, "Install new lights", response["title"])
|
|
assert.NotNil(t, response["category"])
|
|
assert.NotNil(t, response["priority"])
|
|
assert.Equal(t, "150.5", response["estimated_cost"]) // Decimal serializes as string
|
|
})
|
|
|
|
t.Run("task creation without residence access", func(t *testing.T) {
|
|
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
|
|
otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other House")
|
|
|
|
req := requests.CreateTaskRequest{
|
|
ResidenceID: otherResidence.ID,
|
|
Title: "Unauthorized Task",
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "POST", "/api/tasks/", req, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_GetTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/:id/", handler.GetTask)
|
|
|
|
otherGroup := router.Group("/api/other-tasks")
|
|
otherGroup.Use(testutil.MockAuthMiddleware(otherUser))
|
|
otherGroup.GET("/:id/", handler.GetTask)
|
|
|
|
t.Run("get own task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", fmt.Sprintf("/api/tasks/%d/", task.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, "Test Task", response["title"])
|
|
assert.Equal(t, float64(task.ID), response["id"])
|
|
})
|
|
|
|
t.Run("get non-existent task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/9999/", nil, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusNotFound)
|
|
})
|
|
|
|
t.Run("access denied for other user", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", fmt.Sprintf("/api/other-tasks/%d/", task.ID), nil, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusForbidden)
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_ListTasks(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")
|
|
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2")
|
|
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 3")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/", handler.ListTasks)
|
|
|
|
t.Run("list tasks", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/", 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, 3)
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_GetTasksByResidence(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
testutil.SeedLookupData(t, db)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
|
|
// Create tasks with different states
|
|
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Active Task")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/by-residence/:residence_id/", handler.GetTasksByResidence)
|
|
|
|
t.Run("get kanban columns", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", fmt.Sprintf("/api/tasks/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.Contains(t, response, "columns")
|
|
assert.Contains(t, response, "days_threshold")
|
|
assert.Contains(t, response, "residence_id")
|
|
|
|
columns := response["columns"].([]interface{})
|
|
assert.Len(t, columns, 6) // 6 kanban columns
|
|
})
|
|
|
|
t.Run("kanban column structure", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", fmt.Sprintf("/api/tasks/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)
|
|
|
|
columns := response["columns"].([]interface{})
|
|
firstColumn := columns[0].(map[string]interface{})
|
|
|
|
// Verify column structure
|
|
assert.Contains(t, firstColumn, "name")
|
|
assert.Contains(t, firstColumn, "display_name")
|
|
assert.Contains(t, firstColumn, "tasks")
|
|
assert.Contains(t, firstColumn, "count")
|
|
assert.Contains(t, firstColumn, "color")
|
|
assert.Contains(t, firstColumn, "icons")
|
|
assert.Contains(t, firstColumn, "button_types")
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_UpdateTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Original Title")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.PUT("/:id/", handler.UpdateTask)
|
|
|
|
t.Run("update task", func(t *testing.T) {
|
|
newTitle := "Updated Title"
|
|
newDesc := "Updated description"
|
|
req := requests.UpdateTaskRequest{
|
|
Title: &newTitle,
|
|
Description: &newDesc,
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "PUT", fmt.Sprintf("/api/tasks/%d/", task.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"])
|
|
assert.Equal(t, "Updated description", response["description"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_DeleteTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Delete")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.DELETE("/:id/", handler.DeleteTask)
|
|
|
|
t.Run("delete task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "DELETE", fmt.Sprintf("/api/tasks/%d/", task.ID), nil, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusOK)
|
|
|
|
response := testutil.ParseJSON(t, w.Body.Bytes())
|
|
assert.Contains(t, response["message"], "deleted")
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_CancelTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Cancel")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/:id/cancel/", handler.CancelTask)
|
|
|
|
t.Run("cancel task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/cancel/", task.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"], "cancelled")
|
|
|
|
taskResp := response["task"].(map[string]interface{})
|
|
assert.Equal(t, true, taskResp["is_cancelled"])
|
|
})
|
|
|
|
t.Run("cancel already cancelled task", func(t *testing.T) {
|
|
// Already cancelled from previous test
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/cancel/", task.ID), nil, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusBadRequest)
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_UncancelTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Uncancel")
|
|
|
|
// Cancel first
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
taskRepo.Cancel(task.ID)
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/:id/uncancel/", handler.UncancelTask)
|
|
|
|
t.Run("uncancel task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/uncancel/", task.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)
|
|
|
|
taskResp := response["task"].(map[string]interface{})
|
|
assert.Equal(t, false, taskResp["is_cancelled"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_ArchiveTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Archive")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/:id/archive/", handler.ArchiveTask)
|
|
|
|
t.Run("archive task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/archive/", task.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)
|
|
|
|
taskResp := response["task"].(map[string]interface{})
|
|
assert.Equal(t, true, taskResp["is_archived"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_UnarchiveTask(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Unarchive")
|
|
|
|
// Archive first
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
taskRepo.Archive(task.ID)
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/:id/unarchive/", handler.UnarchiveTask)
|
|
|
|
t.Run("unarchive task", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/unarchive/", task.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)
|
|
|
|
taskResp := response["task"].(map[string]interface{})
|
|
assert.Equal(t, false, taskResp["is_archived"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_MarkInProgress(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
testutil.SeedLookupData(t, db)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Start")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/:id/mark-in-progress/", handler.MarkInProgress)
|
|
|
|
t.Run("mark in progress", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "POST", fmt.Sprintf("/api/tasks/%d/mark-in-progress/", task.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"], "in progress")
|
|
assert.NotNil(t, response["task"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_CreateCompletion(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "To Complete")
|
|
|
|
authGroup := router.Group("/api/task-completions")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/", handler.CreateCompletion)
|
|
|
|
t.Run("create completion", func(t *testing.T) {
|
|
completedAt := time.Now().UTC()
|
|
req := requests.CreateTaskCompletionRequest{
|
|
TaskID: task.ID,
|
|
CompletedAt: &completedAt,
|
|
Notes: "Completed successfully",
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "POST", "/api/task-completions/", 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)
|
|
|
|
testutil.AssertJSONFieldExists(t, response, "id")
|
|
assert.Equal(t, float64(task.ID), response["task_id"])
|
|
assert.Equal(t, "Completed successfully", response["notes"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_ListCompletions(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
|
|
|
// Create completions
|
|
for i := 0; i < 3; i++ {
|
|
db.Create(&models.TaskCompletion{
|
|
TaskID: task.ID,
|
|
CompletedByID: user.ID,
|
|
CompletedAt: time.Now().UTC(),
|
|
})
|
|
}
|
|
|
|
authGroup := router.Group("/api/task-completions")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/", handler.ListCompletions)
|
|
|
|
t.Run("list completions", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/task-completions/", 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, 3)
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_GetCompletion(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
|
|
|
completion := &models.TaskCompletion{
|
|
TaskID: task.ID,
|
|
CompletedByID: user.ID,
|
|
CompletedAt: time.Now().UTC(),
|
|
Notes: "Test completion",
|
|
}
|
|
db.Create(completion)
|
|
|
|
authGroup := router.Group("/api/task-completions")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/:id/", handler.GetCompletion)
|
|
|
|
t.Run("get completion", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", fmt.Sprintf("/api/task-completions/%d/", completion.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, float64(completion.ID), response["id"])
|
|
assert.Equal(t, "Test completion", response["notes"])
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_DeleteCompletion(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
|
|
|
completion := &models.TaskCompletion{
|
|
TaskID: task.ID,
|
|
CompletedByID: user.ID,
|
|
CompletedAt: time.Now().UTC(),
|
|
}
|
|
db.Create(completion)
|
|
|
|
authGroup := router.Group("/api/task-completions")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.DELETE("/:id/", handler.DeleteCompletion)
|
|
|
|
t.Run("delete completion", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "DELETE", fmt.Sprintf("/api/task-completions/%d/", completion.ID), nil, "test-token")
|
|
|
|
testutil.AssertStatusCode(t, w, http.StatusOK)
|
|
|
|
response := testutil.ParseJSON(t, w.Body.Bytes())
|
|
assert.Contains(t, response["message"], "deleted")
|
|
})
|
|
}
|
|
|
|
func TestTaskHandler_GetLookups(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
testutil.SeedLookupData(t, db)
|
|
user := testutil.CreateTestUser(t, db, "user", "user@test.com", "password")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.GET("/categories/", handler.GetCategories)
|
|
authGroup.GET("/priorities/", handler.GetPriorities)
|
|
authGroup.GET("/statuses/", handler.GetStatuses)
|
|
authGroup.GET("/frequencies/", handler.GetFrequencies)
|
|
|
|
t.Run("get categories", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/categories/", 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)
|
|
assert.Contains(t, response[0], "id")
|
|
assert.Contains(t, response[0], "name")
|
|
})
|
|
|
|
t.Run("get priorities", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/priorities/", 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)
|
|
assert.Contains(t, response[0], "id")
|
|
assert.Contains(t, response[0], "name")
|
|
assert.Contains(t, response[0], "level")
|
|
})
|
|
|
|
t.Run("get statuses", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/statuses/", 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)
|
|
})
|
|
|
|
t.Run("get frequencies", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/frequencies/", 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 TestTaskHandler_JSONResponses(t *testing.T) {
|
|
handler, router, db := setupTaskHandler(t)
|
|
testutil.SeedLookupData(t, db)
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
|
|
authGroup := router.Group("/api/tasks")
|
|
authGroup.Use(testutil.MockAuthMiddleware(user))
|
|
authGroup.POST("/", handler.CreateTask)
|
|
authGroup.GET("/", handler.ListTasks)
|
|
|
|
t.Run("task response has correct JSON structure", func(t *testing.T) {
|
|
req := requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "JSON Test Task",
|
|
Description: "Testing JSON structure",
|
|
}
|
|
|
|
w := testutil.MakeRequest(router, "POST", "/api/tasks/", 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)
|
|
|
|
// Required fields
|
|
assert.Contains(t, response, "id")
|
|
assert.Contains(t, response, "residence_id")
|
|
assert.Contains(t, response, "created_by_id")
|
|
assert.Contains(t, response, "title")
|
|
assert.Contains(t, response, "description")
|
|
assert.Contains(t, response, "is_cancelled")
|
|
assert.Contains(t, response, "is_archived")
|
|
assert.Contains(t, response, "created_at")
|
|
assert.Contains(t, response, "updated_at")
|
|
|
|
// Type checks
|
|
assert.IsType(t, float64(0), response["id"])
|
|
assert.IsType(t, "", response["title"])
|
|
assert.IsType(t, false, response["is_cancelled"])
|
|
assert.IsType(t, false, response["is_archived"])
|
|
})
|
|
|
|
t.Run("list response returns array", func(t *testing.T) {
|
|
w := testutil.MakeRequest(router, "GET", "/api/tasks/", 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)
|
|
|
|
// Response should be an array of tasks
|
|
assert.IsType(t, []map[string]interface{}{}, response)
|
|
})
|
|
}
|