.golangci.yaml 🔗
@@ -132,3 +132,10 @@ linters:
- db # database
- tx # transaction
- fn # function
+
+ exclusions:
+ rules:
+ - path: _test\.go
+ linters:
+ - dupl
+ - goconst
Amolith created
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 <crush@charm.land>
.golangci.yaml | 7 +
README.md | 2
Taskfile.yaml | 5 +
integration_test.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 183 insertions(+), 1 deletion(-)
@@ -132,3 +132,10 @@ linters:
- db # database
- tx # transaction
- fn # function
+
+ exclusions:
+ rules:
+ - path: _test\.go
+ linters:
+ - dupl
+ - goconst
@@ -9,7 +9,7 @@ SPDX-License-Identifier: CC0-1.0
[][godocs.io]
[][pkg.go.dev]
[](https://goreportcard.com/report/git.secluded.site/go-lunatask)
-
+
[](https://api.reuse.software/info/git.secluded.site/go-lunatask)
[](https://liberapay.com/Amolith/)
@@ -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:
@@ -0,0 +1,170 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// 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)
+}