- Add 13 tests for task kanban categorization and button types - Fix i18n initialization in test setup (was causing nil pointer panics) - Add TaskCompletionImage to test DB auto-migrate - Update ListTasks tests to expect kanban board response instead of array 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
466 lines
16 KiB
Go
466 lines
16 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"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/testutil"
|
|
)
|
|
|
|
func setupTaskService(t *testing.T) (*TaskService, *repositories.TaskRepository, *repositories.ResidenceRepository) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
return service, taskRepo, residenceRepo
|
|
}
|
|
|
|
func TestTaskService_CreateTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
|
|
req := &requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Fix leaky faucet",
|
|
Description: "Kitchen faucet is dripping",
|
|
}
|
|
|
|
resp, err := service.CreateTask(req, user.ID)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, resp.ID)
|
|
assert.Equal(t, "Fix leaky faucet", resp.Title)
|
|
assert.Equal(t, "Kitchen faucet is dripping", resp.Description)
|
|
}
|
|
|
|
func TestTaskService_CreateTask_WithOptionalFields(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
|
|
|
// Get category and priority IDs
|
|
var category models.TaskCategory
|
|
var priority models.TaskPriority
|
|
db.First(&category)
|
|
db.First(&priority)
|
|
|
|
dueDate := requests.FlexibleDate{Time: time.Now().Add(7 * 24 * time.Hour).UTC()}
|
|
cost := decimal.NewFromFloat(150.50)
|
|
|
|
req := &requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Fix leaky faucet",
|
|
CategoryID: &category.ID,
|
|
PriorityID: &priority.ID,
|
|
DueDate: &dueDate,
|
|
EstimatedCost: &cost,
|
|
}
|
|
|
|
resp, err := service.CreateTask(req, user.ID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp.Category)
|
|
assert.NotNil(t, resp.Priority)
|
|
assert.NotNil(t, resp.DueDate)
|
|
assert.NotNil(t, resp.EstimatedCost)
|
|
}
|
|
|
|
func TestTaskService_CreateTask_AccessDenied(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
|
|
|
req := &requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Test Task",
|
|
}
|
|
|
|
_, err := service.CreateTask(req, otherUser.ID)
|
|
// When creating a task, residence access is checked first
|
|
assert.ErrorIs(t, err, ErrResidenceAccessDenied)
|
|
}
|
|
|
|
func TestTaskService_GetTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
resp, err := service.GetTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, task.ID, resp.ID)
|
|
assert.Equal(t, "Test Task", resp.Title)
|
|
}
|
|
|
|
func TestTaskService_GetTask_AccessDenied(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
|
task := testutil.CreateTestTask(t, db, residence.ID, owner.ID, "Test Task")
|
|
|
|
_, err := service.GetTask(task.ID, otherUser.ID)
|
|
assert.ErrorIs(t, err, ErrTaskAccessDenied)
|
|
}
|
|
|
|
func TestTaskService_ListTasks(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
resp, err := service.ListTasks(user.ID)
|
|
require.NoError(t, err)
|
|
// ListTasks returns a KanbanBoardResponse with columns
|
|
// Count total tasks across all columns
|
|
totalTasks := 0
|
|
for _, col := range resp.Columns {
|
|
totalTasks += col.Count
|
|
}
|
|
assert.Equal(t, 3, totalTasks)
|
|
}
|
|
|
|
func TestTaskService_UpdateTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
newTitle := "Updated Title"
|
|
newDesc := "Updated description"
|
|
req := &requests.UpdateTaskRequest{
|
|
Title: &newTitle,
|
|
Description: &newDesc,
|
|
}
|
|
|
|
resp, err := service.UpdateTask(task.ID, user.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Updated Title", resp.Title)
|
|
assert.Equal(t, "Updated description", resp.Description)
|
|
}
|
|
|
|
func TestTaskService_DeleteTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
err := service.DeleteTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = service.GetTask(task.ID, user.ID)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestTaskService_CancelTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
resp, err := service.CancelTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, resp.IsCancelled)
|
|
}
|
|
|
|
func TestTaskService_CancelTask_AlreadyCancelled(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
service.CancelTask(task.ID, user.ID)
|
|
_, err := service.CancelTask(task.ID, user.ID)
|
|
assert.ErrorIs(t, err, ErrTaskAlreadyCancelled)
|
|
}
|
|
|
|
func TestTaskService_UncancelTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
service.CancelTask(task.ID, user.ID)
|
|
resp, err := service.UncancelTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, resp.IsCancelled)
|
|
}
|
|
|
|
func TestTaskService_ArchiveTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
resp, err := service.ArchiveTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.True(t, resp.IsArchived)
|
|
}
|
|
|
|
func TestTaskService_UnarchiveTask(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
service.ArchiveTask(task.ID, user.ID)
|
|
resp, err := service.UnarchiveTask(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.False(t, resp.IsArchived)
|
|
}
|
|
|
|
func TestTaskService_MarkInProgress(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
resp, err := service.MarkInProgress(task.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp.Status)
|
|
assert.Equal(t, "In Progress", resp.Status.Name)
|
|
}
|
|
|
|
func TestTaskService_CreateCompletion(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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")
|
|
|
|
req := &requests.CreateTaskCompletionRequest{
|
|
TaskID: task.ID,
|
|
Notes: "Completed successfully",
|
|
}
|
|
|
|
resp, err := service.CreateCompletion(req, user.ID)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, resp.ID)
|
|
assert.Equal(t, task.ID, resp.TaskID)
|
|
assert.Equal(t, "Completed successfully", resp.Notes)
|
|
}
|
|
|
|
func TestTaskService_GetCompletion(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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 notes",
|
|
}
|
|
db.Create(completion)
|
|
|
|
resp, err := service.GetCompletion(completion.ID, user.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, completion.ID, resp.ID)
|
|
assert.Equal(t, "Test notes", resp.Notes)
|
|
}
|
|
|
|
func TestTaskService_DeleteCompletion(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
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)
|
|
|
|
err := service.DeleteCompletion(completion.ID, user.ID)
|
|
require.NoError(t, err)
|
|
|
|
_, err = service.GetCompletion(completion.ID, user.ID)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestTaskService_GetCategories(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
categories, err := service.GetCategories()
|
|
require.NoError(t, err)
|
|
assert.Greater(t, len(categories), 0)
|
|
|
|
// Check JSON structure
|
|
for _, cat := range categories {
|
|
assert.NotZero(t, cat.ID)
|
|
assert.NotEmpty(t, cat.Name)
|
|
}
|
|
}
|
|
|
|
func TestTaskService_GetPriorities(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
priorities, err := service.GetPriorities()
|
|
require.NoError(t, err)
|
|
assert.Greater(t, len(priorities), 0)
|
|
|
|
// Check order by level
|
|
for i := 1; i < len(priorities); i++ {
|
|
assert.GreaterOrEqual(t, priorities[i].Level, priorities[i-1].Level)
|
|
}
|
|
}
|
|
|
|
func TestTaskService_GetStatuses(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
statuses, err := service.GetStatuses()
|
|
require.NoError(t, err)
|
|
assert.Greater(t, len(statuses), 0)
|
|
}
|
|
|
|
func TestTaskService_GetFrequencies(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
frequencies, err := service.GetFrequencies()
|
|
require.NoError(t, err)
|
|
assert.Greater(t, len(frequencies), 0)
|
|
}
|
|
|
|
func TestTaskService_SharedUserAccess(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
testutil.SeedLookupData(t, db)
|
|
taskRepo := repositories.NewTaskRepository(db)
|
|
residenceRepo := repositories.NewResidenceRepository(db)
|
|
service := NewTaskService(taskRepo, residenceRepo)
|
|
|
|
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
|
|
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
|
|
|
// Share residence
|
|
residenceRepo.AddUser(residence.ID, sharedUser.ID)
|
|
|
|
// Create task as owner
|
|
task := testutil.CreateTestTask(t, db, residence.ID, owner.ID, "Test Task")
|
|
|
|
// Shared user should be able to see the task
|
|
resp, err := service.GetTask(task.ID, sharedUser.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, task.ID, resp.ID)
|
|
|
|
// Shared user should be able to create tasks
|
|
req := &requests.CreateTaskRequest{
|
|
ResidenceID: residence.ID,
|
|
Title: "Shared User Task",
|
|
}
|
|
_, err = service.CreateTask(req, sharedUser.ID)
|
|
require.NoError(t, err)
|
|
}
|