diff --git a/lib/karakeep/client.js b/lib/karakeep/client.js new file mode 100644 index 0000000..f92f826 --- /dev/null +++ b/lib/karakeep/client.js @@ -0,0 +1,11 @@ +export async function getBookmark(id) { + const base = process.env.KARAKEEP_API_URL || 'https://karakeep.hynesy.com'; + const tok = process.env.KARAKEEP_API_TOKEN || ''; + const res = await fetch(`${base}/api/v1/bookmarks/${encodeURIComponent(id)}`, { + headers: { Authorization: 'Bearer ' + tok }, + signal: AbortSignal.timeout(10_000) + }); + if (res.status === 404) return null; + if (!res.ok) throw new Error(`karakeep ${res.status}`); + return res.json(); +} diff --git a/tests/karakeep/client.test.js b/tests/karakeep/client.test.js new file mode 100644 index 0000000..a9216ae --- /dev/null +++ b/tests/karakeep/client.test.js @@ -0,0 +1,35 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { getBookmark } from '../../lib/karakeep/client.js'; + +let lastInit; +beforeEach(() => { + lastInit = null; + global.fetch = vi.fn(async (url, init) => { + lastInit = init; + return new Response( + JSON.stringify({ id: 'b-1', url: 'https://example.com', title: 'X', tags: [{ name: 'archive' }] }), + { status: 200, headers: { 'content-type': 'application/json' }} + ); + }); +}); +afterEach(() => { vi.restoreAllMocks(); }); + +describe('karakeep client', () => { + it('fetches a bookmark with bearer token', async () => { + process.env.KARAKEEP_API_URL = 'https://karakeep.test'; + process.env.KARAKEEP_API_TOKEN = 'tok-123'; + const bm = await getBookmark('b-1'); + expect(bm.url).toBe('https://example.com'); + expect(lastInit.headers.Authorization).toBe('Bearer tok-123'); + }); + + it('returns null on 404', async () => { + global.fetch = vi.fn(async () => new Response('', { status: 404 })); + expect(await getBookmark('nope')).toBeNull(); + }); + + it('throws on other non-2xx', async () => { + global.fetch = vi.fn(async () => new Response('boom', { status: 503 })); + await expect(getBookmark('x')).rejects.toThrow(); + }); +});