import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import crypto from 'node:crypto'; import request from 'supertest'; import { setup } from './helpers.js'; import { stopBoss } from '../helpers/boss.js'; import * as queue from '../../lib/jobs/queue.js'; import { registerWorkers } from '../../lib/jobs/index.js'; import * as spaces from '../../lib/db/repos/spaces.js'; let app, sp; const SECRET = 'test-karakeep-secret'; function sign(body) { return 'sha256=' + crypto.createHmac('sha256', SECRET).update(body).digest('hex'); } beforeEach(async () => { ({ app } = await setup()); process.env.KARAKEEP_WEBHOOK_SECRET = SECRET; sp = await spaces.create({ slug: 'kw', name: 'KW' }, { kind: 'user', id: null }); process.env.KARAKEEP_DEFAULT_SPACE_ID = sp.id; await queue.start(); await registerWorkers(); }); afterEach(async () => { await stopBoss(); }); describe('karakeep webhook', () => { it('enqueues on valid signature', async () => { const body = JSON.stringify({ event: 'bookmark.created', bookmark_id: 'b-1' }); const res = await request(app).post('/api/ingest/karakeep') .set('X-Karakeep-Signature', sign(body)) .set('Content-Type', 'application/json') .send(body); expect(res.status).toBe(202); expect(res.body.job_id).toBeTruthy(); }); it('401 on bad signature', async () => { const res = await request(app).post('/api/ingest/karakeep') .set('X-Karakeep-Signature', 'sha256=wrong') .set('Content-Type', 'application/json') .send('{"event":"bookmark.created","bookmark_id":"b-1"}'); expect(res.status).toBe(401); }); it('401 on missing signature', async () => { const res = await request(app).post('/api/ingest/karakeep') .set('Content-Type', 'application/json') .send('{"event":"bookmark.created","bookmark_id":"b-1"}'); expect(res.status).toBe(401); }); it('non-bookmark.created → 202 skipped', async () => { const body = JSON.stringify({ event: 'bookmark.deleted', bookmark_id: 'b-1' }); const res = await request(app).post('/api/ingest/karakeep') .set('X-Karakeep-Signature', sign(body)) .set('Content-Type', 'application/json') .send(body); expect(res.status).toBe(202); expect(res.body.skipped).toBe(true); }); it('503 when KARAKEEP_DEFAULT_SPACE_ID not set', async () => { delete process.env.KARAKEEP_DEFAULT_SPACE_ID; const body = JSON.stringify({ event: 'bookmark.created', bookmark_id: 'b-2' }); const res = await request(app).post('/api/ingest/karakeep') .set('X-Karakeep-Signature', sign(body)) .set('Content-Type', 'application/json') .send(body); expect(res.status).toBe(503); }); });