diff --git a/server/src/nest/collections/collections.helpers.ts b/server/src/nest/collections/collections.helpers.ts new file mode 100644 index 000000000..9cf4d1330 --- /dev/null +++ b/server/src/nest/collections/collections.helpers.ts @@ -0,0 +1,22 @@ +import type { CollectionLink } from '@trek/shared'; + +/** + * Pure helpers of the collections domain, moved verbatim out of + * collections.service.ts. Nothing here touches the DB, so they stay plain + * exports rather than methods on CollectionsService. + */ + +/** Links are stored as a JSON TEXT column; parse on read, stringify on write. */ +export function parseLinks(raw: unknown): CollectionLink[] | undefined { + if (typeof raw !== 'string' || !raw) return undefined; + try { + const v = JSON.parse(raw); + return Array.isArray(v) ? (v as CollectionLink[]) : undefined; + } catch { + return undefined; + } +} + +export function serializeLinks(links: CollectionLink[] | undefined): string | null { + return links && links.length ? JSON.stringify(links) : null; +} diff --git a/server/src/nest/collections/collections.service.ts b/server/src/nest/collections/collections.service.ts index 7f5da39b4..4a347826f 100644 --- a/server/src/nest/collections/collections.service.ts +++ b/server/src/nest/collections/collections.service.ts @@ -62,7 +62,6 @@ import type { CollectionMember, CollectionMembership, CollectionPlace, - CollectionLink, CollectionCreateRequest, CollectionUpdateRequest, CollectionSavePlaceRequest, @@ -76,22 +75,7 @@ import type { import { NotificationsService } from '../notifications/notifications.service'; import { PlaceImportService, type ExportedCollectionFile } from '../place-import/place-import.service'; import { UnitOfWork } from '../database/unit-of-work'; - -/** Links are stored as a JSON TEXT column; parse on read, stringify on write. */ -function parseLinks(raw: unknown): CollectionLink[] | undefined { - if (typeof raw !== 'string' || !raw) return undefined; - try { - const v = JSON.parse(raw); - return Array.isArray(v) ? (v as CollectionLink[]) : undefined; - } catch { - return undefined; - } -} - -function serializeLinks(links: CollectionLink[] | undefined): string | null { - return links && links.length ? JSON.stringify(links) : null; -} - +import { parseLinks, serializeLinks } from './collections.helpers'; // --------------------------------------------------------------------------- // Errors — thrown as plain Errors carrying a status; TrekExceptionFilter maps diff --git a/server/tests/unit/nest/collections.helpers.test.ts b/server/tests/unit/nest/collections.helpers.test.ts new file mode 100644 index 000000000..c86cc722d --- /dev/null +++ b/server/tests/unit/nest/collections.helpers.test.ts @@ -0,0 +1,31 @@ +/** + * The link helpers of the collections domain, which read and write the JSON + * TEXT column a list or a saved place keeps its links in. + * COLLECTIONS-HELPERS-001 to 003. + */ +import { parseLinks, serializeLinks } from '../../../src/nest/collections/collections.helpers'; + +import { describe, expect, it } from 'vitest'; + +const links = [{ url: 'https://menu.example/', label: 'Menu' }]; + +describe('parseLinks', () => { + it('COLLECTIONS-HELPERS-001: reads a stored array of links back', () => { + expect(parseLinks(JSON.stringify(links))).toEqual(links); + }); + + it('COLLECTIONS-HELPERS-002: answers undefined for a missing, empty, unreadable or non-array value', () => { + expect(parseLinks(null)).toBeUndefined(); + expect(parseLinks('')).toBeUndefined(); + expect(parseLinks('{not json')).toBeUndefined(); + expect(parseLinks(JSON.stringify(links[0]))).toBeUndefined(); + }); +}); + +describe('serializeLinks', () => { + it('COLLECTIONS-HELPERS-003: writes links as JSON and no links as null', () => { + expect(serializeLinks(links)).toBe(JSON.stringify(links)); + expect(serializeLinks([])).toBeNull(); + expect(serializeLinks(undefined)).toBeNull(); + }); +});