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}`