From 7dbcb318da6d7ab355a9e9cba5f0c97064a35076 Mon Sep 17 00:00:00 2001 From: Amolith Date: Fri, 19 Dec 2025 20:24:44 -0700 Subject: [PATCH] test(integration): add live API validation tests Adds build-tagged integration tests that run against the live Lunatask API. Requires LUNATASK_API_KEY and LUNATASK_TEST_AREA environment variables. Tests cover ping, list operations, and full create/read/update/delete round-trips for tasks and notes. Also excludes dupl and goconst linters from test files and updates coverage badge. Assisted-by: Claude Opus 4.5 via Crush --- .golangci.yaml | 7 ++ README.md | 2 +- Taskfile.yaml | 5 ++ integration_test.go | 170 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 integration_test.go diff --git a/.golangci.yaml b/.golangci.yaml index 3e2962ccfbdf6fd969ff778b2b00fce40e172930..551fd42d1fa84253af208296519bf1c79c6a5c97 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -132,3 +132,10 @@ linters: - db # database - tx # transaction - fn # function + + exclusions: + rules: + - path: _test\.go + linters: + - dupl + - goconst diff --git a/README.md b/README.md index 179d4406ae1f2355ae9bd43bf88bd41e44fcf317..92693c680408b466cd938dfed8dd1b27b6d0b04e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: CC0-1.0 [![Godocs.io Reference](https://godocs.io/git.secluded.site/go-lunatask?status.svg)][godocs.io] [![Pkg.go.dev Reference](https://pkg.go.dev/badge/git.secluded.site/go-lunatask.svg)][pkg.go.dev] [![Go Report Card](https://goreportcard.com/badge/git.secluded.site/go-lunatask)](https://goreportcard.com/report/git.secluded.site/go-lunatask) -![Test coverage](https://img.shields.io/badge/coverage-84.6%25-brightgreen) +![Test coverage](https://img.shields.io/badge/coverage-84.9%25-brightgreen) [![REUSE compatibility](https://api.reuse.software/badge/git.secluded.site/go-lunatask)](https://api.reuse.software/info/git.secluded.site/go-lunatask) [![Liberapay donation status](https://img.shields.io/liberapay/receives/Amolith.svg?logo=liberapay)](https://liberapay.com/Amolith/) diff --git a/Taskfile.yaml b/Taskfile.yaml index 5b17d8d3b2e90f293969337da334c3a1c264dc38..416d6a99854b194df53b8d22a09caff8a50dfce9 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -48,6 +48,11 @@ tasks: cmds: - go test -v ./... + integration: + desc: Run integration tests against real API (requires LUNATASK_API_KEY, LUNATASK_TEST_AREA) + cmds: + - go test -v -tags=integration ./... + badge: desc: Update coverage badge cmds: diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c59d3ed0ca1ec7b14e6ca7e3b67d04a2c3018077 --- /dev/null +++ b/integration_test.go @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: Amolith +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +//go:build integration + +package lunatask_test + +import ( + "os" + "testing" + "time" + + lunatask "git.secluded.site/go-lunatask" +) + +var ( + integrationClient *lunatask.Client + testAreaID string +) + +func TestMain(m *testing.M) { + apiKey := os.Getenv("LUNATASK_API_KEY") + if apiKey == "" { + panic("LUNATASK_API_KEY environment variable required for integration tests") + } + + testAreaID = os.Getenv("LUNATASK_TEST_AREA") + if testAreaID == "" { + panic("LUNATASK_TEST_AREA environment variable required for integration tests") + } + + integrationClient = lunatask.NewClient(apiKey) + + os.Exit(m.Run()) +} + +// testName generates a unique name for test entities. +func testName(prefix string) string { + return prefix + "-" + time.Now().Format("20060102-150405.000") +} + +func TestIntegration_Ping(t *testing.T) { + resp, err := integrationClient.Ping(ctx()) + if err != nil { + t.Fatalf("Ping() error = %v", err) + } + + t.Logf("Ping response: %+v", resp) +} + +func TestIntegration_ListTasks(t *testing.T) { + tasks, err := integrationClient.ListTasks(ctx(), nil) + if err != nil { + t.Fatalf("ListTasks() error = %v", err) + } + + t.Logf("Found %d tasks", len(tasks)) +} + +func TestIntegration_ListNotes(t *testing.T) { + notes, err := integrationClient.ListNotes(ctx(), nil) + if err != nil { + t.Fatalf("ListNotes() error = %v", err) + } + + t.Logf("Found %d notes", len(notes)) +} + +func TestIntegration_ListPeople(t *testing.T) { + people, err := integrationClient.ListPeople(ctx(), nil) + if err != nil { + t.Fatalf("ListPeople() error = %v", err) + } + + t.Logf("Found %d people", len(people)) +} + +func TestIntegration_TaskRoundTrip(t *testing.T) { + name := testName("integration-task") + + task, err := integrationClient.NewTask(name). + InArea(testAreaID). + WithStatus(lunatask.StatusNext). + WithEstimate(15). + Create(ctx()) + if err != nil { + t.Fatalf("NewTask().Create() error = %v", err) + } + + if task == nil { + t.Fatal("NewTask().Create() returned nil (duplicate?)") + } + + t.Cleanup(func() { + if _, err := integrationClient.DeleteTask(ctx(), task.ID); err != nil { + t.Errorf("DeleteTask() cleanup error = %v", err) + } + }) + + t.Logf("Created task: %s", task.ID) + + // Verify we can fetch it + fetched, err := integrationClient.GetTask(ctx(), task.ID) + if err != nil { + t.Fatalf("GetTask() error = %v", err) + } + + if fetched.ID != task.ID { + t.Errorf("GetTask().ID = %s, want %s", fetched.ID, task.ID) + } + + // Update it + updated, err := integrationClient.NewTaskUpdate(task.ID). + WithEstimate(30). + Update(ctx()) + if err != nil { + t.Fatalf("NewTaskUpdate().Update() error = %v", err) + } + + if updated.Estimate == nil || *updated.Estimate != 30 { + t.Errorf("updated estimate = %v, want 30", updated.Estimate) + } + + t.Logf("Updated task estimate to 30 minutes") +} + +func TestIntegration_NoteRoundTrip(t *testing.T) { + name := testName("integration-note") + + note, err := integrationClient.NewNote(). + WithName(name). + WithContent("# Test Note\n\nThis is a test."). + Create(ctx()) + if err != nil { + t.Fatalf("NewNote().Create() error = %v", err) + } + + if note == nil { + t.Fatal("NewNote().Create() returned nil (duplicate?)") + } + + t.Cleanup(func() { + if _, err := integrationClient.DeleteNote(ctx(), note.ID); err != nil { + t.Errorf("DeleteNote() cleanup error = %v", err) + } + }) + + t.Logf("Created note: %s", note.ID) + + // Verify we can fetch it + fetched, err := integrationClient.GetNote(ctx(), note.ID) + if err != nil { + t.Fatalf("GetNote() error = %v", err) + } + + if fetched.ID != note.ID { + t.Errorf("GetNote().ID = %s, want %s", fetched.ID, note.ID) + } + + // Update it + updated, err := integrationClient.NewNoteUpdate(note.ID). + WithContent("# Updated\n\nNew content."). + Update(ctx()) + if err != nil { + t.Fatalf("NewNoteUpdate().Update() error = %v", err) + } + + t.Logf("Updated note: %s", updated.ID) +}