trilium-api 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,477 +1,477 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { createTriliumClient } from './client.js';
3
-
4
- // Mock fetch globally - openapi-fetch uses Request objects
5
- const mockFetch = vi.fn();
6
- globalThis.fetch = mockFetch;
7
-
8
- // Helper to create a proper Response mock
9
- function createMockResponse(body: any, status = 200, contentType = 'application/json') {
10
- return {
11
- ok: status >= 200 && status < 300,
12
- status,
13
- headers: new Headers({ 'content-type': contentType }),
14
- json: async () => body,
15
- text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
16
- blob: async () => new Blob([JSON.stringify(body)]),
17
- clone: function() { return this; },
18
- };
19
- }
20
-
21
- describe('createTriliumClient', () => {
22
- const config = {
23
- baseUrl: 'http://localhost:37840',
24
- apiKey: 'test-api-key',
25
- };
26
-
27
- beforeEach(() => {
28
- mockFetch.mockReset();
29
- });
30
-
31
- afterEach(() => {
32
- vi.restoreAllMocks();
33
- });
34
-
35
- describe('client creation', () => {
36
- it('should create a client with correct baseUrl', () => {
37
- const client = createTriliumClient(config);
38
- expect(client).toBeDefined();
39
- expect(typeof client.GET).toBe('function');
40
- expect(typeof client.POST).toBe('function');
41
- expect(typeof client.PATCH).toBe('function');
42
- expect(typeof client.DELETE).toBe('function');
43
- });
44
-
45
- it('should handle trailing slash in baseUrl', () => {
46
- const clientWithSlash = createTriliumClient({
47
- baseUrl: 'http://localhost:37840/',
48
- apiKey: 'test-api-key',
49
- });
50
- expect(clientWithSlash).toBeDefined();
51
- });
52
- });
53
-
54
- describe('GET /app-info', () => {
55
- it('should fetch app info', async () => {
56
- const mockAppInfo = {
57
- appVersion: '0.60.0',
58
- dbVersion: 220,
59
- syncVersion: 30,
60
- buildDate: '2024-01-01T00:00:00Z',
61
- buildRevision: 'abc123',
62
- dataDirectory: '/home/user/data',
63
- clipperProtocolVersion: 1,
64
- utcDateTime: '2024-01-01T12:00:00Z',
65
- };
66
-
67
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAppInfo));
68
-
69
- const client = createTriliumClient(config);
70
- const { data, error } = await client.GET('/app-info');
71
-
72
- expect(error).toBeUndefined();
73
- expect(data).toEqual(mockAppInfo);
74
- expect(mockFetch).toHaveBeenCalledTimes(1);
75
-
76
- // Check the Request object
77
- const request = mockFetch.mock.calls[0]![0] as Request;
78
- expect(request.url).toBe('http://localhost:37840/etapi/app-info');
79
- expect(request.method).toBe('GET');
80
- });
81
- });
82
-
83
- describe('GET /notes/{noteId}', () => {
84
- it('should fetch a note by ID', async () => {
85
- const mockNote = {
86
- noteId: 'test123',
87
- title: 'Test Note',
88
- type: 'text',
89
- mime: 'text/html',
90
- isProtected: false,
91
- blobId: 'blob123',
92
- attributes: [],
93
- parentNoteIds: ['root'],
94
- childNoteIds: [],
95
- parentBranchIds: ['branch123'],
96
- childBranchIds: [],
97
- dateCreated: '2024-01-01 12:00:00.000+0000',
98
- dateModified: '2024-01-01 12:00:00.000+0000',
99
- utcDateCreated: '2024-01-01 12:00:00.000Z',
100
- utcDateModified: '2024-01-01 12:00:00.000Z',
101
- };
102
-
103
- mockFetch.mockResolvedValueOnce(createMockResponse(mockNote));
104
-
105
- const client = createTriliumClient(config);
106
- const { data, error } = await client.GET('/notes/{noteId}', {
107
- params: { path: { noteId: 'test123' } },
108
- });
109
-
110
- expect(error).toBeUndefined();
111
- expect(data).toEqual(mockNote);
112
-
113
- const request = mockFetch.mock.calls[0]![0] as Request;
114
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
115
- expect(request.method).toBe('GET');
116
- });
117
-
118
- it('should handle 404 error when note not found', async () => {
119
- const errorResponse = { status: 404, code: 'NOTE_NOT_FOUND', message: 'Note not found' };
120
- mockFetch.mockResolvedValueOnce(createMockResponse(errorResponse, 404));
121
-
122
- const client = createTriliumClient(config);
123
- const { data, error } = await client.GET('/notes/{noteId}', {
124
- params: { path: { noteId: 'nonexistent' } },
125
- });
126
-
127
- expect(data).toBeUndefined();
128
- expect(error).toBeDefined();
129
- });
130
- });
131
-
132
- describe('GET /notes (search)', () => {
133
- it('should search notes with query', async () => {
134
- const mockResults = {
135
- results: [
136
- { noteId: 'note1', title: 'Blog Post 1' },
137
- { noteId: 'note2', title: 'Blog Post 2' },
138
- ],
139
- };
140
-
141
- mockFetch.mockResolvedValueOnce(createMockResponse(mockResults));
142
-
143
- const client = createTriliumClient(config);
144
- const { data, error } = await client.GET('/notes', {
145
- params: { query: { search: '#blog' } },
146
- });
147
-
148
- expect(error).toBeUndefined();
149
- expect(data).toEqual(mockResults);
150
-
151
- const request = mockFetch.mock.calls[0]![0] as Request;
152
- expect(request.url).toContain('search=%23blog');
153
- });
154
-
155
- it('should search with limit parameter', async () => {
156
- mockFetch.mockResolvedValueOnce(createMockResponse({ results: [] }));
157
-
158
- const client = createTriliumClient(config);
159
- await client.GET('/notes', {
160
- params: { query: { search: 'test', limit: 10 } },
161
- });
162
-
163
- const request = mockFetch.mock.calls[0]![0] as Request;
164
- expect(request.url).toContain('limit=10');
165
- });
166
- });
167
-
168
- describe('POST /create-note', () => {
169
- it('should create a new note', async () => {
170
- const mockCreatedNote = {
171
- note: {
172
- noteId: 'newNote123',
173
- title: 'New Note',
174
- type: 'text',
175
- mime: 'text/html',
176
- isProtected: false,
177
- },
178
- branch: {
179
- branchId: 'branch456',
180
- noteId: 'newNote123',
181
- parentNoteId: 'root',
182
- notePosition: 10,
183
- isExpanded: false,
184
- },
185
- };
186
-
187
- mockFetch.mockResolvedValueOnce(createMockResponse(mockCreatedNote, 201));
188
-
189
- const client = createTriliumClient(config);
190
- const { data, error } = await client.POST('/create-note', {
191
- body: {
192
- parentNoteId: 'root',
193
- title: 'New Note',
194
- type: 'text',
195
- content: '<p>Hello World</p>',
196
- },
197
- });
198
-
199
- expect(error).toBeUndefined();
200
- expect(data).toEqual(mockCreatedNote);
201
-
202
- const request = mockFetch.mock.calls[0]![0] as Request;
203
- expect(request.url).toBe('http://localhost:37840/etapi/create-note');
204
- expect(request.method).toBe('POST');
205
- });
206
- });
207
-
208
- describe('PATCH /notes/{noteId}', () => {
209
- it('should patch a note', async () => {
210
- const mockUpdatedNote = {
211
- noteId: 'test123',
212
- title: 'Updated Title',
213
- type: 'text',
214
- mime: 'text/html',
215
- };
216
-
217
- mockFetch.mockResolvedValueOnce(createMockResponse(mockUpdatedNote));
218
-
219
- const client = createTriliumClient(config);
220
- const { data, error } = await client.PATCH('/notes/{noteId}', {
221
- params: { path: { noteId: 'test123' } },
222
- body: { title: 'Updated Title' },
223
- });
224
-
225
- expect(error).toBeUndefined();
226
- expect(data?.title).toBe('Updated Title');
227
-
228
- const request = mockFetch.mock.calls[0]![0] as Request;
229
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
230
- expect(request.method).toBe('PATCH');
231
- });
232
- });
233
-
234
- describe('DELETE /notes/{noteId}', () => {
235
- it('should delete a note', async () => {
236
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
237
-
238
- const client = createTriliumClient(config);
239
- const { error } = await client.DELETE('/notes/{noteId}', {
240
- params: { path: { noteId: 'test123' } },
241
- });
242
-
243
- expect(error).toBeUndefined();
244
-
245
- const request = mockFetch.mock.calls[0]![0] as Request;
246
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
247
- expect(request.method).toBe('DELETE');
248
- });
249
- });
250
-
251
- describe('GET /notes/{noteId}/content', () => {
252
- it('should fetch note content', async () => {
253
- const mockContent = '<p>This is the note content</p>';
254
-
255
- mockFetch.mockResolvedValueOnce(createMockResponse(mockContent, 200, 'text/html'));
256
-
257
- const client = createTriliumClient(config);
258
- const { data, error } = await client.GET('/notes/{noteId}/content', {
259
- params: { path: { noteId: 'test123' } },
260
- });
261
-
262
- expect(error).toBeUndefined();
263
- // Content comes back as parsed
264
- expect(data).toBeDefined();
265
- });
266
- });
267
-
268
- describe('PUT /notes/{noteId}/content', () => {
269
- it('should update note content', async () => {
270
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
271
-
272
- const client = createTriliumClient(config);
273
- const { error } = await client.PUT('/notes/{noteId}/content', {
274
- params: { path: { noteId: 'test123' } },
275
- body: '<p>Updated content</p>' as any,
276
- });
277
-
278
- expect(error).toBeUndefined();
279
-
280
- const request = mockFetch.mock.calls[0]![0] as Request;
281
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123/content');
282
- expect(request.method).toBe('PUT');
283
- });
284
- });
285
-
286
- describe('Branches API', () => {
287
- it('should create a branch', async () => {
288
- const mockBranch = {
289
- branchId: 'branch123',
290
- noteId: 'note123',
291
- parentNoteId: 'parent123',
292
- notePosition: 10,
293
- prefix: '',
294
- isExpanded: false,
295
- };
296
-
297
- mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch, 201));
298
-
299
- const client = createTriliumClient(config);
300
- const { data, error } = await client.POST('/branches', {
301
- body: {
302
- noteId: 'note123',
303
- parentNoteId: 'parent123',
304
- },
305
- });
306
-
307
- expect(error).toBeUndefined();
308
- expect(data).toEqual(mockBranch);
309
- });
310
-
311
- it('should get a branch by ID', async () => {
312
- const mockBranch = {
313
- branchId: 'branch123',
314
- noteId: 'note123',
315
- parentNoteId: 'root',
316
- };
317
-
318
- mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch));
319
-
320
- const client = createTriliumClient(config);
321
- const { data, error } = await client.GET('/branches/{branchId}', {
322
- params: { path: { branchId: 'branch123' } },
323
- });
324
-
325
- expect(error).toBeUndefined();
326
- expect(data).toEqual(mockBranch);
327
- });
328
-
329
- it('should delete a branch', async () => {
330
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
331
-
332
- const client = createTriliumClient(config);
333
- const { error } = await client.DELETE('/branches/{branchId}', {
334
- params: { path: { branchId: 'branch123' } },
335
- });
336
-
337
- expect(error).toBeUndefined();
338
- });
339
- });
340
-
341
- describe('Attributes API', () => {
342
- it('should create an attribute', async () => {
343
- const mockAttribute = {
344
- attributeId: 'attr123',
345
- noteId: 'note123',
346
- type: 'label',
347
- name: 'testLabel',
348
- value: 'testValue',
349
- position: 10,
350
- isInheritable: false,
351
- };
352
-
353
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute, 201));
354
-
355
- const client = createTriliumClient(config);
356
- const { data, error } = await client.POST('/attributes', {
357
- body: {
358
- noteId: 'note123',
359
- type: 'label',
360
- name: 'testLabel',
361
- value: 'testValue',
362
- },
363
- });
364
-
365
- expect(error).toBeUndefined();
366
- expect(data).toEqual(mockAttribute);
367
- });
368
-
369
- it('should get an attribute by ID', async () => {
370
- const mockAttribute = {
371
- attributeId: 'attr123',
372
- noteId: 'note123',
373
- type: 'label',
374
- name: 'testLabel',
375
- value: 'testValue',
376
- };
377
-
378
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute));
379
-
380
- const client = createTriliumClient(config);
381
- const { data, error } = await client.GET('/attributes/{attributeId}', {
382
- params: { path: { attributeId: 'attr123' } },
383
- });
384
-
385
- expect(error).toBeUndefined();
386
- expect(data).toEqual(mockAttribute);
387
- });
388
-
389
- it('should delete an attribute', async () => {
390
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
391
-
392
- const client = createTriliumClient(config);
393
- const { error } = await client.DELETE('/attributes/{attributeId}', {
394
- params: { path: { attributeId: 'attr123' } },
395
- });
396
-
397
- expect(error).toBeUndefined();
398
- });
399
- });
400
-
401
- describe('Attachments API', () => {
402
- it('should get an attachment by ID', async () => {
403
- const mockAttachment = {
404
- attachmentId: 'attach123',
405
- ownerId: 'note123',
406
- role: 'file',
407
- mime: 'image/png',
408
- title: 'test.png',
409
- position: 10,
410
- blobId: 'blob123',
411
- };
412
-
413
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttachment));
414
-
415
- const client = createTriliumClient(config);
416
- const { data, error } = await client.GET('/attachments/{attachmentId}', {
417
- params: { path: { attachmentId: 'attach123' } },
418
- });
419
-
420
- expect(error).toBeUndefined();
421
- expect(data).toEqual(mockAttachment);
422
- });
423
-
424
- it('should delete an attachment', async () => {
425
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
426
-
427
- const client = createTriliumClient(config);
428
- const { error } = await client.DELETE('/attachments/{attachmentId}', {
429
- params: { path: { attachmentId: 'attach123' } },
430
- });
431
-
432
- expect(error).toBeUndefined();
433
- });
434
- });
435
-
436
- describe('Authorization header', () => {
437
- it('should include Authorization header in requests', async () => {
438
- mockFetch.mockResolvedValueOnce(createMockResponse({}));
439
-
440
- const client = createTriliumClient(config);
441
- await client.GET('/app-info');
442
-
443
- const request = mockFetch.mock.calls[0]![0] as Request;
444
- expect(request.headers.get('Authorization')).toBe('test-api-key');
445
- });
446
- });
447
-
448
- describe('Error handling', () => {
449
- it('should handle network errors', async () => {
450
- mockFetch.mockRejectedValueOnce(new Error('Network error'));
451
-
452
- const client = createTriliumClient(config);
453
-
454
- await expect(client.GET('/app-info')).rejects.toThrow('Network error');
455
- });
456
-
457
- it('should handle 500 server errors', async () => {
458
- mockFetch.mockResolvedValueOnce(createMockResponse({ status: 500, message: 'Internal Server Error' }, 500));
459
-
460
- const client = createTriliumClient(config);
461
- const { data, error } = await client.GET('/app-info');
462
-
463
- expect(data).toBeUndefined();
464
- expect(error).toBeDefined();
465
- });
466
-
467
- it('should handle 401 unauthorized errors', async () => {
468
- mockFetch.mockResolvedValueOnce(createMockResponse({ status: 401, message: 'Unauthorized' }, 401));
469
-
470
- const client = createTriliumClient(config);
471
- const { data, error } = await client.GET('/app-info');
472
-
473
- expect(data).toBeUndefined();
474
- expect(error).toBeDefined();
475
- });
476
- });
477
- });
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createTriliumClient } from './client.js';
3
+
4
+ // Mock fetch globally - openapi-fetch uses Request objects
5
+ const mockFetch = vi.fn();
6
+ globalThis.fetch = mockFetch;
7
+
8
+ // Helper to create a proper Response mock
9
+ function createMockResponse(body: any, status = 200, contentType = 'application/json') {
10
+ return {
11
+ ok: status >= 200 && status < 300,
12
+ status,
13
+ headers: new Headers({ 'content-type': contentType }),
14
+ json: async () => body,
15
+ text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
16
+ blob: async () => new Blob([JSON.stringify(body)]),
17
+ clone: function() { return this; },
18
+ };
19
+ }
20
+
21
+ describe('createTriliumClient', () => {
22
+ const config = {
23
+ baseUrl: 'http://localhost:37840',
24
+ apiKey: 'test-api-key',
25
+ };
26
+
27
+ beforeEach(() => {
28
+ mockFetch.mockReset();
29
+ });
30
+
31
+ afterEach(() => {
32
+ vi.restoreAllMocks();
33
+ });
34
+
35
+ describe('client creation', () => {
36
+ it('should create a client with correct baseUrl', () => {
37
+ const client = createTriliumClient(config);
38
+ expect(client).toBeDefined();
39
+ expect(typeof client.GET).toBe('function');
40
+ expect(typeof client.POST).toBe('function');
41
+ expect(typeof client.PATCH).toBe('function');
42
+ expect(typeof client.DELETE).toBe('function');
43
+ });
44
+
45
+ it('should handle trailing slash in baseUrl', () => {
46
+ const clientWithSlash = createTriliumClient({
47
+ baseUrl: 'http://localhost:37840/',
48
+ apiKey: 'test-api-key',
49
+ });
50
+ expect(clientWithSlash).toBeDefined();
51
+ });
52
+ });
53
+
54
+ describe('GET /app-info', () => {
55
+ it('should fetch app info', async () => {
56
+ const mockAppInfo = {
57
+ appVersion: '0.60.0',
58
+ dbVersion: 220,
59
+ syncVersion: 30,
60
+ buildDate: '2024-01-01T00:00:00Z',
61
+ buildRevision: 'abc123',
62
+ dataDirectory: '/home/user/data',
63
+ clipperProtocolVersion: 1,
64
+ utcDateTime: '2024-01-01T12:00:00Z',
65
+ };
66
+
67
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockAppInfo));
68
+
69
+ const client = createTriliumClient(config);
70
+ const { data, error } = await client.GET('/app-info');
71
+
72
+ expect(error).toBeUndefined();
73
+ expect(data).toEqual(mockAppInfo);
74
+ expect(mockFetch).toHaveBeenCalledTimes(1);
75
+
76
+ // Check the Request object
77
+ const request = mockFetch.mock.calls[0]![0] as Request;
78
+ expect(request.url).toBe('http://localhost:37840/etapi/app-info');
79
+ expect(request.method).toBe('GET');
80
+ });
81
+ });
82
+
83
+ describe('GET /notes/{noteId}', () => {
84
+ it('should fetch a note by ID', async () => {
85
+ const mockNote = {
86
+ noteId: 'test123',
87
+ title: 'Test Note',
88
+ type: 'text',
89
+ mime: 'text/html',
90
+ isProtected: false,
91
+ blobId: 'blob123',
92
+ attributes: [],
93
+ parentNoteIds: ['root'],
94
+ childNoteIds: [],
95
+ parentBranchIds: ['branch123'],
96
+ childBranchIds: [],
97
+ dateCreated: '2024-01-01 12:00:00.000+0000',
98
+ dateModified: '2024-01-01 12:00:00.000+0000',
99
+ utcDateCreated: '2024-01-01 12:00:00.000Z',
100
+ utcDateModified: '2024-01-01 12:00:00.000Z',
101
+ };
102
+
103
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockNote));
104
+
105
+ const client = createTriliumClient(config);
106
+ const { data, error } = await client.GET('/notes/{noteId}', {
107
+ params: { path: { noteId: 'test123' } },
108
+ });
109
+
110
+ expect(error).toBeUndefined();
111
+ expect(data).toEqual(mockNote);
112
+
113
+ const request = mockFetch.mock.calls[0]![0] as Request;
114
+ expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
115
+ expect(request.method).toBe('GET');
116
+ });
117
+
118
+ it('should handle 404 error when note not found', async () => {
119
+ const errorResponse = { status: 404, code: 'NOTE_NOT_FOUND', message: 'Note not found' };
120
+ mockFetch.mockResolvedValueOnce(createMockResponse(errorResponse, 404));
121
+
122
+ const client = createTriliumClient(config);
123
+ const { data, error } = await client.GET('/notes/{noteId}', {
124
+ params: { path: { noteId: 'nonexistent' } },
125
+ });
126
+
127
+ expect(data).toBeUndefined();
128
+ expect(error).toBeDefined();
129
+ });
130
+ });
131
+
132
+ describe('GET /notes (search)', () => {
133
+ it('should search notes with query', async () => {
134
+ const mockResults = {
135
+ results: [
136
+ { noteId: 'note1', title: 'Blog Post 1' },
137
+ { noteId: 'note2', title: 'Blog Post 2' },
138
+ ],
139
+ };
140
+
141
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockResults));
142
+
143
+ const client = createTriliumClient(config);
144
+ const { data, error } = await client.GET('/notes', {
145
+ params: { query: { search: '#blog' } },
146
+ });
147
+
148
+ expect(error).toBeUndefined();
149
+ expect(data).toEqual(mockResults);
150
+
151
+ const request = mockFetch.mock.calls[0]![0] as Request;
152
+ expect(request.url).toContain('search=%23blog');
153
+ });
154
+
155
+ it('should search with limit parameter', async () => {
156
+ mockFetch.mockResolvedValueOnce(createMockResponse({ results: [] }));
157
+
158
+ const client = createTriliumClient(config);
159
+ await client.GET('/notes', {
160
+ params: { query: { search: 'test', limit: 10 } },
161
+ });
162
+
163
+ const request = mockFetch.mock.calls[0]![0] as Request;
164
+ expect(request.url).toContain('limit=10');
165
+ });
166
+ });
167
+
168
+ describe('POST /create-note', () => {
169
+ it('should create a new note', async () => {
170
+ const mockCreatedNote = {
171
+ note: {
172
+ noteId: 'newNote123',
173
+ title: 'New Note',
174
+ type: 'text',
175
+ mime: 'text/html',
176
+ isProtected: false,
177
+ },
178
+ branch: {
179
+ branchId: 'branch456',
180
+ noteId: 'newNote123',
181
+ parentNoteId: 'root',
182
+ notePosition: 10,
183
+ isExpanded: false,
184
+ },
185
+ };
186
+
187
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockCreatedNote, 201));
188
+
189
+ const client = createTriliumClient(config);
190
+ const { data, error } = await client.POST('/create-note', {
191
+ body: {
192
+ parentNoteId: 'root',
193
+ title: 'New Note',
194
+ type: 'text',
195
+ content: '<p>Hello World</p>',
196
+ },
197
+ });
198
+
199
+ expect(error).toBeUndefined();
200
+ expect(data).toEqual(mockCreatedNote);
201
+
202
+ const request = mockFetch.mock.calls[0]![0] as Request;
203
+ expect(request.url).toBe('http://localhost:37840/etapi/create-note');
204
+ expect(request.method).toBe('POST');
205
+ });
206
+ });
207
+
208
+ describe('PATCH /notes/{noteId}', () => {
209
+ it('should patch a note', async () => {
210
+ const mockUpdatedNote = {
211
+ noteId: 'test123',
212
+ title: 'Updated Title',
213
+ type: 'text',
214
+ mime: 'text/html',
215
+ };
216
+
217
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockUpdatedNote));
218
+
219
+ const client = createTriliumClient(config);
220
+ const { data, error } = await client.PATCH('/notes/{noteId}', {
221
+ params: { path: { noteId: 'test123' } },
222
+ body: { title: 'Updated Title' },
223
+ });
224
+
225
+ expect(error).toBeUndefined();
226
+ expect(data?.title).toBe('Updated Title');
227
+
228
+ const request = mockFetch.mock.calls[0]![0] as Request;
229
+ expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
230
+ expect(request.method).toBe('PATCH');
231
+ });
232
+ });
233
+
234
+ describe('DELETE /notes/{noteId}', () => {
235
+ it('should delete a note', async () => {
236
+ mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
237
+
238
+ const client = createTriliumClient(config);
239
+ const { error } = await client.DELETE('/notes/{noteId}', {
240
+ params: { path: { noteId: 'test123' } },
241
+ });
242
+
243
+ expect(error).toBeUndefined();
244
+
245
+ const request = mockFetch.mock.calls[0]![0] as Request;
246
+ expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
247
+ expect(request.method).toBe('DELETE');
248
+ });
249
+ });
250
+
251
+ describe('GET /notes/{noteId}/content', () => {
252
+ it('should fetch note content', async () => {
253
+ const mockContent = '<p>This is the note content</p>';
254
+
255
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockContent, 200, 'text/html'));
256
+
257
+ const client = createTriliumClient(config);
258
+ const { data, error } = await client.GET('/notes/{noteId}/content', {
259
+ params: { path: { noteId: 'test123' } },
260
+ });
261
+
262
+ expect(error).toBeUndefined();
263
+ // Content comes back as parsed
264
+ expect(data).toBeDefined();
265
+ });
266
+ });
267
+
268
+ describe('PUT /notes/{noteId}/content', () => {
269
+ it('should update note content', async () => {
270
+ mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
271
+
272
+ const client = createTriliumClient(config);
273
+ const { error } = await client.PUT('/notes/{noteId}/content', {
274
+ params: { path: { noteId: 'test123' } },
275
+ body: '<p>Updated content</p>' as any,
276
+ });
277
+
278
+ expect(error).toBeUndefined();
279
+
280
+ const request = mockFetch.mock.calls[0]![0] as Request;
281
+ expect(request.url).toBe('http://localhost:37840/etapi/notes/test123/content');
282
+ expect(request.method).toBe('PUT');
283
+ });
284
+ });
285
+
286
+ describe('Branches API', () => {
287
+ it('should create a branch', async () => {
288
+ const mockBranch = {
289
+ branchId: 'branch123',
290
+ noteId: 'note123',
291
+ parentNoteId: 'parent123',
292
+ notePosition: 10,
293
+ prefix: '',
294
+ isExpanded: false,
295
+ };
296
+
297
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch, 201));
298
+
299
+ const client = createTriliumClient(config);
300
+ const { data, error } = await client.POST('/branches', {
301
+ body: {
302
+ noteId: 'note123',
303
+ parentNoteId: 'parent123',
304
+ },
305
+ });
306
+
307
+ expect(error).toBeUndefined();
308
+ expect(data).toEqual(mockBranch);
309
+ });
310
+
311
+ it('should get a branch by ID', async () => {
312
+ const mockBranch = {
313
+ branchId: 'branch123',
314
+ noteId: 'note123',
315
+ parentNoteId: 'root',
316
+ };
317
+
318
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch));
319
+
320
+ const client = createTriliumClient(config);
321
+ const { data, error } = await client.GET('/branches/{branchId}', {
322
+ params: { path: { branchId: 'branch123' } },
323
+ });
324
+
325
+ expect(error).toBeUndefined();
326
+ expect(data).toEqual(mockBranch);
327
+ });
328
+
329
+ it('should delete a branch', async () => {
330
+ mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
331
+
332
+ const client = createTriliumClient(config);
333
+ const { error } = await client.DELETE('/branches/{branchId}', {
334
+ params: { path: { branchId: 'branch123' } },
335
+ });
336
+
337
+ expect(error).toBeUndefined();
338
+ });
339
+ });
340
+
341
+ describe('Attributes API', () => {
342
+ it('should create an attribute', async () => {
343
+ const mockAttribute = {
344
+ attributeId: 'attr123',
345
+ noteId: 'note123',
346
+ type: 'label',
347
+ name: 'testLabel',
348
+ value: 'testValue',
349
+ position: 10,
350
+ isInheritable: false,
351
+ };
352
+
353
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute, 201));
354
+
355
+ const client = createTriliumClient(config);
356
+ const { data, error } = await client.POST('/attributes', {
357
+ body: {
358
+ noteId: 'note123',
359
+ type: 'label',
360
+ name: 'testLabel',
361
+ value: 'testValue',
362
+ },
363
+ });
364
+
365
+ expect(error).toBeUndefined();
366
+ expect(data).toEqual(mockAttribute);
367
+ });
368
+
369
+ it('should get an attribute by ID', async () => {
370
+ const mockAttribute = {
371
+ attributeId: 'attr123',
372
+ noteId: 'note123',
373
+ type: 'label',
374
+ name: 'testLabel',
375
+ value: 'testValue',
376
+ };
377
+
378
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute));
379
+
380
+ const client = createTriliumClient(config);
381
+ const { data, error } = await client.GET('/attributes/{attributeId}', {
382
+ params: { path: { attributeId: 'attr123' } },
383
+ });
384
+
385
+ expect(error).toBeUndefined();
386
+ expect(data).toEqual(mockAttribute);
387
+ });
388
+
389
+ it('should delete an attribute', async () => {
390
+ mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
391
+
392
+ const client = createTriliumClient(config);
393
+ const { error } = await client.DELETE('/attributes/{attributeId}', {
394
+ params: { path: { attributeId: 'attr123' } },
395
+ });
396
+
397
+ expect(error).toBeUndefined();
398
+ });
399
+ });
400
+
401
+ describe('Attachments API', () => {
402
+ it('should get an attachment by ID', async () => {
403
+ const mockAttachment = {
404
+ attachmentId: 'attach123',
405
+ ownerId: 'note123',
406
+ role: 'file',
407
+ mime: 'image/png',
408
+ title: 'test.png',
409
+ position: 10,
410
+ blobId: 'blob123',
411
+ };
412
+
413
+ mockFetch.mockResolvedValueOnce(createMockResponse(mockAttachment));
414
+
415
+ const client = createTriliumClient(config);
416
+ const { data, error } = await client.GET('/attachments/{attachmentId}', {
417
+ params: { path: { attachmentId: 'attach123' } },
418
+ });
419
+
420
+ expect(error).toBeUndefined();
421
+ expect(data).toEqual(mockAttachment);
422
+ });
423
+
424
+ it('should delete an attachment', async () => {
425
+ mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
426
+
427
+ const client = createTriliumClient(config);
428
+ const { error } = await client.DELETE('/attachments/{attachmentId}', {
429
+ params: { path: { attachmentId: 'attach123' } },
430
+ });
431
+
432
+ expect(error).toBeUndefined();
433
+ });
434
+ });
435
+
436
+ describe('Authorization header', () => {
437
+ it('should include Authorization header in requests', async () => {
438
+ mockFetch.mockResolvedValueOnce(createMockResponse({}));
439
+
440
+ const client = createTriliumClient(config);
441
+ await client.GET('/app-info');
442
+
443
+ const request = mockFetch.mock.calls[0]![0] as Request;
444
+ expect(request.headers.get('Authorization')).toBe('test-api-key');
445
+ });
446
+ });
447
+
448
+ describe('Error handling', () => {
449
+ it('should handle network errors', async () => {
450
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
451
+
452
+ const client = createTriliumClient(config);
453
+
454
+ await expect(client.GET('/app-info')).rejects.toThrow('Network error');
455
+ });
456
+
457
+ it('should handle 500 server errors', async () => {
458
+ mockFetch.mockResolvedValueOnce(createMockResponse({ status: 500, message: 'Internal Server Error' }, 500));
459
+
460
+ const client = createTriliumClient(config);
461
+ const { data, error } = await client.GET('/app-info');
462
+
463
+ expect(data).toBeUndefined();
464
+ expect(error).toBeDefined();
465
+ });
466
+
467
+ it('should handle 401 unauthorized errors', async () => {
468
+ mockFetch.mockResolvedValueOnce(createMockResponse({ status: 401, message: 'Unauthorized' }, 401));
469
+
470
+ const client = createTriliumClient(config);
471
+ const { data, error } = await client.GET('/app-info');
472
+
473
+ expect(data).toBeUndefined();
474
+ expect(error).toBeDefined();
475
+ });
476
+ });
477
+ });