Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/routes/(app)/matters/[id]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.' });
Expand All @@ -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.' });
}
Expand Down
27 changes: 20 additions & 7 deletions src/routes/(app)/matters/[id]/page.server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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'));
Expand Down