Add unit tests for Checklists
Create checklists_test.go with comprehensive tests: - TestChecklists_List: list all checklists - TestChecklists_ListArchived: list with archived filter - TestChecklists_Get: get single checklist by ID - TestChecklists_Get_NotFound: handle 404 error - TestChecklists_Create: create new checklist - TestChecklists_Update: update checklist name - TestChecklists_Delete: delete checklist Add testdata/checklists/ fixtures: - list.json: sample checklist list - list_archived.json: archived checklists - single.json: single checklist response All 7 tests pass using httptest.Server mocking. Closes checkvist-api-347
This commit is contained in:
parent
5f71f40077
commit
0bb7d2d735
5 changed files with 309 additions and 1 deletions
|
|
@ -3,7 +3,7 @@
|
|||
{"id":"checkvist-api-1ki","title":"Write GoDoc examples","description":"Create example_test.go with runnable examples:\n- Example_basicUsage\n- ExampleNewClient\n- ExampleClient_Authenticate\n- ExampleChecklistService_List\n- ExampleTaskService_Create\n- ExampleNewTask (builder pattern)\n- ExampleNewFilter (filter builder)\n- ExampleFilter_Apply\nAll examples should be runnable and appear in GoDoc.","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:38.443075101+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:38.443075101+01:00","dependencies":[{"issue_id":"checkvist-api-1ki","depends_on_id":"checkvist-api-c2k","type":"blocks","created_at":"2026-01-14T12:33:14.730667505+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"checkvist-api-1ki","depends_on_id":"checkvist-api-rl9","type":"blocks","created_at":"2026-01-14T12:33:15.039931257+01:00","created_by":"Oliver Jakoubek"},{"issue_id":"checkvist-api-1ki","depends_on_id":"checkvist-api-1ze","type":"blocks","created_at":"2026-01-14T12:33:15.388830036+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-1ze","title":"Implement client-side Filter builder","description":"Create filter.go with Filter builder (since Checkvist API has no server-side filtering):\n- NewFilter(tasks []Task) *Filter\n- WithTag(tag string) *Filter\n- WithTags(tags ...string) *Filter (AND logic)\n- WithStatus(status TaskStatus) *Filter\n- WithDueBefore(t time.Time) *Filter\n- WithDueAfter(t time.Time) *Filter\n- WithDueOn(t time.Time) *Filter\n- WithOverdue() *Filter\n- WithSearch(query string) *Filter (searches in Content)\n- Apply() []Task\nPerformance target: \u003c10ms for 1000+ tasks\nFilters are combined with AND logic.","status":"open","priority":1,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:30:56.24379077+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:30:56.24379077+01:00","dependencies":[{"issue_id":"checkvist-api-1ze","depends_on_id":"checkvist-api-rl9","type":"blocks","created_at":"2026-01-14T12:33:02.543121262+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-1zf","title":"Write unit tests for Filter","description":"Create filter_test.go with tests:\n- TestFilter_WithTag\n- TestFilter_WithMultipleTags\n- TestFilter_WithStatus\n- TestFilter_WithDueBefore\n- TestFilter_WithDueAfter\n- TestFilter_WithDueOn\n- TestFilter_WithOverdue\n- TestFilter_WithSearch\n- TestFilter_Combined\n- TestFilter_Performance_1000Tasks\nUse table-driven tests.","status":"open","priority":1,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:38.136650557+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:38.136650557+01:00","dependencies":[{"issue_id":"checkvist-api-1zf","depends_on_id":"checkvist-api-1ze","type":"blocks","created_at":"2026-01-14T12:33:14.4378645+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-347","title":"Write unit tests for Checklists","description":"Create checklists_test.go with tests:\n- TestChecklists_List\n- TestChecklists_ListArchived\n- TestChecklists_Get\n- TestChecklists_Get_NotFound\n- TestChecklists_Create\n- TestChecklists_Update\n- TestChecklists_Delete\nUse table-driven tests. Create testdata/checklists/ fixtures.","status":"open","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:37.243525209+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:37.243525209+01:00","dependencies":[{"issue_id":"checkvist-api-347","depends_on_id":"checkvist-api-c2k","type":"blocks","created_at":"2026-01-14T12:33:13.512887479+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-347","title":"Write unit tests for Checklists","description":"Create checklists_test.go with tests:\n- TestChecklists_List\n- TestChecklists_ListArchived\n- TestChecklists_Get\n- TestChecklists_Get_NotFound\n- TestChecklists_Create\n- TestChecklists_Update\n- TestChecklists_Delete\nUse table-driven tests. Create testdata/checklists/ fixtures.","status":"closed","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:37.243525209+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T13:41:58.076714398+01:00","closed_at":"2026-01-14T13:41:58.076714398+01:00","close_reason":"Closed","dependencies":[{"issue_id":"checkvist-api-347","depends_on_id":"checkvist-api-c2k","type":"blocks","created_at":"2026-01-14T12:33:13.512887479+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-47y","title":"Quality \u0026 Documentation","description":"Phase 4: Complete unit tests, GoDoc examples, README, and CHANGELOG. Target \u003e80% test coverage. All public functions documented with examples.","status":"open","priority":0,"issue_type":"epic","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:36.655509387+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:31:36.655509387+01:00"}
|
||||
{"id":"checkvist-api-5ab","title":"Implement Note operations","description":"Create notes.go with NoteService:\n- client.Notes(checklistID, taskID) returns NoteService\n- List(ctx) ([]Note, error) - GET /checklists/{id}/tasks/{task_id}/comments.json\n- Create(ctx, comment string) (*Note, error) - POST /checklists/{id}/tasks/{task_id}/comments.json\n- Update(ctx, noteID, comment string) (*Note, error) - PUT /checklists/{id}/tasks/{task_id}/comments/{note_id}.json\n- Delete(ctx, noteID) error - DELETE /checklists/{id}/tasks/{task_id}/comments/{note_id}.json\nContext support for all methods.","status":"closed","priority":0,"issue_type":"feature","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:30:54.268124634+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T13:40:31.06124818+01:00","closed_at":"2026-01-14T13:40:31.06124818+01:00","close_reason":"Closed","dependencies":[{"issue_id":"checkvist-api-5ab","depends_on_id":"checkvist-api-rl9","type":"blocks","created_at":"2026-01-14T12:32:55.843507717+01:00","created_by":"Oliver Jakoubek"}]}
|
||||
{"id":"checkvist-api-5wr","title":"Initialize Go module and project structure","description":"Create go.mod with module path code.beautifulmachines.dev/jakoubek/checkvist-api. Set up flat package structure with placeholder files: client.go, checklists.go, tasks.go, notes.go, filter.go, models.go, errors.go, options.go. Create magefiles/ directory with separate go.mod for Mage build targets. Add LICENSE (MIT) file.","status":"closed","priority":0,"issue_type":"task","owner":"mail@oliverjakoubek.de","created_at":"2026-01-14T12:31:06.285510329+01:00","created_by":"Oliver Jakoubek","updated_at":"2026-01-14T12:43:45.392753825+01:00","closed_at":"2026-01-14T12:43:45.392753825+01:00","close_reason":"Closed"}
|
||||
|
|
|
|||
260
checklists_test.go
Normal file
260
checklists_test.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
package checkvist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestChecklists_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.json":
|
||||
if r.URL.Query().Get("archived") != "" {
|
||||
t.Error("unexpected archived parameter in List")
|
||||
}
|
||||
w.Write(loadFixture(t, "testdata/checklists/list.json"))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
checklists, err := client.Checklists().List(context.Background())
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(checklists) != 2 {
|
||||
t.Fatalf("expected 2 checklists, got %d", len(checklists))
|
||||
}
|
||||
if checklists[0].ID != 1 {
|
||||
t.Errorf("expected ID 1, got %d", checklists[0].ID)
|
||||
}
|
||||
if checklists[0].Name != "My First Checklist" {
|
||||
t.Errorf("expected name 'My First Checklist', got %s", checklists[0].Name)
|
||||
}
|
||||
if checklists[1].TaskCount != 25 {
|
||||
t.Errorf("expected TaskCount 25, got %d", checklists[1].TaskCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_ListArchived(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.json":
|
||||
if r.URL.Query().Get("archived") != "true" {
|
||||
t.Error("expected archived=true parameter")
|
||||
}
|
||||
w.Write(loadFixture(t, "testdata/checklists/list_archived.json"))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
checklists, err := client.Checklists().ListWithOptions(context.Background(), ListOptions{Archived: true})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(checklists) != 1 {
|
||||
t.Fatalf("expected 1 checklist, got %d", len(checklists))
|
||||
}
|
||||
if !checklists[0].Archived {
|
||||
t.Error("expected checklist to be archived")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_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.json":
|
||||
w.Write(loadFixture(t, "testdata/checklists/single.json"))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
checklist, err := client.Checklists().Get(context.Background(), 1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if checklist.ID != 1 {
|
||||
t.Errorf("expected ID 1, got %d", checklist.ID)
|
||||
}
|
||||
if checklist.Name != "My First Checklist" {
|
||||
t.Errorf("expected name 'My First Checklist', got %s", checklist.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_Get_NotFound(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/999.json":
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(`{"error": "Checklist not found"}`))
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("user@example.com", "api-key", WithBaseURL(server.URL))
|
||||
_, err := client.Checklists().Get(context.Background(), 999)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("expected ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_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.json":
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
|
||||
var req createChecklistRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
if req.Name != "New Checklist" {
|
||||
t.Errorf("expected name 'New Checklist', got %s", req.Name)
|
||||
}
|
||||
|
||||
response := Checklist{
|
||||
ID: 42,
|
||||
Name: req.Name,
|
||||
Public: false,
|
||||
Archived: false,
|
||||
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))
|
||||
checklist, err := client.Checklists().Create(context.Background(), "New Checklist")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if checklist.ID != 42 {
|
||||
t.Errorf("expected ID 42, got %d", checklist.ID)
|
||||
}
|
||||
if checklist.Name != "New Checklist" {
|
||||
t.Errorf("expected name 'New Checklist', got %s", checklist.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_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.json":
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
|
||||
var req updateChecklistRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
if req.Name != "Updated Name" {
|
||||
t.Errorf("expected name 'Updated Name', got %s", req.Name)
|
||||
}
|
||||
|
||||
response := Checklist{
|
||||
ID: 1,
|
||||
Name: req.Name,
|
||||
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))
|
||||
checklist, err := client.Checklists().Update(context.Background(), 1, "Updated Name")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if checklist.Name != "Updated Name" {
|
||||
t.Errorf("expected name 'Updated Name', got %s", checklist.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChecklists_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.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.Checklists().Delete(context.Background(), 1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !deleteCalled {
|
||||
t.Error("expected DELETE to be called")
|
||||
}
|
||||
}
|
||||
24
testdata/checklists/list.json
vendored
Normal file
24
testdata/checklists/list.json
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "My First Checklist",
|
||||
"public": false,
|
||||
"archived": false,
|
||||
"read_only": false,
|
||||
"task_count": 10,
|
||||
"task_completed": 3,
|
||||
"tags_as_text": "",
|
||||
"updated_at": "2026-01-14T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Work Tasks",
|
||||
"public": true,
|
||||
"archived": false,
|
||||
"read_only": false,
|
||||
"task_count": 25,
|
||||
"task_completed": 12,
|
||||
"tags_as_text": "work, important",
|
||||
"updated_at": "2026-01-13T15:30:00Z"
|
||||
}
|
||||
]
|
||||
13
testdata/checklists/list_archived.json
vendored
Normal file
13
testdata/checklists/list_archived.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Old Project",
|
||||
"public": false,
|
||||
"archived": true,
|
||||
"read_only": false,
|
||||
"task_count": 50,
|
||||
"task_completed": 50,
|
||||
"tags_as_text": "",
|
||||
"updated_at": "2025-06-01T09:00:00Z"
|
||||
}
|
||||
]
|
||||
11
testdata/checklists/single.json
vendored
Normal file
11
testdata/checklists/single.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": 1,
|
||||
"name": "My First Checklist",
|
||||
"public": false,
|
||||
"archived": false,
|
||||
"read_only": false,
|
||||
"task_count": 10,
|
||||
"task_completed": 3,
|
||||
"tags_as_text": "",
|
||||
"updated_at": "2026-01-14T10:00:00Z"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue