test(server): seed the plugin discovery, permission, feed and stay specs through the factories

Converts the raw prepare() fixtures and reads in the plugin discovery spec,
the permissions service spec, the feeds and invite MCP spec and the day
accommodations repository spec to the ORM factories and readers.

The permission cache cases still write their override rows past
savePermissions, now through setAppSetting, so the stale-cache behaviour
they pin is unchanged. The day accommodations spec keeps the legacy
statements it holds the repository to behind allow markers.
This commit is contained in:
Maurice
2026-10-08 16:04:51 +02:00
parent 855c4e3ba3
commit 2860148df3
4 changed files with 126 additions and 103 deletions
@@ -9,6 +9,9 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { resetTestDb } from '../../../helpers/test-db';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { updateRows } from '../../../helpers/factories/rows';
import { readTripDays } from '../../../helpers/factories/trips';
import { Reservations } from '../../../../src/db/entities/Reservations.entity';
import { createDayAccommodation, createPlace, createReservation, createTrip, createUser } from '../../../helpers/factories';
import { DayAccommodations } from '../../../../src/db/entities/DayAccommodations.entity';
import type { DayAccommodationsRepository } from '../../../../src/db/repositories/DayAccommodations.repository';
@@ -28,41 +31,43 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
// One seeded world for every test below: a trip with two stays —
// `named` (a place, a check-in/out, a linked live booking) and `bare`
// (no place, no booking) — plus a second trip's stay, to prove scoping.
const seed = () => {
const seed = async () => {
const { user } = createUser(testDb);
const trip = createTrip(testDb, user.id, { start_date: '2026-09-01', end_date: '2026-09-05' });
const otherTrip = createTrip(testDb, user.id, { start_date: '2026-09-01', end_date: '2026-09-02' });
const days = testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number ASC').all(trip.id) as { id: number }[];
const days = await readTripDays(t, trip.id);
const place = createPlace(testDb, trip.id, { name: 'The Plaza', description: 'Fancy' });
const named = createDayAccommodation(testDb, trip.id, place.id, days[0].id, days[1].id, { check_in: '15:00', check_out: '11:00', confirmation: 'CONF-1' });
const booking = createReservation(testDb, trip.id, { title: 'Plaza Stay', type: 'hotel' });
testDb.prepare('UPDATE reservations SET accommodation_id = ? WHERE id = ?').run(`${named.id}.0`, booking.id);
await updateRows(t, Reservations, { id: booking.id }, { accommodation_id: `${named.id}.0` });
const bare = createDayAccommodation(testDb, trip.id, place.id, days[2].id, days[3].id);
const otherDays = testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number ASC').all(otherTrip.id) as { id: number }[];
const otherDays = await readTripDays(t, otherTrip.id);
const otherPlace = createPlace(testDb, otherTrip.id, { name: 'Elsewhere' });
const otherStay = createDayAccommodation(testDb, otherTrip.id, otherPlace.id, otherDays[0].id, otherDays[1].id);
return { trip, otherTrip, days, place, named, booking, bare, otherStay };
};
it('AC2 findWithPlace — matches the legacy LEFT JOIN places statement, both with and without a place', async () => {
const { named, place } = seed();
const { named, place } = await seed();
const legacySql = `
SELECT a.*, p.name as place_name, p.address as place_address, p.image_url as place_image, p.lat as place_lat, p.lng as place_lng
FROM day_accommodations a LEFT JOIN places p ON a.place_id = p.id WHERE a.id = ?`;
const typed = await repo.findWithPlace(named.id);
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(legacySql).get(named.id);
expect(typed).toEqual(legacy);
expect(typed?.place_name).toBe(place.name);
});
it('AC3 listForTripWithPlaceAndBooking — matches the legacy LEFT JOIN places/reservations statement, fanning out one row per linked booking', async () => {
const { trip, named, bare, booking } = seed();
const { trip, named, bare, booking } = await seed();
const legacySql = `
SELECT a.*, p.name as place_name, p.address as place_address, p.image_url as place_image, p.lat as place_lat, p.lng as place_lng,
r.title as reservation_title
FROM day_accommodations a LEFT JOIN places p ON a.place_id = p.id LEFT JOIN reservations r ON r.accommodation_id = a.id
WHERE a.trip_id = ? ORDER BY a.created_at ASC`;
const typed = await repo.listForTripWithPlaceAndBooking(trip.id);
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(legacySql).all(trip.id);
expect(typed).toEqual(legacy);
expect(typed.map((r) => r.id)).toEqual([named.id, bare.id]);
@@ -71,14 +76,15 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('AC34 findInTrip — matches the legacy statement, scoped by trip (a foreign-trip id misses)', async () => {
const { trip, named, otherStay } = seed();
const { trip, named, otherStay } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT * FROM day_accommodations WHERE id = ? AND trip_id = ?').get(named.id, trip.id);
expect(await repo.findInTrip(named.id, trip.id)).toEqual(legacy);
expect(await repo.findInTrip(otherStay.id, trip.id)).toBeUndefined();
});
it('RS37/RS38/RS21 existsInTrip / getCheckIn / getTripId — match the legacy statements', async () => {
const { trip, named, otherStay } = seed();
const { trip, named, otherStay } = await seed();
expect(await repo.existsInTrip(named.id, trip.id)).toBe(true);
expect(await repo.existsInTrip(otherStay.id, trip.id)).toBe(false);
expect(await repo.getCheckIn(named.id)).toBe('15:00');
@@ -87,7 +93,8 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('DY19 listStartEndDayNumbers — matches the legacy joined day_number statement', async () => {
const { trip, named, bare } = seed();
const { trip, named, bare } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`
SELECT a.id, s.day_number as start_no, e.day_number as end_no FROM day_accommodations a
JOIN days s ON s.id = a.start_day_id JOIN days e ON e.id = a.end_day_id WHERE a.trip_id = ?`).all(trip.id);
@@ -97,7 +104,8 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('DY20 listForResync — matches the legacy statement, both persist(false) mirror columns present', async () => {
const { trip, named } = seed();
const { trip, named } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT id, start_day_id, end_day_id, check_in FROM day_accommodations WHERE trip_id = ?').all(trip.id);
const typed = await repo.listForResync(trip.id);
expect(typed).toEqual(legacy);
@@ -105,7 +113,8 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('DY41 listIdsCheckingInOrOutOn — matches the legacy statement: check-in day, check-out day, not a day only run across', async () => {
const { trip, days, named, bare } = seed();
const { trip, days, named, bare } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = (dayId: number) => (testDb.prepare(
'SELECT id FROM day_accommodations WHERE trip_id = ? AND (start_day_id = ? OR end_day_id = ?) ORDER BY id',
).all(trip.id, dayId, dayId) as { id: number }[]).map((r) => r.id);
@@ -117,12 +126,13 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('DY41 listIdsCheckingInOrOutOn — scoped by trip (another trip\'s day matches nothing here)', async () => {
const { trip, otherStay } = seed();
const { trip, otherStay } = await seed();
expect(await repo.listIdsCheckingInOrOutOn(trip.id, otherStay.start_day_id)).toEqual([]);
});
it('RPL3 listRoadtripStays — matches the legacy LEFT JOIN places + MIN(reservation) statement, id order', async () => {
const { trip, named, bare, booking } = seed();
const { trip, named, bare, booking } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`
SELECT a.id, a.place_id, a.start_day_id, a.end_day_id, a.check_in, a.check_out,
p.name AS place_name, p.lat AS place_lat, p.lng AS place_lng,
@@ -136,7 +146,8 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('PL16 listForPlace — matches the legacy statement, scoped by trip AND place', async () => {
const { trip, place, named, bare } = seed();
const { trip, place, named, bare } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT id FROM day_accommodations WHERE trip_id = ? AND place_id = ?').all(trip.id, place.id);
const typed = await repo.listForPlace(trip.id, place.id);
expect(typed).toEqual(legacy);
@@ -144,7 +155,8 @@ describe('DayAccommodationsRepository — fully seeded world', () => {
});
it('TP55 listAllForTrip — matches the legacy SELECT *, no ORDER BY, scoped by trip', async () => {
const { trip, otherTrip, named, bare, otherStay } = seed();
const { trip, otherTrip, named, bare, otherStay } = await seed();
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT * FROM day_accommodations WHERE trip_id = ?').all(trip.id);
const typed = await repo.listAllForTrip(trip.id);
expect(typed).toEqual(legacy);