Replace status_id with in_progress boolean field
- Remove task_statuses lookup table and StatusID foreign key - Add InProgress boolean field to Task model - Add database migration (005_replace_status_with_in_progress) - Update all handlers, services, and repositories - Update admin frontend to display in_progress as checkbox/boolean - Remove Task Statuses tab from admin lookups page - Update tests to use InProgress instead of StatusID - Task categorization now uses InProgress for kanban column assignment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -125,7 +125,6 @@ func setupIntegrationTest(t *testing.T) *TestApp {
|
||||
|
||||
api.GET("/task-categories", taskHandler.GetCategories)
|
||||
api.GET("/task-priorities", taskHandler.GetPriorities)
|
||||
api.GET("/task-statuses", taskHandler.GetStatuses)
|
||||
api.GET("/task-frequencies", taskHandler.GetFrequencies)
|
||||
}
|
||||
|
||||
@@ -334,10 +333,11 @@ func TestIntegration_ResidenceFlow(t *testing.T) {
|
||||
var createResp map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &createResp)
|
||||
require.NoError(t, err)
|
||||
residenceID := createResp["id"].(float64)
|
||||
createData := createResp["data"].(map[string]interface{})
|
||||
residenceID := createData["id"].(float64)
|
||||
assert.NotZero(t, residenceID)
|
||||
assert.Equal(t, "My House", createResp["name"])
|
||||
assert.True(t, createResp["is_primary"].(bool))
|
||||
assert.Equal(t, "My House", createData["name"])
|
||||
assert.True(t, createData["is_primary"].(bool))
|
||||
|
||||
// 2. Get the residence
|
||||
w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, token)
|
||||
@@ -368,8 +368,9 @@ func TestIntegration_ResidenceFlow(t *testing.T) {
|
||||
var updateResp map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &updateResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "My Updated House", updateResp["name"])
|
||||
assert.Equal(t, "Dallas", updateResp["city"])
|
||||
updateData := updateResp["data"].(map[string]interface{})
|
||||
assert.Equal(t, "My Updated House", updateData["name"])
|
||||
assert.Equal(t, "Dallas", updateData["city"])
|
||||
|
||||
// 5. Delete the residence (returns 200 with message, not 204)
|
||||
w = app.makeAuthenticatedRequest(t, "DELETE", "/api/residences/"+formatID(residenceID), nil, token)
|
||||
@@ -396,7 +397,8 @@ func TestIntegration_ResidenceSharingFlow(t *testing.T) {
|
||||
|
||||
var createResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &createResp)
|
||||
residenceID := createResp["id"].(float64)
|
||||
createData := createResp["data"].(map[string]interface{})
|
||||
residenceID := createData["id"].(float64)
|
||||
|
||||
// Other user cannot access initially
|
||||
w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, userToken)
|
||||
@@ -448,7 +450,8 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var residenceResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &residenceResp)
|
||||
residenceID := uint(residenceResp["id"].(float64))
|
||||
residenceData := residenceResp["data"].(map[string]interface{})
|
||||
residenceID := uint(residenceData["id"].(float64))
|
||||
|
||||
// 1. Create a task
|
||||
taskBody := map[string]interface{}{
|
||||
@@ -461,9 +464,10 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var taskResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &taskResp)
|
||||
taskID := taskResp["id"].(float64)
|
||||
taskData := taskResp["data"].(map[string]interface{})
|
||||
taskID := taskData["id"].(float64)
|
||||
assert.NotZero(t, taskID)
|
||||
assert.Equal(t, "Fix leaky faucet", taskResp["title"])
|
||||
assert.Equal(t, "Fix leaky faucet", taskData["title"])
|
||||
|
||||
// 2. Get the task
|
||||
w = app.makeAuthenticatedRequest(t, "GET", "/api/tasks/"+formatID(taskID), nil, token)
|
||||
@@ -477,9 +481,10 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
w = app.makeAuthenticatedRequest(t, "PUT", "/api/tasks/"+formatID(taskID), updateBody, token)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var updateResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &updateResp)
|
||||
assert.Equal(t, "Fix kitchen faucet", updateResp["title"])
|
||||
var taskUpdateResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &taskUpdateResp)
|
||||
taskUpdateData := taskUpdateResp["data"].(map[string]interface{})
|
||||
assert.Equal(t, "Fix kitchen faucet", taskUpdateData["title"])
|
||||
|
||||
// 4. Mark as in progress
|
||||
w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/mark-in-progress", nil, token)
|
||||
@@ -487,9 +492,8 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var progressResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &progressResp)
|
||||
task := progressResp["task"].(map[string]interface{})
|
||||
status := task["status"].(map[string]interface{})
|
||||
assert.Equal(t, "In Progress", status["name"])
|
||||
progressData := progressResp["data"].(map[string]interface{})
|
||||
assert.True(t, progressData["in_progress"].(bool))
|
||||
|
||||
// 5. Complete the task
|
||||
completionBody := map[string]interface{}{
|
||||
@@ -501,9 +505,10 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var completionResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &completionResp)
|
||||
completionID := completionResp["id"].(float64)
|
||||
completionData := completionResp["data"].(map[string]interface{})
|
||||
completionID := completionData["id"].(float64)
|
||||
assert.NotZero(t, completionID)
|
||||
assert.Equal(t, "Fixed the faucet", completionResp["notes"])
|
||||
assert.Equal(t, "Fixed the faucet", completionData["notes"])
|
||||
|
||||
// 6. List completions
|
||||
w = app.makeAuthenticatedRequest(t, "GET", "/api/completions?task_id="+formatID(taskID), nil, token)
|
||||
@@ -515,8 +520,8 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var archiveResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &archiveResp)
|
||||
archivedTask := archiveResp["task"].(map[string]interface{})
|
||||
assert.True(t, archivedTask["is_archived"].(bool))
|
||||
archivedData := archiveResp["data"].(map[string]interface{})
|
||||
assert.True(t, archivedData["is_archived"].(bool))
|
||||
|
||||
// 8. Unarchive the task
|
||||
w = app.makeAuthenticatedRequest(t, "POST", "/api/tasks/"+formatID(taskID)+"/unarchive", nil, token)
|
||||
@@ -528,8 +533,8 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
var cancelResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &cancelResp)
|
||||
cancelledTask := cancelResp["task"].(map[string]interface{})
|
||||
assert.True(t, cancelledTask["is_cancelled"].(bool))
|
||||
cancelledData := cancelResp["data"].(map[string]interface{})
|
||||
assert.True(t, cancelledData["is_cancelled"].(bool))
|
||||
|
||||
// 10. Delete the task (returns 200 with message, not 204)
|
||||
w = app.makeAuthenticatedRequest(t, "DELETE", "/api/tasks/"+formatID(taskID), nil, token)
|
||||
@@ -547,7 +552,8 @@ func TestIntegration_TasksByResidenceKanban(t *testing.T) {
|
||||
|
||||
var residenceResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &residenceResp)
|
||||
residenceID := uint(residenceResp["id"].(float64))
|
||||
residenceData := residenceResp["data"].(map[string]interface{})
|
||||
residenceID := uint(residenceData["id"].(float64))
|
||||
|
||||
// Create multiple tasks
|
||||
for i := 1; i <= 3; i++ {
|
||||
@@ -592,7 +598,6 @@ func TestIntegration_LookupEndpoints(t *testing.T) {
|
||||
{"residence types", "/api/residence-types"},
|
||||
{"task categories", "/api/task-categories"},
|
||||
{"task priorities", "/api/task-priorities"},
|
||||
{"task statuses", "/api/task-statuses"},
|
||||
{"task frequencies", "/api/task-frequencies"},
|
||||
}
|
||||
|
||||
@@ -633,7 +638,8 @@ func TestIntegration_CrossUserAccessDenied(t *testing.T) {
|
||||
|
||||
var residenceResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &residenceResp)
|
||||
residenceID := residenceResp["id"].(float64)
|
||||
residenceData := residenceResp["data"].(map[string]interface{})
|
||||
residenceID := residenceData["id"].(float64)
|
||||
|
||||
// User1 creates a task
|
||||
taskBody := map[string]interface{}{
|
||||
@@ -645,7 +651,8 @@ func TestIntegration_CrossUserAccessDenied(t *testing.T) {
|
||||
|
||||
var taskResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &taskResp)
|
||||
taskID := taskResp["id"].(float64)
|
||||
taskData := taskResp["data"].(map[string]interface{})
|
||||
taskID := taskData["id"].(float64)
|
||||
|
||||
// User2 cannot access User1's residence
|
||||
w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceID), nil, user2Token)
|
||||
@@ -693,7 +700,12 @@ func TestIntegration_ResponseStructure(t *testing.T) {
|
||||
var resp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
// Verify all expected fields are present
|
||||
// Response is wrapped with "data" and "summary"
|
||||
data := resp["data"].(map[string]interface{})
|
||||
_, hasSummary := resp["summary"]
|
||||
assert.True(t, hasSummary, "Expected 'summary' field in response")
|
||||
|
||||
// Verify all expected fields are present in data
|
||||
expectedFields := []string{
|
||||
"id", "owner_id", "name", "street_address", "city",
|
||||
"state_province", "postal_code", "country",
|
||||
@@ -701,13 +713,13 @@ func TestIntegration_ResponseStructure(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, field := range expectedFields {
|
||||
_, exists := resp[field]
|
||||
assert.True(t, exists, "Expected field %s to be present", field)
|
||||
_, exists := data[field]
|
||||
assert.True(t, exists, "Expected field %s to be present in data", field)
|
||||
}
|
||||
|
||||
// Check that nullable fields can be null
|
||||
assert.Nil(t, resp["bedrooms"])
|
||||
assert.Nil(t, resp["bathrooms"])
|
||||
assert.Nil(t, data["bedrooms"])
|
||||
assert.Nil(t, data["bathrooms"])
|
||||
}
|
||||
|
||||
// ============ Helper Functions ============
|
||||
|
||||
Reference in New Issue
Block a user