notes_test.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package lunatask_test
  6
  7import (
  8	"net/http"
  9	"net/http/httptest"
 10	"net/url"
 11	"testing"
 12	"time"
 13
 14	lunatask "git.secluded.site/go-lunatask"
 15)
 16
 17const (
 18	noteID     = "5999b945-b2b1-48c6-aa72-b251b75b3c2e"
 19	notebookID = "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1"
 20)
 21
 22// --- ListNotes ---
 23
 24func TestListNotes_Success(t *testing.T) {
 25	t.Parallel()
 26
 27	server := newJSONServer(t, "/notes", notesResponseBody)
 28	defer server.Close()
 29
 30	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
 31
 32	notes, err := client.ListNotes(ctx(), nil)
 33	if err != nil {
 34		t.Fatalf("error = %v", err)
 35	}
 36
 37	if len(notes) != 2 {
 38		t.Fatalf("len = %d, want 2", len(notes))
 39	}
 40
 41	note := notes[0]
 42	if note.ID != noteID {
 43		t.Errorf("ID = %q, want %q", note.ID, noteID)
 44	}
 45
 46	if note.NotebookID == nil || *note.NotebookID != notebookID {
 47		t.Errorf("NotebookID = %v, want %q", note.NotebookID, notebookID)
 48	}
 49
 50	wantCreated := time.Date(2021, 1, 10, 10, 39, 25, 0, time.UTC)
 51	if !note.CreatedAt.Equal(wantCreated) {
 52		t.Errorf("CreatedAt = %v, want %v", note.CreatedAt, wantCreated)
 53	}
 54}
 55
 56func TestListNotes_Filter(t *testing.T) {
 57	t.Parallel()
 58
 59	tests := []filterTest{
 60		{
 61			Name:      "source_only",
 62			Source:    ptr("evernote"),
 63			SourceID:  nil,
 64			WantQuery: url.Values{"source": {"evernote"}},
 65		},
 66		{
 67			Name:      "source_and_id",
 68			Source:    ptr("evernote"),
 69			SourceID:  ptr("note-123"),
 70			WantQuery: url.Values{"source": {"evernote"}, "source_id": {"note-123"}},
 71		},
 72	}
 73
 74	runFilterTests(t, "/notes", `{"notes": []}`, tests, func(c *lunatask.Client, source, sourceID *string) error {
 75		opts := &lunatask.ListNotesOptions{Source: source, SourceID: sourceID}
 76		_, err := c.ListNotes(ctx(), opts)
 77
 78		return err //nolint:wrapcheck // test helper
 79	})
 80}
 81
 82func TestListNotes_Errors(t *testing.T) {
 83	t.Parallel()
 84
 85	testErrorCases(t, func(c *lunatask.Client) error {
 86		_, err := c.ListNotes(ctx(), nil)
 87
 88		return err //nolint:wrapcheck // test helper
 89	})
 90}
 91
 92// --- GetNote (undocumented but supported) ---
 93
 94func TestGetNote_Success(t *testing.T) {
 95	t.Parallel()
 96
 97	server := newJSONServer(t, "/notes/"+noteID, singleNoteResponseBody)
 98	defer server.Close()
 99
100	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
101
102	note, err := client.GetNote(ctx(), noteID)
103	if err != nil {
104		t.Fatalf("error = %v", err)
105	}
106
107	if note == nil {
108		t.Fatal("returned nil")
109	}
110
111	if note.ID != noteID {
112		t.Errorf("ID = %q, want %q", note.ID, noteID)
113	}
114}
115
116func TestGetNote_Errors(t *testing.T) {
117	t.Parallel()
118
119	testErrorCases(t, func(c *lunatask.Client) error {
120		_, err := c.GetNote(ctx(), noteID)
121
122		return err //nolint:wrapcheck // test helper
123	})
124}
125
126// --- CreateNote ---
127
128func TestCreateNote_Success(t *testing.T) {
129	t.Parallel()
130
131	server, capture := newPOSTServer(t, "/notes", singleNoteResponseBody)
132	defer server.Close()
133
134	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
135
136	note, err := client.NewNote().
137		WithName("My note").
138		WithContent("Note content").
139		InNotebook(notebookID).
140		FromSource("evernote", "ext-123").
141		Create(ctx())
142	if err != nil {
143		t.Fatalf("error = %v", err)
144	}
145
146	if note == nil {
147		t.Fatal("returned nil")
148	}
149
150	if note.ID != noteID {
151		t.Errorf("ID = %q, want %q", note.ID, noteID)
152	}
153
154	assertBodyField(t, capture.Body, "name", "My note")
155	assertBodyField(t, capture.Body, "content", "Note content")
156	assertBodyField(t, capture.Body, "notebook_id", notebookID)
157	assertBodyField(t, capture.Body, "source", "evernote")
158	assertBodyField(t, capture.Body, "source_id", "ext-123")
159}
160
161func TestCreateNote_Duplicate(t *testing.T) {
162	t.Parallel()
163
164	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
165		writer.WriteHeader(http.StatusNoContent)
166	}))
167	defer server.Close()
168
169	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
170
171	note, err := client.NewNote().
172		WithName("Duplicate").
173		InNotebook(notebookID).
174		FromSource("evernote", "dup-123").
175		Create(ctx())
176	if err != nil {
177		t.Fatalf("error = %v, want nil", err)
178	}
179
180	if note != nil {
181		t.Errorf("note = %v, want nil for duplicate", note)
182	}
183}
184
185func TestCreateNote_Errors(t *testing.T) {
186	t.Parallel()
187
188	testErrorCases(t, func(c *lunatask.Client) error {
189		_, err := c.NewNote().WithName("x").Create(ctx())
190
191		return err //nolint:wrapcheck // test helper
192	})
193}
194
195// --- UpdateNote ---
196
197func TestUpdateNote_Success(t *testing.T) {
198	t.Parallel()
199
200	server, capture := newPUTServer(t, "/notes/"+noteID, singleNoteResponseBody)
201	defer server.Close()
202
203	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
204
205	note, err := client.NewNoteUpdate(noteID).
206		WithName("Updated name").
207		WithContent("New content").
208		Update(ctx())
209	if err != nil {
210		t.Fatalf("error = %v", err)
211	}
212
213	if note == nil {
214		t.Fatal("returned nil")
215	}
216
217	assertBodyField(t, capture.Body, "name", "Updated name")
218	assertBodyField(t, capture.Body, "content", "New content")
219}
220
221func TestUpdateNote_AllFields(t *testing.T) {
222	t.Parallel()
223
224	server, capture := newPUTServer(t, "/notes/"+noteID, singleNoteResponseBody)
225	defer server.Close()
226
227	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
228	dateOn := lunatask.NewDate(time.Date(2024, 5, 10, 0, 0, 0, 0, time.UTC))
229
230	_, err := client.NewNoteUpdate(noteID).
231		WithName("Full update").
232		WithContent("Full content").
233		InNotebook("new-notebook-id").
234		OnDate(dateOn).
235		Update(ctx())
236	if err != nil {
237		t.Fatalf("error = %v", err)
238	}
239
240	assertBodyField(t, capture.Body, "name", "Full update")
241	assertBodyField(t, capture.Body, "content", "Full content")
242	assertBodyField(t, capture.Body, "notebook_id", "new-notebook-id")
243	assertBodyField(t, capture.Body, "date_on", "2024-05-10")
244}
245
246func TestUpdateNote_Errors(t *testing.T) {
247	t.Parallel()
248
249	testErrorCases(t, func(c *lunatask.Client) error {
250		_, err := c.NewNoteUpdate(noteID).WithName("x").Update(ctx())
251
252		return err //nolint:wrapcheck // test helper
253	})
254}
255
256// --- DeleteNote ---
257
258func TestDeleteNote_Success(t *testing.T) {
259	t.Parallel()
260
261	server := newDELETEServer(t, "/notes/"+noteID, singleNoteResponseBody)
262	defer server.Close()
263
264	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
265
266	note, err := client.DeleteNote(ctx(), noteID)
267	if err != nil {
268		t.Fatalf("error = %v", err)
269	}
270
271	if note == nil {
272		t.Fatal("returned nil")
273	}
274
275	if note.ID != noteID {
276		t.Errorf("ID = %q, want %q", note.ID, noteID)
277	}
278}
279
280func TestDeleteNote_Errors(t *testing.T) {
281	t.Parallel()
282
283	testErrorCases(t, func(c *lunatask.Client) error {
284		_, err := c.DeleteNote(ctx(), noteID)
285
286		return err //nolint:wrapcheck // test helper
287	})
288}
289
290// --- Test Data ---
291
292const singleNoteResponseBody = `{
293	"note": {
294		"id": "5999b945-b2b1-48c6-aa72-b251b75b3c2e",
295		"notebook_id": "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1",
296		"date_on": null,
297		"pinned": false,
298		"sources": [{"source": "evernote", "source_id": "352fd2d7-cdc0-4e91-a0a3-9d6cc9d440e7"}],
299		"created_at": "2021-01-10T10:39:25Z",
300		"updated_at": "2021-01-10T10:39:25Z"
301	}
302}`
303
304const notesResponseBody = `{
305	"notes": [
306		{
307			"id": "5999b945-b2b1-48c6-aa72-b251b75b3c2e",
308			"notebook_id": "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1",
309			"date_on": null,
310			"pinned": false,
311			"sources": [{"source": "evernote", "source_id": "352fd2d7-cdc0-4e91-a0a3-9d6cc9d440e7"}],
312			"created_at": "2021-01-10T10:39:25Z",
313			"updated_at": "2021-01-10T10:39:25Z"
314		},
315		{
316			"id": "2ca8eb4c-4825-47e4-84de-2bbe0017b6c0",
317			"notebook_id": "fc2aa380-3320-4525-8611-7332d5060478",
318			"date_on": null,
319			"pinned": false,
320			"sources": [],
321			"created_at": "2021-01-13T08:12:25Z",
322			"updated_at": "2021-01-15T10:39:25Z"
323		}
324	]
325}`