Add unit tests for Tasks
Create tasks_test.go with comprehensive tests: - TestTasks_List: list all tasks in checklist - TestTasks_Get: get single task by ID - TestTasks_Create: create basic task - TestTasks_Create_WithBuilder: create task with all options - TestTasks_Update: update task properties - TestTasks_Delete: delete task - TestTasks_Close: mark task as completed - TestTasks_Reopen: reopen closed task - TestTasks_Invalidate: invalidate task - TestDueDate_Parsing: table-driven due date parsing tests - TestTaskBuilder: builder pattern validation Add testdata/tasks/ fixtures: - list.json: sample task list - single.json: single task response All 11 tests pass using httptest.Server mocking. Closes checkvist-api-v2f
This commit is contained in:
parent
0bb7d2d735
commit
e2d0f2299c
4 changed files with 487 additions and 1 deletions
436
tasks_test.go
Normal file
436
tasks_test.go
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
package checkvist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTasks_List(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks.json":
|
||||
w.Write(loadFixture(t, "testdata/tasks/list.json"))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
tasks, err := client.Tasks(1).List(context.Background())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(tasks) != 2 {
|
||||
t.Fatalf("expected 2 tasks, got %d", len(tasks))
|
||||
}
|
||||
if tasks[0].ID != 101 {
|
||||
t.Errorf("expected ID 101, got %d", tasks[0].ID)
|
||||
}
|
||||
if tasks[0].Content != "First task" {
|
||||
t.Errorf("expected content 'First task', got %s", tasks[0].Content)
|
||||
}
|
||||
if tasks[1].Priority != 1 {
|
||||
t.Errorf("expected priority 1, got %d", tasks[1].Priority)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Get(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101.json":
|
||||
w.Write(loadFixture(t, "testdata/tasks/single.json"))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
task, err := client.Tasks(1).Get(context.Background(), 101)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.ID != 101 {
|
||||
t.Errorf("expected ID 101, got %d", task.ID)
|
||||
}
|
||||
if task.Content != "First task" {
|
||||
t.Errorf("expected content 'First task', got %s", task.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Create(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks.json":
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
|
||||
var req CreateTaskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
if req.Content != "New task" {
|
||||
t.Errorf("expected content 'New task', got %s", req.Content)
|
||||
}
|
||||
|
||||
response := Task{
|
||||
ID: 200,
|
||||
ChecklistID: 1,
|
||||
Content: req.Content,
|
||||
Status: StatusOpen,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
task, err := client.Tasks(1).Create(context.Background(), NewTask("New task"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.ID != 200 {
|
||||
t.Errorf("expected ID 200, got %d", task.ID)
|
||||
}
|
||||
if task.Content != "New task" {
|
||||
t.Errorf("expected content 'New task', got %s", task.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Create_WithBuilder(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks.json":
|
||||
var req CreateTaskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
|
||||
if req.Content != "Task with options" {
|
||||
t.Errorf("expected content 'Task with options', got %s", req.Content)
|
||||
}
|
||||
if req.Priority != 1 {
|
||||
t.Errorf("expected priority 1, got %d", req.Priority)
|
||||
}
|
||||
if req.Due != "^tomorrow" {
|
||||
t.Errorf("expected due '^tomorrow', got %s", req.Due)
|
||||
}
|
||||
if req.Tags != "tag1, tag2" {
|
||||
t.Errorf("expected tags 'tag1, tag2', got %s", req.Tags)
|
||||
}
|
||||
if req.ParentID != 100 {
|
||||
t.Errorf("expected parent_id 100, got %d", req.ParentID)
|
||||
}
|
||||
|
||||
response := Task{
|
||||
ID: 201,
|
||||
ChecklistID: 1,
|
||||
ParentID: req.ParentID,
|
||||
Content: req.Content,
|
||||
Priority: req.Priority,
|
||||
DueDateRaw: "2026-01-15",
|
||||
TagsAsText: req.Tags,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
builder := NewTask("Task with options").
|
||||
WithPriority(1).
|
||||
WithDueDate(DueTomorrow).
|
||||
WithTags("tag1", "tag2").
|
||||
WithParent(100)
|
||||
|
||||
task, err := client.Tasks(1).Create(context.Background(), builder)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.ID != 201 {
|
||||
t.Errorf("expected ID 201, got %d", task.ID)
|
||||
}
|
||||
if task.ParentID != 100 {
|
||||
t.Errorf("expected ParentID 100, got %d", task.ParentID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Update(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101.json":
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
|
||||
response := Task{
|
||||
ID: 101,
|
||||
ChecklistID: 1,
|
||||
Content: "Updated content",
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
content := "Updated content"
|
||||
task, err := client.Tasks(1).Update(context.Background(), 101, UpdateTaskRequest{
|
||||
Content: &content,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.Content != "Updated content" {
|
||||
t.Errorf("expected content 'Updated content', got %s", task.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Delete(t *testing.T) {
|
||||
var deleteCalled bool
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101.json":
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
deleteCalled = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
err := client.Tasks(1).Delete(context.Background(), 101)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !deleteCalled {
|
||||
t.Error("expected DELETE to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Close(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101/close.json":
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
response := Task{
|
||||
ID: 101,
|
||||
Status: StatusClosed,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
task, err := client.Tasks(1).Close(context.Background(), 101)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.Status != StatusClosed {
|
||||
t.Errorf("expected status Closed, got %v", task.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Reopen(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101/reopen.json":
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
response := Task{
|
||||
ID: 101,
|
||||
Status: StatusOpen,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
task, err := client.Tasks(1).Reopen(context.Background(), 101)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.Status != StatusOpen {
|
||||
t.Errorf("expected status Open, got %v", task.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks_Invalidate(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/auth/login.json":
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": "test-token"})
|
||||
case "/checklists/1/tasks/101/invalidate.json":
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
response := Task{
|
||||
ID: 101,
|
||||
Status: StatusInvalidated,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
task, err := client.Tasks(1).Invalidate(context.Background(), 101)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if task.Status != StatusInvalidated {
|
||||
t.Errorf("expected status Invalidated, got %v", task.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDueDate_Parsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dueRaw string
|
||||
expected *time.Time
|
||||
}{
|
||||
{
|
||||
name: "ISO date",
|
||||
dueRaw: "2026-01-20",
|
||||
expected: timePtr(time.Date(2026, 1, 20, 0, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
dueRaw: "",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid format",
|
||||
dueRaw: "tomorrow",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
task := &Task{DueDateRaw: tc.dueRaw}
|
||||
parseDueDate(task)
|
||||
|
||||
if tc.expected == nil {
|
||||
if task.DueDate != nil {
|
||||
t.Errorf("expected nil DueDate, got %v", task.DueDate)
|
||||
}
|
||||
} else {
|
||||
if task.DueDate == nil {
|
||||
t.Fatal("expected DueDate to be set")
|
||||
}
|
||||
if !task.DueDate.Equal(*tc.expected) {
|
||||
t.Errorf("expected %v, got %v", tc.expected, task.DueDate)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskBuilder(t *testing.T) {
|
||||
builder := NewTask("Test content").
|
||||
WithParent(50).
|
||||
WithPosition(3).
|
||||
WithDueDate(DueNextWeek).
|
||||
WithPriority(2).
|
||||
WithTags("work", "urgent")
|
||||
|
||||
req := builder.build()
|
||||
|
||||
if req.Content != "Test content" {
|
||||
t.Errorf("expected content 'Test content', got %s", req.Content)
|
||||
}
|
||||
if req.ParentID != 50 {
|
||||
t.Errorf("expected ParentID 50, got %d", req.ParentID)
|
||||
}
|
||||
if req.Position != 3 {
|
||||
t.Errorf("expected Position 3, got %d", req.Position)
|
||||
}
|
||||
if req.Due != "^next week" {
|
||||
t.Errorf("expected Due '^next week', got %s", req.Due)
|
||||
}
|
||||
if req.Priority != 2 {
|
||||
t.Errorf("expected Priority 2, got %d", req.Priority)
|
||||
}
|
||||
if req.Tags != "work, urgent" {
|
||||
t.Errorf("expected Tags 'work, urgent', got %s", req.Tags)
|
||||
}
|
||||
}
|
||||
|
||||
func timePtr(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue