Files
TREK/server/tests/unit/db/repositories/PackingItems.repository.test.ts
T
Maurice d28e91d056 test(server): seed the repository specs through the ORM factories
The repository suites now write their fixtures and read back what a call
left behind through the factories and readers, not hand-written SQLite on
the better-sqlite3 handle. What stays raw is marked with test-sql-allow:
the legacy statements the parity cases hold a repository read to, rows
read as the table stores them (a BLOB, an integer the entity maps to a
boolean), and a write that has to land on the connection an open
transaction holds.

Cases that pinned a "raw UPDATE" between two repository reads now make
that write through a context of their own, which the repository's
identity map knows as little about, so they still prove the same thing.
2026-10-08 18:46:13 +02:00

104 lines
4.7 KiB
TypeScript

/**
* Plan 4 Task 8b-4a (3h L4) — full-key `toEqual(<legacy raw>)` parity for
* `PackingItemsRepository.listPublicForShare` (`share.service.ts:386` SH13),
* flagged by the 3h ledger as having no repository-level parity test. The
* legacy statement (PackingItems.repository.ts's own docstring): `SELECT *
* FROM packing_items WHERE trip_id = ? AND is_private = 0 ORDER BY
* sort_order ASC` — a public share viewer is neither owner nor recipient,
* so a Personal or Shared-with-people item (`is_private = 1`) must never
* surface, regardless of `owner_id`/recipient rows (the `_shared/packing-
* visibility.ts` three-tier model this method deliberately does NOT route
* through — it only ever sees the Common tier).
*/
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { resetTestDb } from '../../../helpers/test-db';
import { createPackingItem, createTrip, createUser } from '../../../helpers/factories';
import { createTestPackingItemsRepo } from '../../../helpers/packing-repos';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { updateRows } from '../../../helpers/factories/rows';
import { addPackingItemRecipients } from '../../../helpers/factories/packing';
import { PackingItems } from '../../../../src/db/entities/PackingItems.entity';
import type { PackingItemsRepository } from '../../../../src/db/repositories/PackingItems.repository';
const testDb = createSnapshotTestDb();
let packingItemsRepo: PackingItemsRepository;
let t: TestOrm;
beforeAll(async () => {
packingItemsRepo = await createTestPackingItemsRepo(testDb);
t = await createTestOrm(testDb);
});
beforeEach(() => resetTestDb(testDb));
afterAll(async () => {
await t.close();
testDb.close();
});
function legacyPublicForShare(tripId: number): unknown {
// test-sql-allow: the legacy statement is the oracle the repository read is held to.
return testDb.prepare(
'SELECT * FROM packing_items WHERE trip_id = ? AND is_private = 0 ORDER BY sort_order ASC',
).all(tripId);
}
describe('PackingItemsRepository — share.service.ts SH13 read', () => {
it('listPublicForShare — matches the legacy statement, Common items only, every nullable column both null and set', async () => {
const { user: owner } = createUser(testDb);
const trip = createTrip(testDb, owner.id);
const other = createTrip(testDb, owner.id);
// Common, every nullable column set.
const commonFull = createPackingItem(testDb, trip.id, { name: 'Tent', category: 'Gear' });
await updateRows(t, PackingItems, { id: commonFull.id }, {
checked: 1,
sort_order: 5,
weight_grams: 1200,
bag: null,
quantity: 2,
updated_at: '2026-09-01T00:00:00.000Z',
is_private: 0,
owner: owner.id,
});
// Common, every nullable column left null.
const commonBare = createPackingItem(testDb, trip.id, { name: 'Rope' });
await updateRows(t, PackingItems, { id: commonBare.id }, {
category: null,
sort_order: null,
weight_grams: null,
bag: null,
updated_at: null,
is_private: 0,
owner: null,
});
// Personal (is_private = 1, owned) — must be excluded even though it has an owner.
const personal = createPackingItem(testDb, trip.id, { name: 'Passport' });
await updateRows(t, PackingItems, { id: personal.id }, { is_private: 1, owner: owner.id });
// Shared-with-people (is_private = 1, has a recipient) — must be excluded too.
const shared = createPackingItem(testDb, trip.id, { name: 'First aid kit' });
await updateRows(t, PackingItems, { id: shared.id }, { is_private: 1, owner: owner.id });
await addPackingItemRecipients(t, shared.id, [owner.id]);
// A Common item on a different trip must never leak in.
const foreign = createPackingItem(testDb, other.id, { name: 'Foreign item' });
await updateRows(t, PackingItems, { id: foreign.id }, { is_private: 0 });
const legacy = legacyPublicForShare(trip.id);
const typed = await packingItemsRepo.listPublicForShare(trip.id);
expect(typed).toEqual(legacy);
// ORDER BY sort_order ASC: commonBare (default sort_order 0) before commonFull (sort_order 5).
expect(typed.map((r) => r.id)).toEqual([commonBare.id, commonFull.id]);
});
it('listPublicForShare — [] for a trip with no Common items', async () => {
const { user } = createUser(testDb);
const trip = createTrip(testDb, user.id);
const personalOnly = createPackingItem(testDb, trip.id);
await updateRows(t, PackingItems, { id: personalOnly.id }, { is_private: 1 });
expect(await packingItemsRepo.listPublicForShare(trip.id)).toEqual([]);
});
});