Include TotalSummary in CRUD responses to eliminate redundant API calls
Backend changes: - Add WithSummaryResponse wrappers for Task, TaskCompletion, and Residence CRUD - Update services to return summary with all mutations (create, update, delete) - Update handlers to pass through new response types - Add getSummaryForUser helper for fetching summary in CRUD operations - Wire ResidenceService into TaskService for summary access - Add summary field to JoinResidenceResponse This optimization eliminates the need for a separate getSummary() call after every task/residence mutation, reducing network calls from 2 to 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -55,13 +55,18 @@ func TestResidenceHandler_CreateResidence(t *testing.T) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "My House", response["name"])
|
||||
assert.Equal(t, "123 Main St", response["street_address"])
|
||||
assert.Equal(t, "Austin", response["city"])
|
||||
assert.Equal(t, "TX", response["state_province"])
|
||||
assert.Equal(t, "78701", response["postal_code"])
|
||||
assert.Equal(t, "USA", response["country"]) // Default
|
||||
assert.Equal(t, true, response["is_primary"])
|
||||
// Response should be wrapped in WithSummaryResponse
|
||||
assert.Contains(t, response, "data")
|
||||
assert.Contains(t, response, "summary")
|
||||
|
||||
residenceData := response["data"].(map[string]interface{})
|
||||
assert.Equal(t, "My House", residenceData["name"])
|
||||
assert.Equal(t, "123 Main St", residenceData["street_address"])
|
||||
assert.Equal(t, "Austin", residenceData["city"])
|
||||
assert.Equal(t, "TX", residenceData["state_province"])
|
||||
assert.Equal(t, "78701", residenceData["postal_code"])
|
||||
assert.Equal(t, "USA", residenceData["country"]) // Default
|
||||
assert.Equal(t, true, residenceData["is_primary"])
|
||||
})
|
||||
|
||||
t.Run("creation with optional fields", func(t *testing.T) {
|
||||
@@ -91,11 +96,16 @@ func TestResidenceHandler_CreateResidence(t *testing.T) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, float64(3), response["bedrooms"])
|
||||
assert.Equal(t, "2.5", response["bathrooms"]) // Decimal serializes as string
|
||||
assert.Equal(t, float64(2000), response["square_footage"])
|
||||
// Response should be wrapped in WithSummaryResponse
|
||||
assert.Contains(t, response, "data")
|
||||
assert.Contains(t, response, "summary")
|
||||
|
||||
residenceData := response["data"].(map[string]interface{})
|
||||
assert.Equal(t, float64(3), residenceData["bedrooms"])
|
||||
assert.Equal(t, "2.5", residenceData["bathrooms"]) // Decimal serializes as string
|
||||
assert.Equal(t, float64(2000), residenceData["square_footage"])
|
||||
// Note: first residence becomes primary by default even if is_primary=false is specified
|
||||
assert.Contains(t, []interface{}{true, false}, response["is_primary"])
|
||||
assert.Contains(t, []interface{}{true, false}, residenceData["is_primary"])
|
||||
})
|
||||
|
||||
t.Run("creation with missing required fields", func(t *testing.T) {
|
||||
@@ -214,8 +224,13 @@ func TestResidenceHandler_UpdateResidence(t *testing.T) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "Updated Name", response["name"])
|
||||
assert.Equal(t, "Dallas", response["city"])
|
||||
// Response should be wrapped in WithSummaryResponse
|
||||
assert.Contains(t, response, "data")
|
||||
assert.Contains(t, response, "summary")
|
||||
|
||||
residenceData := response["data"].(map[string]interface{})
|
||||
assert.Equal(t, "Updated Name", residenceData["name"])
|
||||
assert.Equal(t, "Dallas", residenceData["city"])
|
||||
})
|
||||
|
||||
t.Run("shared user cannot update", func(t *testing.T) {
|
||||
@@ -258,8 +273,14 @@ func TestResidenceHandler_DeleteResidence(t *testing.T) {
|
||||
|
||||
testutil.AssertStatusCode(t, w, http.StatusOK)
|
||||
|
||||
response := testutil.ParseJSON(t, w.Body.Bytes())
|
||||
assert.Contains(t, response["message"], "deleted")
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Response should be wrapped in WithSummaryResponse
|
||||
assert.Contains(t, response, "data")
|
||||
assert.Contains(t, response, "summary")
|
||||
assert.Contains(t, response["data"], "deleted")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -326,6 +347,10 @@ func TestResidenceHandler_JoinWithCode(t *testing.T) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// JoinResidenceResponse includes summary
|
||||
assert.Contains(t, response, "residence")
|
||||
assert.Contains(t, response, "summary")
|
||||
|
||||
residenceResp := response["residence"].(map[string]interface{})
|
||||
assert.Equal(t, "Join Test", residenceResp["name"])
|
||||
})
|
||||
@@ -457,23 +482,34 @@ func TestResidenceHandler_JSONResponses(t *testing.T) {
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Required fields
|
||||
assert.Contains(t, response, "id")
|
||||
assert.Contains(t, response, "name")
|
||||
assert.Contains(t, response, "street_address")
|
||||
assert.Contains(t, response, "city")
|
||||
assert.Contains(t, response, "state_province")
|
||||
assert.Contains(t, response, "postal_code")
|
||||
assert.Contains(t, response, "country")
|
||||
assert.Contains(t, response, "is_primary")
|
||||
assert.Contains(t, response, "is_active")
|
||||
assert.Contains(t, response, "created_at")
|
||||
assert.Contains(t, response, "updated_at")
|
||||
// Response should be wrapped in WithSummaryResponse
|
||||
assert.Contains(t, response, "data")
|
||||
assert.Contains(t, response, "summary")
|
||||
|
||||
residenceData := response["data"].(map[string]interface{})
|
||||
|
||||
// Required fields in residence data
|
||||
assert.Contains(t, residenceData, "id")
|
||||
assert.Contains(t, residenceData, "name")
|
||||
assert.Contains(t, residenceData, "street_address")
|
||||
assert.Contains(t, residenceData, "city")
|
||||
assert.Contains(t, residenceData, "state_province")
|
||||
assert.Contains(t, residenceData, "postal_code")
|
||||
assert.Contains(t, residenceData, "country")
|
||||
assert.Contains(t, residenceData, "is_primary")
|
||||
assert.Contains(t, residenceData, "is_active")
|
||||
assert.Contains(t, residenceData, "created_at")
|
||||
assert.Contains(t, residenceData, "updated_at")
|
||||
|
||||
// Type checks
|
||||
assert.IsType(t, float64(0), response["id"])
|
||||
assert.IsType(t, "", response["name"])
|
||||
assert.IsType(t, true, response["is_primary"])
|
||||
assert.IsType(t, float64(0), residenceData["id"])
|
||||
assert.IsType(t, "", residenceData["name"])
|
||||
assert.IsType(t, true, residenceData["is_primary"])
|
||||
|
||||
// Summary should have expected fields
|
||||
summary := response["summary"].(map[string]interface{})
|
||||
assert.Contains(t, summary, "total_residences")
|
||||
assert.Contains(t, summary, "total_tasks")
|
||||
})
|
||||
|
||||
t.Run("list response returns array", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user