Fix Close/Invalidate/Reopen to handle API array response

The Checkvist API returns an array of tasks (containing the modified task
and potentially its subtasks) for close, reopen, and invalidate operations.
The code was incorrectly trying to decode into a single Task struct.

Changes:
- Decode response into []Task instead of Task
- Return first element (the modified task)
- Add defensive error handling for empty arrays
- Update tests to mock array responses

Fixes: checkvist-api-2zr
This commit is contained in:
Oliver Jakoubek 2026-01-15 10:48:38 +01:00
commit b716d4d0fe
3 changed files with 37 additions and 18 deletions

View file

@ -201,42 +201,57 @@ func (s *TaskService) Delete(ctx context.Context, taskID int) error {
}
// Close marks a task as completed.
// The API returns an array containing the modified task and potentially its subtasks.
func (s *TaskService) Close(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/close.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
var tasks []Task
if err := s.client.doPost(ctx, path, nil, &tasks); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
if len(tasks) == 0 {
return nil, fmt.Errorf("close task: unexpected empty response")
}
parseDueDate(&tasks[0])
return &tasks[0], nil
}
// Reopen reopens a closed or invalidated task.
// The API returns an array containing the modified task and potentially its subtasks.
func (s *TaskService) Reopen(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/reopen.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
var tasks []Task
if err := s.client.doPost(ctx, path, nil, &tasks); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
if len(tasks) == 0 {
return nil, fmt.Errorf("reopen task: unexpected empty response")
}
parseDueDate(&tasks[0])
return &tasks[0], nil
}
// Invalidate marks a task as invalidated (strikethrough).
// The API returns an array containing the modified task and potentially its subtasks.
func (s *TaskService) Invalidate(ctx context.Context, taskID int) (*Task, error) {
path := fmt.Sprintf("/checklists/%d/tasks/%d/invalidate.json", s.checklistID, taskID)
var task Task
if err := s.client.doPost(ctx, path, nil, &task); err != nil {
var tasks []Task
if err := s.client.doPost(ctx, path, nil, &tasks); err != nil {
return nil, err
}
parseDueDate(&task)
return &task, nil
if len(tasks) == 0 {
return nil, fmt.Errorf("invalidate task: unexpected empty response")
}
parseDueDate(&tasks[0])
return &tasks[0], nil
}
// parseDueDate attempts to parse the DueDateRaw string into a time.Time.