From 8d8540e3206821e234deeb76661bad61af4a62a0 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Tue, 31 Mar 2026 13:05:30 -0700 Subject: [PATCH] fix(note-shares): treat expired shares as not found findShareByToken now returns null when expiresAt is in the past, aligning the public share API with stored metadata. Add unit tests for expiry, future expiry, missing expiry, and no match. Made-with: Cursor --- .../modules/note-shares/repository.test.ts | 54 +++++++++++++++++++ backend/src/modules/note-shares/repository.ts | 10 +++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 backend/src/modules/note-shares/repository.test.ts diff --git a/backend/src/modules/note-shares/repository.test.ts b/backend/src/modules/note-shares/repository.test.ts new file mode 100644 index 0000000..dceac3f --- /dev/null +++ b/backend/src/modules/note-shares/repository.test.ts @@ -0,0 +1,54 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const findManyMock = vi.fn(); + +vi.mock('../../lib/datastore.js', () => ({ + getCollection: vi.fn(() => ({ + findMany: findManyMock, + })), +})); + +import { findShareByToken } from './repository.js'; + +function shareDoc(overrides: { expiresAt?: string } = {}) { + return { + id: 's1', + productId: 'notelett', + workspaceId: 'w1', + userId: 'u1', + noteId: 'n1', + shareToken: 'tok', + createdAt: new Date().toISOString(), + ...overrides, + }; +} + +describe('findShareByToken', () => { + beforeEach(() => { + findManyMock.mockReset(); + }); + + it('returns null when expiresAt is in the past', async () => { + const past = new Date(Date.now() - 86_400_000).toISOString(); + findManyMock.mockResolvedValueOnce([shareDoc({ expiresAt: past })]); + await expect(findShareByToken('tok', 'notelett')).resolves.toBeNull(); + }); + + it('returns share when expiresAt is in the future', async () => { + const future = new Date(Date.now() + 86_400_000).toISOString(); + const doc = shareDoc({ expiresAt: future }); + findManyMock.mockResolvedValueOnce([doc]); + await expect(findShareByToken('tok', 'notelett')).resolves.toEqual(doc); + }); + + it('returns share when expiresAt is absent', async () => { + const doc = shareDoc(); + findManyMock.mockResolvedValueOnce([doc]); + await expect(findShareByToken('tok', 'notelett')).resolves.toEqual(doc); + }); + + it('returns null when no row matches', async () => { + findManyMock.mockResolvedValueOnce([]); + await expect(findShareByToken('tok', 'notelett')).resolves.toBeNull(); + }); +}); diff --git a/backend/src/modules/note-shares/repository.ts b/backend/src/modules/note-shares/repository.ts index 69aba65..56597de 100644 --- a/backend/src/modules/note-shares/repository.ts +++ b/backend/src/modules/note-shares/repository.ts @@ -16,7 +16,15 @@ export async function findShareByToken( ): Promise { const filter: FilterMap = { shareToken, productId }; const items = await collection().findMany({ filter, limit: 1 }); - return items[0] ?? null; + const share = items[0] ?? null; + if (!share) return null; + if (share.expiresAt) { + const exp = Date.parse(share.expiresAt); + if (!Number.isNaN(exp) && exp < Date.now()) { + return null; + } + } + return share; } export async function listSharesForNote(