Skip to content
Merged
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
17 changes: 14 additions & 3 deletions src/lib/stores/session-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,9 +839,20 @@ export const useSessionStore = create<SessionState>()(
set((state) => ({ savedCases: [savedItem, ...state.savedCases] }));
},
deleteSavedCase: async (id) => {
set((state) => ({ savedCases: state.savedCases.filter((c) => c.id !== id) }));
const userId = await getUserId();
await applyUserScopedIdFilter(supabase.from('cases').delete(), userId, id);
const previous = get().savedCases;
set({ savedCases: previous.filter((item) => item.id !== id) });
try {
const userId = await getUserId();
// A missing session must not report success for a row the server still has.
if (!userId) throw new Error('[SessionStore] Could not confirm the signed-in user to delete a case');
throwOnWriteError(
await applyUserScopedIdFilter(supabase.from('cases').delete(), userId, id),
'delete case',
);
} catch (error) {
set({ savedCases: previous });
throw error;
}
},
loadSavedCase: (id) => {
const item = get().savedCases.find((c) => c.id === id);
Expand Down
69 changes: 69 additions & 0 deletions src/test/stores/delete-saved-case.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const { deleteResult, getUser } = vi.hoisted(() => ({
deleteResult: { error: null as { message: string } | null },
getUser: vi.fn(async () => ({ data: { user: { id: 'user-1' } as { id: string } | null } })),
}));

vi.mock('@/lib/supabase/client', () => ({
supabase: {
auth: { getUser },
from: () => ({
delete: () => {
const query = {
eq: () => query,
then: (resolve: (value: { error: { message: string } | null }) => void, reject?: (reason: unknown) => void) => (
Promise.resolve({ error: deleteResult.error }).then(resolve, reject)
),
};
return query;
},
}),
},
}));

import type { GenerateOutput } from '@/lib/ai/schemas/generate';
import { useSessionStore } from '@/lib/stores/session-store';

const savedCase = {
id: 'case-1',
title: 'Saved motion',
createdAt: 1,
debateId: 'debate-1',
data: {
caseStructure: { thesis: 'Thesis', framework: '', keyDefinitions: [] as string[], burdenOfProof: '' },
contentions: [] as GenerateOutput['contentions'],
predictedOpponentCase: {
likelyArguments: [] as string[],
strategicWeaknesses: [] as string[],
keyClashes: [] as string[],
},
counters: [] as GenerateOutput['counters'],
metadata: { side: 'pro' as const, format: 'custom', generatedAt: '2026-01-01T00:00:00Z' },
} satisfies GenerateOutput,
};

describe('deleting a saved case', () => {
beforeEach(() => {
deleteResult.error = null;
getUser.mockResolvedValue({ data: { user: { id: 'user-1' } } });
useSessionStore.setState({ savedCases: [savedCase] });
});

it('removes the case after the server delete succeeds', async () => {
await useSessionStore.getState().deleteSavedCase('case-1');
expect(useSessionStore.getState().savedCases).toEqual([]);
});

it('puts the case back and reports the failure when the server delete fails', async () => {
deleteResult.error = { message: 'permission denied' };
await expect(useSessionStore.getState().deleteSavedCase('case-1')).rejects.toThrow(/delete case/);
expect(useSessionStore.getState().savedCases).toEqual([savedCase]);
});

it('puts the case back when the session cannot be confirmed', async () => {
getUser.mockResolvedValueOnce({ data: { user: null } });
await expect(useSessionStore.getState().deleteSavedCase('case-1')).rejects.toThrow(/signed-in user/);
expect(useSessionStore.getState().savedCases).toEqual([savedCase]);
});
});
Loading