Implement Task API methods (CRUD operations)
- GetTask: retrieve single task by ID - GetAllTasks: retrieve all tasks for a project with status filter - CreateTask: create new task and return the created task - UpdateTask: partial updates via pointer fields - CloseTask/OpenTask: status operations with proper error handling - Returns ErrTaskNotFound, ErrTaskClosed, ErrTaskOpen as appropriate - Comprehensive test coverage for all methods and edge cases
This commit is contained in:
parent
fc4577e729
commit
d27aabe4c4
3 changed files with 613 additions and 1 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
{"id":"kanboard-api-3dc","title":"Implement Link API methods","description":"Implement direct API methods for task link operations.\n\n## Methods to implement\n- GetAllTaskLinks(ctx, taskID int) ([]TaskLink, error) - getAllTaskLinks\n- CreateTaskLink(ctx, taskID, oppositeTaskID, linkID int) (int, error) - createTaskLink\n- RemoveTaskLink(ctx, taskLinkID int) error - removeTaskLink (Nice-to-have)\n\n## TaskScope methods to add\n- GetLinks(ctx) ([]TaskLink, error)\n- LinkTo(ctx, oppositeTaskID, linkID int) error\n\n## Files to create\n- links.go\n- task_scope.go (extend)\n\n## Acceptance criteria\n- CreateTaskLink returns the link ID\n- Links include related task information","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:09.328552773+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:09.328552773+01:00","dependencies":[{"issue_id":"kanboard-api-3dc","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:43:49.785710003+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-3dc","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:49.886111429+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-3dc","title":"Implement Link API methods","description":"Implement direct API methods for task link operations.\n\n## Methods to implement\n- GetAllTaskLinks(ctx, taskID int) ([]TaskLink, error) - getAllTaskLinks\n- CreateTaskLink(ctx, taskID, oppositeTaskID, linkID int) (int, error) - createTaskLink\n- RemoveTaskLink(ctx, taskLinkID int) error - removeTaskLink (Nice-to-have)\n\n## TaskScope methods to add\n- GetLinks(ctx) ([]TaskLink, error)\n- LinkTo(ctx, oppositeTaskID, linkID int) error\n\n## Files to create\n- links.go\n- task_scope.go (extend)\n\n## Acceptance criteria\n- CreateTaskLink returns the link ID\n- Links include related task information","status":"open","priority":2,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:09.328552773+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:09.328552773+01:00","dependencies":[{"issue_id":"kanboard-api-3dc","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:43:49.785710003+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-3dc","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:49.886111429+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-5fb","title":"Implement TaskUpdateParams builder","description":"Implement TaskUpdateParams for fluent task update configuration.\n\n## Requirements\n- TaskUpdateParams struct with pointer fields\n- NewTaskUpdate() *TaskUpdateParams constructor\n- Fluent setter methods:\n - SetTitle(title string) *TaskUpdateParams\n - SetDescription(desc string) *TaskUpdateParams\n - SetColor(colorID string) *TaskUpdateParams\n - SetOwner(ownerID int) *TaskUpdateParams\n - SetCategory(categoryID int) *TaskUpdateParams\n - SetPriority(priority int) *TaskUpdateParams\n - SetDueDate(date time.Time) *TaskUpdateParams\n- Internal method to convert to UpdateTaskRequest\n\n## Files to create\n- task_update_params.go\n\n## Example usage\n```go\nparams := kanboard.NewTaskUpdate().\n SetTitle(\"New Title\").\n SetPriority(2)\n```\n\n## Acceptance criteria\n- Only set fields are included in update request\n- All setters return *TaskUpdateParams for chaining","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:40.814955926+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:35:40.814955926+01:00","dependencies":[{"issue_id":"kanboard-api-5fb","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:31.134402453+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-5fb","title":"Implement TaskUpdateParams builder","description":"Implement TaskUpdateParams for fluent task update configuration.\n\n## Requirements\n- TaskUpdateParams struct with pointer fields\n- NewTaskUpdate() *TaskUpdateParams constructor\n- Fluent setter methods:\n - SetTitle(title string) *TaskUpdateParams\n - SetDescription(desc string) *TaskUpdateParams\n - SetColor(colorID string) *TaskUpdateParams\n - SetOwner(ownerID int) *TaskUpdateParams\n - SetCategory(categoryID int) *TaskUpdateParams\n - SetPriority(priority int) *TaskUpdateParams\n - SetDueDate(date time.Time) *TaskUpdateParams\n- Internal method to convert to UpdateTaskRequest\n\n## Files to create\n- task_update_params.go\n\n## Example usage\n```go\nparams := kanboard.NewTaskUpdate().\n SetTitle(\"New Title\").\n SetPriority(2)\n```\n\n## Acceptance criteria\n- Only set fields are included in update request\n- All setters return *TaskUpdateParams for chaining","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:40.814955926+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:35:40.814955926+01:00","dependencies":[{"issue_id":"kanboard-api-5fb","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:31.134402453+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-70e","title":"Add GoDoc documentation to all public APIs","description":"Add comprehensive GoDoc comments to all exported types and functions.\n\n## Requirements\n- Package-level documentation in doc.go\n- All exported types documented\n- All exported functions documented with examples\n- All exported constants/variables documented\n\n## Documentation standards\n- Start with the name being documented\n- Include usage examples where helpful\n- Document error conditions\n- Note thread-safety characteristics\n\n## Files to create/modify\n- doc.go (package documentation)\n- All .go files with exported symbols\n\n## Acceptance criteria\n- `go doc` produces clean output\n- All exports have documentation\n- Examples are runnable","status":"open","priority":2,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:54.342657373+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:54.342657373+01:00","dependencies":[{"issue_id":"kanboard-api-70e","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:46:55.508211397+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-70e","title":"Add GoDoc documentation to all public APIs","description":"Add comprehensive GoDoc comments to all exported types and functions.\n\n## Requirements\n- Package-level documentation in doc.go\n- All exported types documented\n- All exported functions documented with examples\n- All exported constants/variables documented\n\n## Documentation standards\n- Start with the name being documented\n- Include usage examples where helpful\n- Document error conditions\n- Note thread-safety characteristics\n\n## Files to create/modify\n- doc.go (package documentation)\n- All .go files with exported symbols\n\n## Acceptance criteria\n- `go doc` produces clean output\n- All exports have documentation\n- Examples are runnable","status":"open","priority":2,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:54.342657373+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:54.342657373+01:00","dependencies":[{"issue_id":"kanboard-api-70e","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:46:55.508211397+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-91a","title":"Implement Task API methods (CRUD)","description":"Implement direct API methods for core task operations.\n\n## Methods to implement\n- GetTask(ctx, taskID int) (*Task, error) - getTask\n- GetAllTasks(ctx, projectID int, status TaskStatus) ([]Task, error) - getAllTasks\n- CreateTask(ctx, req CreateTaskRequest) (*Task, error) - createTask\n- UpdateTask(ctx, req UpdateTaskRequest) error - updateTask\n- CloseTask(ctx, taskID int) error - closeTask\n- OpenTask(ctx, taskID int) error - openTask\n\n## Files to create\n- tasks.go\n\n## Acceptance criteria\n- CreateTask returns the created task\n- UpdateTask supports partial updates via pointer fields\n- Proper error types (ErrTaskNotFound, ErrTaskClosed, etc.)","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:16.9962543+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:35:16.9962543+01:00","dependencies":[{"issue_id":"kanboard-api-91a","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:42:53.291442735+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-91a","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:42:53.434459365+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-91a","title":"Implement Task API methods (CRUD)","description":"Implement direct API methods for core task operations.\n\n## Methods to implement\n- GetTask(ctx, taskID int) (*Task, error) - getTask\n- GetAllTasks(ctx, projectID int, status TaskStatus) ([]Task, error) - getAllTasks\n- CreateTask(ctx, req CreateTaskRequest) (*Task, error) - createTask\n- UpdateTask(ctx, req UpdateTaskRequest) error - updateTask\n- CloseTask(ctx, taskID int) error - closeTask\n- OpenTask(ctx, taskID int) error - openTask\n\n## Files to create\n- tasks.go\n\n## Acceptance criteria\n- CreateTask returns the created task\n- UpdateTask supports partial updates via pointer fields\n- Proper error types (ErrTaskNotFound, ErrTaskClosed, etc.)","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:16.9962543+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T18:26:49.591860359+01:00","closed_at":"2026-01-15T18:26:49.591860359+01:00","close_reason":"Closed","dependencies":[{"issue_id":"kanboard-api-91a","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:42:53.291442735+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-91a","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:42:53.434459365+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-9k8","title":"Implement TaskParams builder (Options Pattern)","description":"Implement TaskParams for fluent task creation configuration.\n\n## Requirements\n- TaskParams struct with private fields\n- NewTask(title string) *TaskParams constructor\n- Fluent setter methods:\n - WithDescription(desc string) *TaskParams\n - InColumn(columnID int) *TaskParams\n - WithCategory(categoryID int) *TaskParams\n - WithOwner(ownerID int) *TaskParams\n - WithColor(colorID string) *TaskParams\n - WithPriority(priority int) *TaskParams\n - WithDueDate(date time.Time) *TaskParams\n - WithTags(tags ...string) *TaskParams\n - WithReference(ref string) *TaskParams\n- Internal method to convert to CreateTaskRequest\n\n## Files to create\n- task_params.go\n\n## Example usage\n```go\nparams := kanboard.NewTask(\"My Task\").\n WithDescription(\"Details\").\n InColumn(2).\n WithTags(\"urgent\", \"backend\")\n```\n\n## Acceptance criteria\n- Pure configuration object (no I/O)\n- All setters return *TaskParams for chaining\n- Unset optional fields remain nil","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:40.439513879+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:35:40.439513879+01:00","dependencies":[{"issue_id":"kanboard-api-9k8","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:31.070100884+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-9k8","title":"Implement TaskParams builder (Options Pattern)","description":"Implement TaskParams for fluent task creation configuration.\n\n## Requirements\n- TaskParams struct with private fields\n- NewTask(title string) *TaskParams constructor\n- Fluent setter methods:\n - WithDescription(desc string) *TaskParams\n - InColumn(columnID int) *TaskParams\n - WithCategory(categoryID int) *TaskParams\n - WithOwner(ownerID int) *TaskParams\n - WithColor(colorID string) *TaskParams\n - WithPriority(priority int) *TaskParams\n - WithDueDate(date time.Time) *TaskParams\n - WithTags(tags ...string) *TaskParams\n - WithReference(ref string) *TaskParams\n- Internal method to convert to CreateTaskRequest\n\n## Files to create\n- task_params.go\n\n## Example usage\n```go\nparams := kanboard.NewTask(\"My Task\").\n WithDescription(\"Details\").\n InColumn(2).\n WithTags(\"urgent\", \"backend\")\n```\n\n## Acceptance criteria\n- Pure configuration object (no I/O)\n- All setters return *TaskParams for chaining\n- Unset optional fields remain nil","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:40.439513879+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:35:40.439513879+01:00","dependencies":[{"issue_id":"kanboard-api-9k8","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:43:31.070100884+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-apl","title":"Implement Project API methods","description":"Implement direct API methods for project/board operations.\n\n## Methods to implement\n- GetAllProjects(ctx) ([]Project, error) - getAllProjects\n- GetProjectByID(ctx, projectID int) (*Project, error) - getProjectById\n- GetProjectByName(ctx, name string) (*Project, error) - getProjectByName (Nice-to-have)\n\n## Files to create\n- projects.go\n\n## Acceptance criteria\n- All methods use context for cancellation\n- Proper error handling and wrapping\n- Returns ErrProjectNotFound when appropriate","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:15.864764497+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T18:21:36.226187777+01:00","closed_at":"2026-01-15T18:21:36.226187777+01:00","close_reason":"Closed","dependencies":[{"issue_id":"kanboard-api-apl","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:42:52.850751716+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-apl","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:42:52.946556814+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-apl","title":"Implement Project API methods","description":"Implement direct API methods for project/board operations.\n\n## Methods to implement\n- GetAllProjects(ctx) ([]Project, error) - getAllProjects\n- GetProjectByID(ctx, projectID int) (*Project, error) - getProjectById\n- GetProjectByName(ctx, name string) (*Project, error) - getProjectByName (Nice-to-have)\n\n## Files to create\n- projects.go\n\n## Acceptance criteria\n- All methods use context for cancellation\n- Proper error handling and wrapping\n- Returns ErrProjectNotFound when appropriate","status":"closed","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:35:15.864764497+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T18:21:36.226187777+01:00","closed_at":"2026-01-15T18:21:36.226187777+01:00","close_reason":"Closed","dependencies":[{"issue_id":"kanboard-api-apl","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:42:52.850751716+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"kanboard-api-apl","depends_on_id":"kanboard-api-cyc","type":"blocks","created_at":"2026-01-15T17:42:52.946556814+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
{"id":"kanboard-api-c3i","title":"Create README.md with documentation","description":"Create comprehensive README.md documentation in English.\n\n## Sections to include\n- Project overview and features\n- Installation instructions\n- Quick start example\n- API documentation overview\n- Authentication methods (API token, user/password)\n- Fluent API examples\n- Direct API examples\n- Error handling\n- Thread-safety notes\n- Tag operations warning (non-atomic)\n- License (MIT)\n\n## Example code to include\n```go\n// Client creation\nclient := kanboard.NewClient(\"https://kanboard.example.com\").\n WithAPIToken(\"my-token\").\n WithTimeout(30 * time.Second)\n\n// Fluent API\ntask, _ := client.Board(1).CreateTask(ctx,\n kanboard.NewTask(\"My Task\").WithDescription(\"Details\"))\n\n// Direct API\ntasks, _ := client.GetAllTasks(ctx, 1, kanboard.StatusActive)\n```\n\n## Files to create\n- README.md\n\n## Acceptance criteria\n- Clear installation instructions\n- Working code examples\n- Documents all major features","status":"open","priority":2,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:53.228407343+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:53.228407343+01:00","dependencies":[{"issue_id":"kanboard-api-c3i","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:46:55.445847253+01:00","created_by":"Oliver Jakoubek"}]}
|
{"id":"kanboard-api-c3i","title":"Create README.md with documentation","description":"Create comprehensive README.md documentation in English.\n\n## Sections to include\n- Project overview and features\n- Installation instructions\n- Quick start example\n- API documentation overview\n- Authentication methods (API token, user/password)\n- Fluent API examples\n- Direct API examples\n- Error handling\n- Thread-safety notes\n- Tag operations warning (non-atomic)\n- License (MIT)\n\n## Example code to include\n```go\n// Client creation\nclient := kanboard.NewClient(\"https://kanboard.example.com\").\n WithAPIToken(\"my-token\").\n WithTimeout(30 * time.Second)\n\n// Fluent API\ntask, _ := client.Board(1).CreateTask(ctx,\n kanboard.NewTask(\"My Task\").WithDescription(\"Details\"))\n\n// Direct API\ntasks, _ := client.GetAllTasks(ctx, 1, kanboard.StatusActive)\n```\n\n## Files to create\n- README.md\n\n## Acceptance criteria\n- Clear installation instructions\n- Working code examples\n- Documents all major features","status":"open","priority":2,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-15T17:36:53.228407343+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-15T17:36:53.228407343+01:00","dependencies":[{"issue_id":"kanboard-api-c3i","depends_on_id":"kanboard-api-uls","type":"blocks","created_at":"2026-01-15T17:46:55.445847253+01:00","created_by":"Oliver Jakoubek"}]}
|
||||||
|
|
|
||||||
102
tasks.go
Normal file
102
tasks.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package kanboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTask returns a task by its ID.
|
||||||
|
// Returns ErrTaskNotFound if the task does not exist.
|
||||||
|
func (c *Client) GetTask(ctx context.Context, taskID int) (*Task, error) {
|
||||||
|
params := map[string]int{"task_id": taskID}
|
||||||
|
|
||||||
|
var result *Task
|
||||||
|
if err := c.call(ctx, "getTask", params, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("getTask: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
return nil, fmt.Errorf("%w: task %d", ErrTaskNotFound, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTasks returns all tasks for a project with the specified status.
|
||||||
|
func (c *Client) GetAllTasks(ctx context.Context, projectID int, status TaskStatus) ([]Task, error) {
|
||||||
|
params := map[string]int{
|
||||||
|
"project_id": projectID,
|
||||||
|
"status_id": int(status),
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []Task
|
||||||
|
if err := c.call(ctx, "getAllTasks", params, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("getAllTasks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTask creates a new task and returns the created task.
|
||||||
|
func (c *Client) CreateTask(ctx context.Context, req CreateTaskRequest) (*Task, error) {
|
||||||
|
var taskID int
|
||||||
|
if err := c.call(ctx, "createTask", req, &taskID); err != nil {
|
||||||
|
return nil, fmt.Errorf("createTask: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if taskID == 0 {
|
||||||
|
return nil, fmt.Errorf("createTask: failed to create task")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the created task to return full details
|
||||||
|
return c.GetTask(ctx, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTask updates an existing task.
|
||||||
|
// Only non-nil fields in the request will be updated.
|
||||||
|
func (c *Client) UpdateTask(ctx context.Context, req UpdateTaskRequest) error {
|
||||||
|
var success bool
|
||||||
|
if err := c.call(ctx, "updateTask", req, &success); err != nil {
|
||||||
|
return fmt.Errorf("updateTask: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("updateTask: update failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseTask closes a task (sets it to inactive).
|
||||||
|
// Returns ErrTaskClosed if the task is already closed.
|
||||||
|
func (c *Client) CloseTask(ctx context.Context, taskID int) error {
|
||||||
|
params := map[string]int{"task_id": taskID}
|
||||||
|
|
||||||
|
var success bool
|
||||||
|
if err := c.call(ctx, "closeTask", params, &success); err != nil {
|
||||||
|
return fmt.Errorf("closeTask: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("%w: task %d", ErrTaskClosed, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTask opens a task (sets it to active).
|
||||||
|
// Returns ErrTaskOpen if the task is already open.
|
||||||
|
func (c *Client) OpenTask(ctx context.Context, taskID int) error {
|
||||||
|
params := map[string]int{"task_id": taskID}
|
||||||
|
|
||||||
|
var success bool
|
||||||
|
if err := c.call(ctx, "openTask", params, &success); err != nil {
|
||||||
|
return fmt.Errorf("openTask: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("%w: task %d", ErrTaskOpen, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
510
tasks_test.go
Normal file
510
tasks_test.go
Normal file
|
|
@ -0,0 +1,510 @@
|
||||||
|
package kanboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_GetTask(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method != "getTask" {
|
||||||
|
t.Errorf("expected method=getTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["task_id"].(float64) != 42 {
|
||||||
|
t.Errorf("expected task_id=42, got %v", params["task_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`{"id": "42", "title": "Test Task", "project_id": "1", "column_id": "1", "is_active": "1"}`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
task, err := client.GetTask(context.Background(), 42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(task.ID) != 42 {
|
||||||
|
t.Errorf("expected ID=42, got %d", task.ID)
|
||||||
|
}
|
||||||
|
if task.Title != "Test Task" {
|
||||||
|
t.Errorf("expected title='Test Task', got %s", task.Title)
|
||||||
|
}
|
||||||
|
if !bool(task.IsActive) {
|
||||||
|
t.Error("expected task to be active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_GetTask_NotFound(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`null`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
_, err := client.GetTask(context.Background(), 999)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for non-existent task")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, ErrTaskNotFound) {
|
||||||
|
t.Errorf("expected ErrTaskNotFound, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_GetAllTasks(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method != "getAllTasks" {
|
||||||
|
t.Errorf("expected method=getAllTasks, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["project_id"].(float64) != 1 {
|
||||||
|
t.Errorf("expected project_id=1, got %v", params["project_id"])
|
||||||
|
}
|
||||||
|
if params["status_id"].(float64) != 1 {
|
||||||
|
t.Errorf("expected status_id=1, got %v", params["status_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`[
|
||||||
|
{"id": "1", "title": "Task One", "project_id": "1", "is_active": "1"},
|
||||||
|
{"id": "2", "title": "Task Two", "project_id": "1", "is_active": "1"}
|
||||||
|
]`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
tasks, err := client.GetAllTasks(context.Background(), 1, StatusActive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tasks) != 2 {
|
||||||
|
t.Errorf("expected 2 tasks, got %d", len(tasks))
|
||||||
|
}
|
||||||
|
if tasks[0].Title != "Task One" {
|
||||||
|
t.Errorf("expected first task='Task One', got %s", tasks[0].Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_GetAllTasks_Empty(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`[]`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
tasks, err := client.GetAllTasks(context.Background(), 1, StatusInactive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tasks) != 0 {
|
||||||
|
t.Errorf("expected 0 tasks, got %d", len(tasks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_CreateTask(t *testing.T) {
|
||||||
|
callCount := 0
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
callCount++
|
||||||
|
if callCount == 1 {
|
||||||
|
// First call: createTask
|
||||||
|
if req.Method != "createTask" {
|
||||||
|
t.Errorf("expected method=createTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["title"] != "New Task" {
|
||||||
|
t.Errorf("expected title='New Task', got %v", params["title"])
|
||||||
|
}
|
||||||
|
if params["project_id"].(float64) != 1 {
|
||||||
|
t.Errorf("expected project_id=1, got %v", params["project_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`42`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
} else {
|
||||||
|
// Second call: getTask to fetch created task
|
||||||
|
if req.Method != "getTask" {
|
||||||
|
t.Errorf("expected method=getTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`{"id": "42", "title": "New Task", "project_id": "1", "column_id": "1", "is_active": "1"}`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
task, err := client.CreateTask(context.Background(), CreateTaskRequest{
|
||||||
|
Title: "New Task",
|
||||||
|
ProjectID: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(task.ID) != 42 {
|
||||||
|
t.Errorf("expected ID=42, got %d", task.ID)
|
||||||
|
}
|
||||||
|
if task.Title != "New Task" {
|
||||||
|
t.Errorf("expected title='New Task', got %s", task.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_CreateTask_WithOptions(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method == "createTask" {
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["description"] != "Task description" {
|
||||||
|
t.Errorf("expected description='Task description', got %v", params["description"])
|
||||||
|
}
|
||||||
|
if params["color_id"] != "blue" {
|
||||||
|
t.Errorf("expected color_id='blue', got %v", params["color_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`42`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
} else {
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`{"id": "42", "title": "New Task", "project_id": "1", "column_id": "1", "is_active": "1", "description": "Task description", "color_id": "blue"}`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
_, err := client.CreateTask(context.Background(), CreateTaskRequest{
|
||||||
|
Title: "New Task",
|
||||||
|
ProjectID: 1,
|
||||||
|
Description: "Task description",
|
||||||
|
ColorID: "blue",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_CreateTask_Failure(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
// Return 0 (false) to indicate failure
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`0`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
_, err := client.CreateTask(context.Background(), CreateTaskRequest{
|
||||||
|
Title: "New Task",
|
||||||
|
ProjectID: 1,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for failed task creation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_UpdateTask(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method != "updateTask" {
|
||||||
|
t.Errorf("expected method=updateTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["id"].(float64) != 42 {
|
||||||
|
t.Errorf("expected id=42, got %v", params["id"])
|
||||||
|
}
|
||||||
|
if params["title"] != "Updated Title" {
|
||||||
|
t.Errorf("expected title='Updated Title', got %v", params["title"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`true`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
title := "Updated Title"
|
||||||
|
err := client.UpdateTask(context.Background(), UpdateTaskRequest{
|
||||||
|
ID: 42,
|
||||||
|
Title: &title,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_UpdateTask_PartialUpdate(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
// Only priority should be set
|
||||||
|
if _, hasTitle := params["title"]; hasTitle {
|
||||||
|
t.Error("title should not be present in partial update")
|
||||||
|
}
|
||||||
|
if params["priority"].(float64) != 2 {
|
||||||
|
t.Errorf("expected priority=2, got %v", params["priority"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`true`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
priority := 2
|
||||||
|
err := client.UpdateTask(context.Background(), UpdateTaskRequest{
|
||||||
|
ID: 42,
|
||||||
|
Priority: &priority,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_UpdateTask_Failure(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`false`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
title := "Updated Title"
|
||||||
|
err := client.UpdateTask(context.Background(), UpdateTaskRequest{
|
||||||
|
ID: 42,
|
||||||
|
Title: &title,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for failed update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_CloseTask(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method != "closeTask" {
|
||||||
|
t.Errorf("expected method=closeTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["task_id"].(float64) != 42 {
|
||||||
|
t.Errorf("expected task_id=42, got %v", params["task_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`true`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
err := client.CloseTask(context.Background(), 42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_CloseTask_AlreadyClosed(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
// Kanboard returns false if task is already closed
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`false`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
err := client.CloseTask(context.Background(), 42)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for already closed task")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, ErrTaskClosed) {
|
||||||
|
t.Errorf("expected ErrTaskClosed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_OpenTask(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
if req.Method != "openTask" {
|
||||||
|
t.Errorf("expected method=openTask, got %s", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := req.Params.(map[string]any)
|
||||||
|
if params["task_id"].(float64) != 42 {
|
||||||
|
t.Errorf("expected task_id=42, got %v", params["task_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`true`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
err := client.OpenTask(context.Background(), 42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_OpenTask_AlreadyOpen(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req JSONRPCRequest
|
||||||
|
json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
|
// Kanboard returns false if task is already open
|
||||||
|
resp := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: req.ID,
|
||||||
|
Result: json.RawMessage(`false`),
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
err := client.OpenTask(context.Background(), 42)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for already open task")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, ErrTaskOpen) {
|
||||||
|
t.Errorf("expected ErrTaskOpen, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_GetTask_ContextCanceled(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
select {}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := NewClient(server.URL).WithAPIToken("test-token")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, err := client.GetTask(ctx, 42)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error due to canceled context")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue