diff --git a/src/routes/(app)/matters/[id]/+page.server.ts b/src/routes/(app)/matters/[id]/+page.server.ts index afe861d1..9edd6959 100644 --- a/src/routes/(app)/matters/[id]/+page.server.ts +++ b/src/routes/(app)/matters/[id]/+page.server.ts @@ -126,14 +126,17 @@ export const actions: Actions = { return { success: true }; }, + // Attach/detach go through the project-side endpoints (Wave D.1 T3). The legacy + // `PATCH /api/v1/knowledge-bases/{kb_id}` with {project_id} silently no-ops. linkKb: async (event) => { const data = await event.request.formData(); const kb_id = String(data.get('kb_id') ?? ''); if (!kb_id) return fail(400, { error: 'Missing kb_id.' }); - const res = await lqFetch(event, `/api/v1/knowledge-bases/${kb_id}`, { - method: 'PATCH', - body: JSON.stringify({ project_id: event.params.id }) + const res = await lqFetch(event, `/api/v1/projects/${event.params.id}/knowledge-bases`, { + method: 'POST', + body: JSON.stringify({ knowledge_base_id: kb_id }) }); + // 200 = attached (idempotent — repeat attach returns 200, not 409). if (!res.ok) { if (res.status === 404) return fail(404, { error: 'Knowledge base no longer exists.' }); return fail(502, { error: 'Could not link the knowledge base.' }); @@ -145,11 +148,12 @@ export const actions: Actions = { const data = await event.request.formData(); const kb_id = String(data.get('kb_id') ?? ''); if (!kb_id) return fail(400, { error: 'Missing kb_id.' }); - const res = await lqFetch(event, `/api/v1/knowledge-bases/${kb_id}`, { - method: 'PATCH', - body: JSON.stringify({ project_id: null }) - }); - // 200 + 404 → success (already gone is fine for the UI). + const res = await lqFetch( + event, + `/api/v1/projects/${event.params.id}/knowledge-bases/${kb_id}`, + { method: 'DELETE' } + ); + // 204 (idempotent, incl. not-attached) + 404 (project gone) → success for the UI. if (!res.ok && res.status !== 404) { return fail(502, { error: 'Could not unlink the knowledge base.' }); } diff --git a/src/routes/(app)/matters/[id]/page.server.test.ts b/src/routes/(app)/matters/[id]/page.server.test.ts index 2ae44b94..b7a3796c 100644 --- a/src/routes/(app)/matters/[id]/page.server.test.ts +++ b/src/routes/(app)/matters/[id]/page.server.test.ts @@ -373,13 +373,14 @@ const kbEvent = (kb_id: string, id = 'p1') => }) as never; describe('/matters/[id] linkKb / unlinkKb actions', () => { - it('linkKb PATCHes the KB with the matter id', async () => { + it('linkKb POSTs { knowledge_base_id } to the project attach endpoint', async () => { lqFetch.mockResolvedValue(new Response('{}', { status: 200 })); const r = await actions.linkKb(kbEvent('k1')); expect(r).toEqual({ success: true }); - expect(lqFetch.mock.calls[0][1]).toBe('/api/v1/knowledge-bases/k1'); - expect(lqFetch.mock.calls[0][2].method).toBe('PATCH'); - expect(JSON.parse(lqFetch.mock.calls[0][2].body)).toEqual({ project_id: 'p1' }); + expect(lqFetch).toHaveBeenCalledTimes(1); + expect(lqFetch.mock.calls[0][1]).toBe('/api/v1/projects/p1/knowledge-bases'); + expect(lqFetch.mock.calls[0][2].method).toBe('POST'); + expect(JSON.parse(lqFetch.mock.calls[0][2].body)).toEqual({ knowledge_base_id: 'k1' }); }); it('linkKb maps 404 to a friendly error', async () => { @@ -394,11 +395,13 @@ describe('/matters/[id] linkKb / unlinkKb actions', () => { expect(r).toMatchObject({ status: 502, data: { error: 'Could not link the knowledge base.' } }); }); - it('unlinkKb PATCHes the KB with project_id: null', async () => { - lqFetch.mockResolvedValue(new Response('{}', { status: 200 })); + it('unlinkKb DELETEs the project-scoped KB join', async () => { + lqFetch.mockResolvedValue(new Response(null, { status: 204 })); const r = await actions.unlinkKb(kbEvent('k1')); expect(r).toEqual({ success: true }); - expect(JSON.parse(lqFetch.mock.calls[0][2].body)).toEqual({ project_id: null }); + expect(lqFetch).toHaveBeenCalledTimes(1); + expect(lqFetch.mock.calls[0][1]).toBe('/api/v1/projects/p1/knowledge-bases/k1'); + expect(lqFetch.mock.calls[0][2].method).toBe('DELETE'); }); it('unlinkKb treats 404 as silent success', async () => { @@ -407,6 +410,16 @@ describe('/matters/[id] linkKb / unlinkKb actions', () => { expect(r).toEqual({ success: true }); }); + it('never calls the legacy KB PATCH endpoint (silent no-op upstream)', async () => { + lqFetch.mockResolvedValue(new Response(null, { status: 204 })); + await actions.linkKb(kbEvent('k1')); + await actions.unlinkKb(kbEvent('k1')); + for (const call of lqFetch.mock.calls) { + expect(call[1]).not.toBe('/api/v1/knowledge-bases/k1'); + expect(call[2].method).not.toBe('PATCH'); + } + }); + it('unlinkKb maps other failures to a 502', async () => { lqFetch.mockResolvedValue(new Response('boom', { status: 500 })); const r = await actions.unlinkKb(kbEvent('k1'));