From 56060188a6369daf90a72c71c80a156bee3f0dc3 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 8 Oct 2026 17:16:54 +0200 Subject: [PATCH] test(server): seed the atlas service spec through the factories Converts the raw prepare() fixtures and reads in the atlas service spec to the ORM factories and readers: places, booking endpoints, travellers, visited countries and regions, cached place regions and bucket list rows go through makePlace, makeReservationEndpoint, insertRow and upsertRow, and the region cache is read back through findRow. The two #2527 cases keep their raw move and delete, with an allow marker: they land inside the repository call the test intercepts, synchronously on the call's own connection, which is the race they pin. The database mock loses its raw canAccessTrip and isOwner, which nothing reads any more. --- server/tests/unit/nest/atlas.service.test.ts | 423 +++++++++---------- 1 file changed, 199 insertions(+), 224 deletions(-) diff --git a/server/tests/unit/nest/atlas.service.test.ts b/server/tests/unit/nest/atlas.service.test.ts index f74cc5c88..f2446d2eb 100644 --- a/server/tests/unit/nest/atlas.service.test.ts +++ b/server/tests/unit/nest/atlas.service.test.ts @@ -19,14 +19,6 @@ vi.mock('../../../src/db/database', async () => { closeDb: () => {}, reinitialize: () => {}, getPlaceWithTags: () => null, - canAccessTrip: (tripId: any, userId: number) => - db.prepare(` - SELECT t.id, t.user_id FROM trips t - LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? - WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL) - `).get(userId, tripId, userId), - isOwner: (tripId: any, userId: number) => - !!db.prepare('SELECT id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId), }; return mock; }); @@ -49,8 +41,17 @@ import { createTestPlaceRegionsRepo, } from '../../helpers/atlas-repos'; import type { PlaceRegionsRepository } from '../../../src/db/repositories/PlaceRegions.repository'; +import type { PlaceRow } from '../../../src/db/repositories/Places.repository'; import { PlaceRegions } from '../../../src/db/entities/PlaceRegions.entity'; import { createTestOrm, type TestOrm } from '../../helpers/test-orm'; +import { countRows, findRow, insertRow, updateRows, upsertRow } from '../../helpers/factories/rows'; +import { makePlace } from '../../helpers/factories/places'; +import { addReservationTraveler, makeReservationEndpoint } from '../../helpers/factories/reservations'; +import { BucketList } from '../../../src/db/entities/BucketList.entity'; +import { Reservations } from '../../../src/db/entities/Reservations.entity'; +import { TripMembers } from '../../../src/db/entities/TripMembers.entity'; +import { VisitedCountries } from '../../../src/db/entities/VisitedCountries.entity'; +import { VisitedRegions } from '../../../src/db/entities/VisitedRegions.entity'; // Direct construction over the shared test connection — no TestingModule (repo // convention for DI-native service unit tests). @@ -58,8 +59,7 @@ let atlas: AtlasService; // Kept so the #2527 race tests can step in between a lookup and its cache write. let placeRegionsRepo: PlaceRegionsRepository; -function insertReservationEndpoint( - db: any, +async function insertReservationEndpoint( reservationId: number, role: 'from' | 'to' | 'stop', sequence: number, @@ -69,20 +69,25 @@ function insertReservationEndpoint( localDate: string | null = null, localTime: string | null = null ) { - db.prepare( - 'INSERT INTO reservation_endpoints (reservation_id, role, sequence, name, lat, lng, code, local_date, local_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)' - ).run(reservationId, role, sequence, `Endpoint ${sequence}`, lat, lng, code, localDate, localTime); + await makeReservationEndpoint(orm, reservationId, { + role, sequence, name: `Endpoint ${sequence}`, lat, lng, code, local_date: localDate, local_time: localTime, + }); } -function insertPlace(db: any, tripId: number, name: string, address: string | null = null) { - const cat = db.prepare('SELECT id FROM categories LIMIT 1').get() as { id: number } | undefined; - const result = db.prepare( - 'INSERT INTO places (trip_id, name, address, category_id) VALUES (?, ?, ?, ?)' - ).run(tripId, name, address, cat?.id ?? null); - return db.prepare('SELECT * FROM places WHERE id = ?').get(result.lastInsertRowid); +/** A place with only an address, filed under the first category, the way the geocoder meets it. */ +function insertPlace(tripId: number, name: string, address: string | null = null) { + return makePlace(orm, tripId, { name, address, lat: null, lng: null }); } +/** Caches the place's region, as a finished background lookup leaves it. */ +async function stampRegion(placeId: number, countryCode: string, regionCode: string, regionName: string): Promise { + await upsertRow(orm, PlaceRegions, { place: placeId, country_code: countryCode, region_code: regionCode, region_name: regionName }); +} + +let orm: TestOrm; + beforeAll(async () => { + orm = await sharedTestOrm(testDb); atlas = new AtlasService( await createTestBucketListRepo(testDb), await createTestHiddenCountriesRepo(testDb), @@ -119,7 +124,7 @@ describe('getStats', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Mystery Trip' }); // Place with no address and no coordinates → can't resolve country - insertPlace(testDb, trip.id, 'Unknown Place', null); + await insertPlace(trip.id, 'Unknown Place', null); const stats = await atlas.stats(user.id); @@ -137,9 +142,9 @@ describe('getStats', () => { // 3 places in France, 1 in Germany → France should win for (let i = 0; i < 3; i++) { - insertPlace(testDb, trip.id, `Paris Place ${i}`, `Street ${i}, Paris, France`); + await insertPlace(trip.id, `Paris Place ${i}`, `Street ${i}, Paris, France`); } - insertPlace(testDb, trip.id, 'Berlin Place', 'Some Street, Berlin, Germany'); + await insertPlace(trip.id, 'Berlin Place', 'Some Street, Berlin, Germany'); const stats = await atlas.stats(user.id); @@ -152,8 +157,8 @@ describe('getStats', () => { it('ATLAS-UNIT-003: returns manually marked countries when user has no trips', async () => { const { user } = createUser(testDb); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'AU'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'AU' }); const stats = await atlas.stats(user.id); @@ -167,7 +172,7 @@ describe('getStats', () => { const { user } = createUser(testDb); // Past dates — see ATLAS-UNIT-002; a dateless trip is an 'idea' and never mostVisited. const trip = createTrip(testDb, user.id, { title: 'Italy Trip', start_date: '2023-05-01', end_date: '2023-05-10' }); - insertPlace(testDb, trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); + await insertPlace(trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); const stats = await atlas.stats(user.id); @@ -181,8 +186,8 @@ describe('getStats', () => { const trip = createTrip(testDb, user.id, { title: 'Tokyo Layover Trip' }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); // Tokyo: 35.6762°N, 139.6503°E — inside the JP bounding box, no place row. - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 35.6762, 139.6503); - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 51.4700, -0.4543); + await insertReservationEndpoint(reservation.id, 'from', 0, 35.6762, 139.6503); + await insertReservationEndpoint(reservation.id, 'to', 1, 51.4700, -0.4543); const stats = await atlas.stats(user.id); @@ -197,9 +202,9 @@ describe('getStats', () => { const reservation = createReservation(testDb, trip.id, { type: 'flight' }); // Departs Belgium, connects through Tokyo (role: stop — never leaves the airport), // lands in Australia. Only BE/AU were actually reached. - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); - insertReservationEndpoint(testDb, reservation.id, 'stop', 1, 35.6762, 139.6503); - insertReservationEndpoint(testDb, reservation.id, 'to', 2, -33.8688, 151.2093); + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); + await insertReservationEndpoint(reservation.id, 'stop', 1, 35.6762, 139.6503); + await insertReservationEndpoint(reservation.id, 'to', 2, -33.8688, 151.2093); const stats = await atlas.stats(user.id); @@ -215,8 +220,8 @@ describe('getStats', () => { const reservation = createReservation(testDb, trip.id, { type: 'flight' }); // Brussels -> Malaga airport (36.6749, -4.4991). The destination sits inside both // the ES and DZ bounding boxes; without the ES entry it geocoded to Algeria. - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 36.6749, -4.4991); + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); + await insertReservationEndpoint(reservation.id, 'to', 1, 36.6749, -4.4991); const stats = await atlas.stats(user.id); @@ -242,17 +247,17 @@ describe('getStats layover pairs across two bookings', () => { type Clock = { date: string | null; time: string | null }; - function inboundToHelsinki(tripId: number, arrival: Clock) { + async function inboundToHelsinki(tripId: number, arrival: Clock) { const res = createReservation(testDb, tripId, { type: 'flight', title: 'BRU-HEL' }); - insertReservationEndpoint(testDb, res.id, 'from', 0, BRU.lat, BRU.lng, BRU.code, arrival.date, '07:00'); - insertReservationEndpoint(testDb, res.id, 'to', 1, HEL.lat, HEL.lng, HEL.code, arrival.date, arrival.time); + await insertReservationEndpoint(res.id, 'from', 0, BRU.lat, BRU.lng, BRU.code, arrival.date, '07:00'); + await insertReservationEndpoint(res.id, 'to', 1, HEL.lat, HEL.lng, HEL.code, arrival.date, arrival.time); return res; } - function onwardFromHelsinki(tripId: number, departure: Clock, type = 'flight') { + async function onwardFromHelsinki(tripId: number, departure: Clock, type = 'flight') { const res = createReservation(testDb, tripId, { type, title: 'HEL-JFK' }); - insertReservationEndpoint(testDb, res.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, departure.date, departure.time); - insertReservationEndpoint(testDb, res.id, 'to', 1, JFK.lat, JFK.lng, JFK.code, departure.date, '15:00'); + await insertReservationEndpoint(res.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, departure.date, departure.time); + await insertReservationEndpoint(res.id, 'to', 1, JFK.lat, JFK.lng, JFK.code, departure.date, '15:00'); return res; } @@ -262,8 +267,8 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-044 (#1535): a hub arrived at and left again the same day is not visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'New York via Helsinki' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); - onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); const codes = await codesFor(user.id); expect(codes).toContain('BE'); @@ -274,8 +279,8 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-045 (#1535): a hub the traveler left two days later stays visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Helsinki stopover' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); - onwardFromHelsinki(trip.id, { date: '2026-08-03', time: '11:00' }); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await onwardFromHelsinki(trip.id, { date: '2026-08-03', time: '11:00' }); expect(await codesFor(user.id)).toContain('FI'); }); @@ -285,11 +290,10 @@ describe('getStats layover pairs across two bookings', () => { // endpoints without local parts, so only reservation_time carries the day. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'New York via Helsinki' }); - const inbound = inboundToHelsinki(trip.id, { date: null, time: null }); - const onward = onwardFromHelsinki(trip.id, { date: null, time: null }); - const setTime = testDb.prepare('UPDATE reservations SET reservation_time = ? WHERE id = ?'); - setTime.run('2026-08-01', inbound.id); - setTime.run('2026-08-01', onward.id); + const inbound = await inboundToHelsinki(trip.id, { date: null, time: null }); + const onward = await onwardFromHelsinki(trip.id, { date: null, time: null }); + await updateRows(orm, Reservations, { id: inbound.id }, { reservation_time: '2026-08-01' }); + await updateRows(orm, Reservations, { id: onward.id }, { reservation_time: '2026-08-01' }); const codes = await codesFor(user.id); expect(codes).toContain('BE'); @@ -300,11 +304,10 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-047 (#1535): clockless bookings two days apart keep the hub visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Helsinki stopover' }); - const inbound = inboundToHelsinki(trip.id, { date: null, time: null }); - const onward = onwardFromHelsinki(trip.id, { date: null, time: null }); - const setTime = testDb.prepare('UPDATE reservations SET reservation_time = ? WHERE id = ?'); - setTime.run('2026-08-01', inbound.id); - setTime.run('2026-08-03', onward.id); + const inbound = await inboundToHelsinki(trip.id, { date: null, time: null }); + const onward = await onwardFromHelsinki(trip.id, { date: null, time: null }); + await updateRows(orm, Reservations, { id: inbound.id }, { reservation_time: '2026-08-01' }); + await updateRows(orm, Reservations, { id: onward.id }, { reservation_time: '2026-08-03' }); expect(await codesFor(user.id)).toContain('FI'); }); @@ -312,9 +315,9 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-048 (#1535): a place in the layover country keeps it visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'New York via Helsinki' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); - onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); - insertPlace(testDb, trip.id, 'Kamppi Chapel', 'Simonkatu 7, Helsinki, Finland'); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); + await insertPlace(trip.id, 'Kamppi Chapel', 'Simonkatu 7, Helsinki, Finland'); expect(await codesFor(user.id)).toContain('FI'); }); @@ -322,8 +325,8 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-049 (#1366 guard): a car rental picked up where the flight landed keeps the country', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Driving Finland' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); - onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }, 'car_rental'); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }, 'car_rental'); expect(await codesFor(user.id)).toContain('FI'); }); @@ -331,9 +334,9 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-050 (#1535): a cancelled onward flight is no connection', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Helsinki, stuck' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); - const onward = onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); - testDb.prepare('UPDATE reservations SET status = ? WHERE id = ?').run('cancelled', onward.id); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + const onward = await onwardFromHelsinki(trip.id, { date: '2026-08-01', time: '11:00' }); + await updateRows(orm, Reservations, { id: onward.id }, { status: 'cancelled' }); expect(await codesFor(user.id)).toContain('FI'); }); @@ -344,8 +347,8 @@ describe('getStats layover pairs across two bookings', () => { const { user } = createUser(testDb); const arriving = createTrip(testDb, user.id, { title: 'Back from Brussels' }); const leaving = createTrip(testDb, user.id, { title: 'Off to New York' }); - inboundToHelsinki(arriving.id, { date: '2026-08-01', time: '09:30' }); - onwardFromHelsinki(leaving.id, { date: '2026-08-01', time: '11:00' }); + await inboundToHelsinki(arriving.id, { date: '2026-08-01', time: '09:30' }); + await onwardFromHelsinki(leaving.id, { date: '2026-08-01', time: '11:00' }); expect(await codesFor(user.id)).toContain('FI'); }); @@ -353,10 +356,10 @@ describe('getStats layover pairs across two bookings', () => { it('ATLAS-UNIT-052 (#1535): a same-day out-and-back is a day in the country, not a transfer', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'A day in Helsinki' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); const back = createReservation(testDb, trip.id, { type: 'flight', title: 'HEL-BRU' }); - insertReservationEndpoint(testDb, back.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, '2026-08-01', '18:00'); - insertReservationEndpoint(testDb, back.id, 'to', 1, BRU.lat, BRU.lng, BRU.code, '2026-08-01', '20:30'); + await insertReservationEndpoint(back.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, '2026-08-01', '18:00'); + await insertReservationEndpoint(back.id, 'to', 1, BRU.lat, BRU.lng, BRU.code, '2026-08-01', '20:30'); expect(await codesFor(user.id)).toContain('FI'); }); @@ -367,10 +370,10 @@ describe('getStats layover pairs across two bookings', () => { // onwards and drops Finland, though the traveler spent the day in Helsinki. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'A day in Helsinki' }); - inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); + await inboundToHelsinki(trip.id, { date: '2026-08-01', time: '09:30' }); const back = createReservation(testDb, trip.id, { type: 'flight', title: 'HEL-CRL' }); - insertReservationEndpoint(testDb, back.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, '2026-08-01', '18:00'); - insertReservationEndpoint(testDb, back.id, 'to', 1, 50.4592, 4.4538, 'CRL', '2026-08-01', '20:30'); + await insertReservationEndpoint(back.id, 'from', 0, HEL.lat, HEL.lng, HEL.code, '2026-08-01', '18:00'); + await insertReservationEndpoint(back.id, 'to', 1, 50.4592, 4.4538, 'CRL', '2026-08-01', '20:30'); const codes = await codesFor(user.id); expect(codes).toContain('FI'); @@ -553,8 +556,8 @@ describe('unmarkCountryVisited — tombstones', () => { // Brussels -> Tokyo. JP is derived from the endpoint; it has no visited_countries // row, so the DELETE in unmarkCountryVisited used to affect nothing and getStats // re-derived JP on the very next call. - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); const before = await atlas.stats(user.id); expect(before.countries.map((c: { code: string }) => c.code)).toContain('JP'); @@ -571,8 +574,8 @@ describe('unmarkCountryVisited — tombstones', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Layover Trip' }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); await atlas.unmarkCountry(user.id, 'JP'); expect((await atlas.stats(user.id)).countries.map((c: { code: string }) => c.code)).not.toContain('JP'); @@ -587,13 +590,13 @@ describe('unmarkCountryVisited — tombstones', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Japan Trip' }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); await atlas.unmarkCountry(user.id, 'JP'); expect((await atlas.stats(user.id)).countries.map((c: { code: string }) => c.code)).not.toContain('JP'); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const after = await atlas.stats(user.id); const jp = after.countries.find((c: { code: string }) => c.code === 'JP'); @@ -751,12 +754,8 @@ describe('getCountryGeo', () => { // ── Helpers for new tests ──────────────────────────────────────────────────── -function insertPlaceWithCoords(db: any, tripId: number, name: string, lat: number, lng: number, address: string | null = null) { - const cat = db.prepare('SELECT id FROM categories LIMIT 1').get() as { id: number } | undefined; - const result = db.prepare( - 'INSERT INTO places (trip_id, name, address, lat, lng, category_id) VALUES (?, ?, ?, ?, ?, ?)' - ).run(tripId, name, address, lat, lng, cat?.id ?? null); - return db.prepare('SELECT * FROM places WHERE id = ?').get(result.lastInsertRowid); +function insertPlaceWithCoords(tripId: number, name: string, lat: number, lng: number, address: string | null = null) { + return makePlace(orm, tripId, { name, address, lat, lng }); } // ── getStats — extended ────────────────────────────────────────────────────── @@ -784,7 +783,7 @@ describe('getStats — extended', () => { it('ATLAS-UNIT-007: manually marked country is merged when user has trips but no resolvable places for that country', async () => { const { user } = createUser(testDb); createTrip(testDb, user.id, { title: 'Japan Trip', start_date: '2024-01-01', end_date: '2024-01-10' }); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); const stats = await atlas.stats(user.id); @@ -797,7 +796,7 @@ describe('getStats — extended', () => { it('ATLAS-UNIT-008: lastTrip is resolved with a country code when its places have an address', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Past France Trip', start_date: '2023-05-01', end_date: '2023-05-10' }); - insertPlace(testDb, trip.id, 'Eiffel Tower', 'Champ de Mars, Paris, France'); + await insertPlace(trip.id, 'Eiffel Tower', 'Champ de Mars, Paris, France'); const stats = await atlas.stats(user.id); @@ -859,7 +858,7 @@ describe('getStats — extended', () => { // region-level fix (ATLAS-UNIT-024) at the country level. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'San Francisco Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Hotel Pickwick', 37.7830549, -122.4066689, '85 5th St, San Francisco, CA'); + await insertPlaceWithCoords(trip.id, 'Hotel Pickwick', 37.7830549, -122.4066689, '85 5th St, San Francisco, CA'); const stats = await atlas.stats(user.id); @@ -873,7 +872,7 @@ describe('getStats — extended', () => { // place_regions cache), so this exercises the fix independently of ATLAS-UNIT-027. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Past NY Trip', start_date: '2023-05-01', end_date: '2023-05-10' }); - insertPlaceWithCoords(testDb, trip.id, 'Imperial Court Hotel', 40.7848394, -73.981643, '307 W 79th Street, New York, NY'); + await insertPlaceWithCoords(trip.id, 'Imperial Court Hotel', 40.7848394, -73.981643, '307 W 79th Street, New York, NY'); const stats = await atlas.stats(user.id); @@ -898,8 +897,8 @@ describe('getCountryPlaces', () => { it('ATLAS-UNIT-014: returns matching places when place address resolves to the requested country', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'France Trip' }); - insertPlace(testDb, trip.id, 'Louvre', '75001 Paris, France'); - insertPlace(testDb, trip.id, 'Berlin Wall', 'Bernauer Str., Berlin, Germany'); + await insertPlace(trip.id, 'Louvre', '75001 Paris, France'); + await insertPlace(trip.id, 'Berlin Wall', 'Bernauer Str., Berlin, Germany'); const result = await atlas.countryPlaces(user.id, 'FR'); @@ -911,7 +910,7 @@ describe('getCountryPlaces', () => { it('ATLAS-UNIT-015: manually_marked is true when country is in visited_countries', async () => { const { user } = createUser(testDb); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); createTrip(testDb, user.id, { title: 'Japan' }); const result = await atlas.countryPlaces(user.id, 'JP'); @@ -923,7 +922,7 @@ describe('getCountryPlaces', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Coord Trip' }); // Paris coordinates (48.85°N, 2.35°E) — falls inside FR bounding box - insertPlaceWithCoords(testDb, trip.id, 'Secret Paris Spot', 48.85, 2.35); + await insertPlaceWithCoords(trip.id, 'Secret Paris Spot', 48.85, 2.35); const result = await atlas.countryPlaces(user.id, 'FR'); @@ -945,8 +944,8 @@ describe('getVisitedRegions', () => { it('ATLAS-UNIT-018: returns manually marked regions even when user has no places with coordinates', async () => { const { user } = createUser(testDb); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'DE'); - testDb.prepare('INSERT INTO visited_regions (user_id, region_code, region_name, country_code) VALUES (?, ?, ?, ?)').run(user.id, 'DE-BY', 'Bayern', 'DE'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'DE' }); + await insertRow(orm, VisitedRegions, { user: user.id, region_code: 'DE-BY', region_name: 'Bayern', country_code: 'DE' }); const result = await atlas.visitedRegions(user.id); @@ -972,7 +971,7 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Paris Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Paris Hotel', 48.85, 2.35); + await insertPlaceWithCoords(trip.id, 'Paris Hotel', 48.85, 2.35); // First call triggers the background geocoding fire-and-forget await atlas.visitedRegions(user.id); @@ -989,12 +988,10 @@ describe('getVisitedRegions', () => { it('ATLAS-UNIT-020: places already cached in place_regions are not re-geocoded', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Cached Trip' }); - const place = insertPlaceWithCoords(testDb, trip.id, 'Cached Place', 48.85, 2.35); + const place = await insertPlaceWithCoords(trip.id, 'Cached Place', 48.85, 2.35); // Pre-populate the place_regions cache so the fetch path is never reached - testDb.prepare( - 'INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)' - ).run(place.id, 'FR', 'FR-75', 'Île-de-France'); + await stampRegion(place.id, 'FR', 'FR-75', 'Île-de-France'); const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }); vi.stubGlobal('fetch', mockFetch); @@ -1018,7 +1015,7 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Manchester Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Old Trafford', 53.4631, -2.2913); + await insertPlaceWithCoords(trip.id, 'Old Trafford', 53.4631, -2.2913); await atlas.visitedRegions(user.id); // The background geocode is fire-and-forget; give its microtasks a turn to settle @@ -1044,7 +1041,7 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Barcelona Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Sagrada Familia', 41.4036, 2.1744); + await insertPlaceWithCoords(trip.id, 'Sagrada Familia', 41.4036, 2.1744); await atlas.visitedRegions(user.id); // The background geocode is fire-and-forget; give its microtasks a turn to settle @@ -1068,8 +1065,8 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Luxembourg Trip' }); - insertPlaceWithCoords( - testDb, trip.id, 'Airbnb - Welcome Home', 49.8502458, 6.3576404, + await insertPlaceWithCoords( + trip.id, 'Airbnb - Welcome Home', 49.8502458, 6.3576404, '4 Gruusswiss, Bollendorf-Pont, Distrikt Gréiwemaacher 6555, Luxembourg' ); @@ -1095,8 +1092,8 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'San Francisco Trip' }); - insertPlaceWithCoords( - testDb, trip.id, 'Hotel Pickwick', 37.7830549, -122.4066689, + await insertPlaceWithCoords( + trip.id, 'Hotel Pickwick', 37.7830549, -122.4066689, '85 5th St, San Francisco, CA' ); @@ -1126,7 +1123,7 @@ describe('getVisitedRegions', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Middle of the ocean' }); - insertPlaceWithCoords(testDb, trip.id, 'Buoy', 10, -40); + await insertPlaceWithCoords(trip.id, 'Buoy', 10, -40); await atlas.visitedRegions(user.id); await vi.runAllTimersAsync(); @@ -1163,7 +1160,7 @@ describe('getVisitedRegions', () => { // regionCache is an in-memory Map keyed by rounded lat/lng and persists across tests in // this file, so reusing the same point would silently hit that cached result instead of // exercising this test's fetch/gate path. - insertPlaceWithCoords(testDb, trip.id, 'Weather buoy', 20, -35, '123 Nowhere Rd, JP'); + await insertPlaceWithCoords(trip.id, 'Weather buoy', 20, -35, '123 Nowhere Rd, JP'); await atlas.visitedRegions(user.id); await vi.runAllTimersAsync(); @@ -1179,17 +1176,14 @@ describe('getVisitedRegions', () => { // ── A place that moves (#2527) ─────────────────────────────────────────────── describe('a place that moves keeps no stale region', () => { - function cachedCountryOf(placeId: number): string | undefined { - const row = testDb.prepare('SELECT country_code FROM place_regions WHERE place_id = ?').get(placeId) as - | { country_code: string } - | undefined; - return row?.country_code; + async function cachedCountryOf(placeId: number): Promise { + return (await findRow(orm, PlaceRegions, { place: placeId }))?.country_code; } // The lookup runs in the background; the first one in a worker also loads the admin-1 bundle. async function settledCountryOf(placeId: number): Promise { for (let i = 0; i < 150; i++) { - const code = cachedCountryOf(placeId); + const code = await cachedCountryOf(placeId); if (code) return code; await new Promise((resolve) => setTimeout(resolve, 20)); } @@ -1199,7 +1193,7 @@ describe('a place that moves keeps no stale region', () => { it('ATLAS-SVC-2527a: a lookup still running when the place moves does not write the old country back', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Lyon, then Munich' }); - const place = insertPlaceWithCoords(testDb, trip.id, 'Hotel', 45.764, 4.8357, 'Lyon, France'); + const place = await insertPlaceWithCoords(trip.id, 'Hotel', 45.764, 4.8357, 'Lyon, France'); // The Atlas load starts resolving Lyon, and the place is corrected before it answers. // The repository reads are async here, so the move is placed deterministically @@ -1207,6 +1201,7 @@ describe('a place that moves keeps no stale region', () => { const write = placeRegionsRepo.upsertRegionWhileUnmoved.bind(placeRegionsRepo); const step = vi.spyOn(placeRegionsRepo, 'upsertRegionWhileUnmoved').mockImplementationOnce(async (at, info) => { testDb + // test-sql-allow: the move lands inside the repository call this test intercepts, on its own connection, synchronously. .prepare('UPDATE places SET lat = ?, lng = ?, address = ? WHERE id = ?') .run(48.1374, 11.5755, 'Marienplatz, Munich, Germany', place.id); return write(at, info); @@ -1215,7 +1210,7 @@ describe('a place that moves keeps no stale region', () => { await vi.waitFor(() => expect(step).toHaveBeenCalled(), { timeout: 3000 }); step.mockRestore(); await new Promise((resolve) => setTimeout(resolve, 300)); - expect(cachedCountryOf(place.id)).toBeUndefined(); + expect(await cachedCountryOf(place.id)).toBeUndefined(); // The next load resolves where the place is now. expect((await atlas.stats(user.id)).countries.map((c) => c.code)).toEqual(['DE']); @@ -1226,10 +1221,11 @@ describe('a place that moves keeps no stale region', () => { it('ATLAS-SVC-2527b: a place deleted while its lookup runs leaves nothing behind', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Cancelled' }); - const place = insertPlaceWithCoords(testDb, trip.id, 'Hotel', 43.2965, 5.3698, 'Marseille, France'); + const place = await insertPlaceWithCoords(trip.id, 'Hotel', 43.2965, 5.3698, 'Marseille, France'); const write = placeRegionsRepo.upsertRegionWhileUnmoved.bind(placeRegionsRepo); const step = vi.spyOn(placeRegionsRepo, 'upsertRegionWhileUnmoved').mockImplementationOnce(async (at, info) => { + // test-sql-allow: the delete lands inside the repository call this test intercepts, on its own connection, synchronously. testDb.prepare('DELETE FROM places WHERE id = ?').run(place.id); return write(at, info); }); @@ -1238,7 +1234,7 @@ describe('a place that moves keeps no stale region', () => { step.mockRestore(); await new Promise((resolve) => setTimeout(resolve, 300)); - expect(testDb.prepare('SELECT COUNT(*) AS n FROM place_regions').get()).toEqual({ n: 0 }); + expect(await countRows(orm, PlaceRegions)).toBe(0); }); }); @@ -1269,7 +1265,7 @@ describe('the background lookup runs inside its own request context', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Manchester Trip' }); // The bundled GB polygons answer without Nominatim (ATLAS-UNIT-021), so no fetch is involved. - const place = insertPlaceWithCoords(testDb, trip.id, 'Old Trafford', 53.4631, -2.2913); + const place = await insertPlaceWithCoords(trip.id, 'Old Trafford', 53.4631, -2.2913); // Only the repository the loop writes through is strict; the rest of the service is not exercised here. const bare = new AtlasService( await createTestBucketListRepo(testDb), @@ -1286,10 +1282,10 @@ describe('the background lookup runs inside its own request context', () => { ); // Private on purpose; bracket notation reaches it without an `any`. - bare['cacheRegionsInBackground']([place]); + bare['cacheRegionsInBackground']([place as PlaceRow]); await vi.waitFor( - () => expect(testDb.prepare('SELECT country_code FROM place_regions WHERE place_id = ?').get(place.id)).toEqual({ country_code: 'GB' }), + async () => expect((await findRow(orm, PlaceRegions, { place: place.id }))?.country_code).toBe('GB'), { timeout: 5000 }, ); }); @@ -1315,7 +1311,7 @@ describe('unmarkRegionVisited — tombstones + country cascade', () => { // placeCount until explicitly re-marked. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'SF Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Golden Gate Park', 37.7694, -122.4862); + await insertPlaceWithCoords(trip.id, 'Golden Gate Park', 37.7694, -122.4862); await primeRegionCache(user.id); const before = await atlas.visitedRegions(user.id); @@ -1330,7 +1326,7 @@ describe('unmarkRegionVisited — tombstones + country cascade', () => { it('ATLAS-SVC-025: re-marking a hidden region brings it back', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'SF Trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Golden Gate Park', 37.7694, -122.4862); + await insertPlaceWithCoords(trip.id, 'Golden Gate Park', 37.7694, -122.4862); await primeRegionCache(user.id); await atlas.unmarkRegion(user.id, 'US-CA'); @@ -1362,8 +1358,8 @@ describe('unmarkRegionVisited — tombstones + country cascade', () => { it('ATLAS-SVC-027: hiding one of a country\'s several regions does NOT cascade-hide the country', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'NY road trip' }); - insertPlaceWithCoords(testDb, trip.id, 'Boston hotel', 42.3588336, -71.0578303); // MA - insertPlaceWithCoords(testDb, trip.id, 'Philly hotel', 39.9527237, -75.1635262); // PA + await insertPlaceWithCoords(trip.id, 'Boston hotel', 42.3588336, -71.0578303); // MA + await insertPlaceWithCoords(trip.id, 'Philly hotel', 39.9527237, -75.1635262); // PA await primeRegionCache(user.id); await atlas.unmarkRegion(user.id, 'US-MA'); @@ -1407,7 +1403,7 @@ describe('atlas quirk fixes', () => { // The legacy early return hardcoded manually_marked: false, so a user who // marked a country visited before creating any trip saw it as unmarked. const { user } = createUser(testDb); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); const result = await atlas.countryPlaces(user.id, 'JP'); @@ -1465,8 +1461,8 @@ describe('atlas quirk fixes', () => { expect(await atlas.updateBucketItem(other.id, item.id, { name: 'Stolen' })).toBeNull(); expect(await atlas.deleteBucketItem(other.id, item.id)).toBe(false); - const row = testDb.prepare('SELECT name FROM bucket_list WHERE id = ?').get(item.id) as { name: string }; - expect(row.name).toBe('Mine'); + const row = await findRow(orm, BucketList, { id: item.id }); + expect(row?.name).toBe('Mine'); }); it('ATLAS-SVC-035: unmarkRegion is atomic — a failure mid-flow rolls the region delete back', async () => { @@ -1482,10 +1478,8 @@ describe('atlas quirk fixes', () => { testDb.exec('ALTER TABLE hidden_regions_gone RENAME TO hidden_regions'); } - const row = testDb - .prepare('SELECT 1 FROM visited_regions WHERE user_id = ? AND region_code = ?') - .get(user.id, 'JP-13'); - expect(row).toBeDefined(); + const row = await findRow(orm, VisitedRegions, { user: user.id, region_code: 'JP-13' }); + expect(row).not.toBeNull(); }); it('ATLAS-SVC-036: markCountry is atomic — a failure on the tombstone lift rolls the insert back', async () => { @@ -1498,10 +1492,8 @@ describe('atlas quirk fixes', () => { testDb.exec('ALTER TABLE hidden_countries_gone RENAME TO hidden_countries'); } - const row = testDb - .prepare('SELECT 1 FROM visited_countries WHERE user_id = ? AND country_code = ?') - .get(user.id, 'DE'); - expect(row).toBeUndefined(); + const row = await findRow(orm, VisitedCountries, { user: user.id, country_code: 'DE' }); + expect(row).toBeNull(); }); }); @@ -1513,15 +1505,14 @@ describe('atlas quirk fixes', () => { // which is exactly what the report asks for. describe('bucket-list duplicates (#1898)', () => { - const countRows = (userId: number): number => - (testDb.prepare('SELECT COUNT(*) AS n FROM bucket_list WHERE user_id = ?').get(userId) as { n: number }).n; + const bucketCount = (userId: number): Promise => countRows(orm, BucketList, { user: userId }); it('ATLAS-SVC-037: adding the same wish twice is refused and writes no second row', async () => { const { user } = createUser(testDb); await atlas.createBucketItem(user.id, { name: 'Japan', country_code: 'JP' }); await expect(atlas.createBucketItem(user.id, { name: 'Japan', country_code: 'JP' })).rejects.toThrow(BucketItemExistsError); - expect(countRows(user.id)).toBe(1); + expect(await bucketCount(user.id)).toBe(1); }); it('ATLAS-SVC-038: the same place with another target date is a separate entry', async () => { @@ -1531,7 +1522,7 @@ describe('bucket-list duplicates (#1898)', () => { await atlas.createBucketItem(user.id, { name: 'Japan', country_code: 'JP', target_date: '2027-05' }); await atlas.createBucketItem(user.id, { name: 'Japan', country_code: 'JP', target_date: '2028-09' }); - expect(countRows(user.id)).toBe(3); + expect(await bucketCount(user.id)).toBe(3); // ...but each of those dates only once. await expect(atlas.createBucketItem(user.id, { name: 'Japan', country_code: 'JP', target_date: '2027-05' })).rejects.toThrow( BucketItemExistsError, @@ -1543,7 +1534,7 @@ describe('bucket-list duplicates (#1898)', () => { await atlas.createBucketItem(user.id, { name: 'Kyoto' }); await expect(atlas.createBucketItem(user.id, { name: ' kyoto ' })).rejects.toThrow(BucketItemExistsError); - expect(countRows(user.id)).toBe(1); + expect(await bucketCount(user.id)).toBe(1); }); it('ATLAS-SVC-040: an empty string and NULL are the same "not set"', async () => { @@ -1552,8 +1543,8 @@ describe('bucket-list duplicates (#1898)', () => { // The forms send '' where the map dialogs send null — both must land on the // same identity, and the row itself is stored normalised. - const row = testDb.prepare('SELECT country_code, target_date FROM bucket_list WHERE user_id = ?').get(user.id); - expect(row).toEqual({ country_code: null, target_date: null }); + const row = await findRow(orm, BucketList, { user: user.id }); + expect({ country_code: row?.country_code, target_date: row?.target_date }).toEqual({ country_code: null, target_date: null }); await expect(atlas.createBucketItem(user.id, { name: 'Lisbon', country_code: null, target_date: null })).rejects.toThrow( BucketItemExistsError, ); @@ -1568,8 +1559,8 @@ describe('bucket-list duplicates (#1898)', () => { await atlas.createBucketItem(user.id, { name: 'Altstadt', country_code: 'DE', lat: 50.94, lng: 6.96 }); await atlas.createBucketItem(other.id, { name: 'Altstadt', country_code: 'DE', lat: 48.13, lng: 11.57 }); - expect(countRows(user.id)).toBe(3); - expect(countRows(other.id)).toBe(1); + expect(await bucketCount(user.id)).toBe(3); + expect(await bucketCount(other.id)).toBe(1); }); it('ATLAS-SVC-042: a coordinate-less wish does not collide with the same name pinned to a place', async () => { @@ -1578,7 +1569,7 @@ describe('bucket-list duplicates (#1898)', () => { await atlas.createBucketItem(user.id, { name: 'Kyoto', country_code: 'JP', lat: 35.01, lng: 135.76 }); - expect(countRows(user.id)).toBe(2); + expect(await bucketCount(user.id)).toBe(2); }); it('ATLAS-SVC-043: an update may not move a row onto another wish', async () => { @@ -1590,10 +1581,8 @@ describe('bucket-list duplicates (#1898)', () => { }; await expect(atlas.updateBucketItem(user.id, second.id, { target_date: '2027-05' })).rejects.toThrow(BucketItemExistsError); - const row = testDb.prepare('SELECT target_date FROM bucket_list WHERE id = ?').get(second.id) as { - target_date: string; - }; - expect(row.target_date).toBe('2028-09'); + const row = await findRow(orm, BucketList, { id: second.id }); + expect(row?.target_date).toBe('2028-09'); }); it('ATLAS-SVC-044: an update of a row against itself is not a collision', async () => { @@ -1612,13 +1601,12 @@ describe('bucket-list duplicates (#1898)', () => { const { user } = createUser(testDb); // Rows written before the guard existed are left alone on purpose — no // migration deletes user data for this. - const insert = testDb.prepare('INSERT INTO bucket_list (user_id, name, country_code) VALUES (?, ?, ?)'); - insert.run(user.id, 'Japan', 'JP'); - const legacy = insert.run(user.id, 'Japan', 'JP'); + await insertRow(orm, BucketList, { user: user.id, name: 'Japan', country_code: 'JP' }); + const legacy = await insertRow(orm, BucketList, { user: user.id, name: 'Japan', country_code: 'JP' }); expect(await atlas.bucketList(user.id)).toHaveLength(2); - expect(await atlas.deleteBucketItem(user.id, Number(legacy.lastInsertRowid))).toBe(true); - expect(countRows(user.id)).toBe(1); + expect(await atlas.deleteBucketItem(user.id, legacy)).toBe(true); + expect(await bucketCount(user.id)).toBe(1); }); }); @@ -1643,7 +1631,7 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { it('ATLAS-UNIT-029: a country from a trip that already happened is visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Rome, last month', start_date: PAST_START, end_date: PAST_END }); - insertPlace(testDb, trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); + await insertPlace(trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); const stats = await atlas.stats(user.id); @@ -1656,7 +1644,7 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { it('ATLAS-UNIT-030: a country from a future trip is planned — listed, but not counted as visited', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Japan, next month', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const stats = await atlas.stats(user.id); @@ -1675,7 +1663,7 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { start_date: isoOffsetDays(-2), end_date: isoOffsetDays(5), }); - insertPlace(testDb, trip.id, 'Brandenburger Tor', 'Pariser Platz, Berlin, Germany'); + await insertPlace(trip.id, 'Brandenburger Tor', 'Pariser Platz, Berlin, Germany'); const stats = await atlas.stats(user.id); @@ -1687,7 +1675,7 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const today = isoOffsetDays(0); const trip = createTrip(testDb, user.id, { title: 'Flying out today', start_date: today, end_date: isoOffsetDays(6) }); - insertPlace(testDb, trip.id, 'Louvre', '75001 Paris, France'); + await insertPlace(trip.id, 'Louvre', '75001 Paris, France'); const stats = await atlas.stats(user.id); @@ -1698,7 +1686,7 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { it('ATLAS-UNIT-033: a trip with no dates at all is an idea, not a plan', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Someday: Japan' }); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const stats = await atlas.stats(user.id); @@ -1712,8 +1700,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const past = createTrip(testDb, user.id, { title: 'Munich 2023', start_date: PAST_START, end_date: PAST_END }); const future = createTrip(testDb, user.id, { title: 'Munich again', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, past.id, 'Marienplatz', 'Marienplatz, Munich, Germany'); - insertPlace(testDb, future.id, 'Englischer Garten', 'Englischer Garten, Munich, Germany'); + await insertPlace(past.id, 'Marienplatz', 'Marienplatz, Munich, Germany'); + await insertPlace(future.id, 'Englischer Garten', 'Englischer Garten, Munich, Germany'); const stats = await atlas.stats(user.id); @@ -1728,8 +1716,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { // downgrade it back to a plan. const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Japan, next month', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); const stats = await atlas.stats(user.id); @@ -1744,8 +1732,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: FUTURE_START, end_date: FUTURE_END }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo const before = await atlas.stats(user.id); expect(before.countries.find((c: any) => c.code === 'JP')).toMatchObject({ status: 'planned' }); @@ -1762,8 +1750,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const past = createTrip(testDb, user.id, { title: 'Paris 2023', start_date: PAST_START, end_date: PAST_END }); const future = createTrip(testDb, user.id, { title: 'Tokyo soon', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, past.id, 'Louvre', '75001 Paris, France'); - insertPlace(testDb, future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(past.id, 'Louvre', '75001 Paris, France'); + await insertPlace(future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const stats = await atlas.stats(user.id); @@ -1775,8 +1763,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const past = createTrip(testDb, user.id, { title: 'Rome 2023', start_date: PAST_START, end_date: PAST_END }); const future = createTrip(testDb, user.id, { title: 'Japan soon', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, past.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); - for (let i = 0; i < 3; i++) insertPlace(testDb, future.id, `Tokyo Place ${i}`, `Street ${i}, Tokyo, Japan`); + await insertPlace(past.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); + for (let i = 0; i < 3; i++) await insertPlace(future.id, `Tokyo Place ${i}`, `Street ${i}, Tokyo, Japan`); const stats = await atlas.stats(user.id); @@ -1792,8 +1780,8 @@ describe('getStats — visited vs planned vs idea (#1048)', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: FUTURE_START, end_date: FUTURE_END }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo const stats = await atlas.stats(user.id); @@ -1808,7 +1796,7 @@ describe('getStats first and last visit (#1535)', () => { it('ATLAS-UNIT-054: one trip runs from its start to its end, not from its start to its start', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Paris', start_date: PAST_START, end_date: PAST_END }); - insertPlace(testDb, trip.id, 'Louvre', '75001 Paris, France'); + await insertPlace(trip.id, 'Louvre', '75001 Paris, France'); const stats = await atlas.stats(user.id); @@ -1819,8 +1807,8 @@ describe('getStats first and last visit (#1535)', () => { const { user } = createUser(testDb); const past = createTrip(testDb, user.id, { title: 'Munich 2023', start_date: PAST_START, end_date: PAST_END }); const future = createTrip(testDb, user.id, { title: 'Munich again', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, past.id, 'Marienplatz', 'Marienplatz, Munich, Germany'); - insertPlace(testDb, future.id, 'Englischer Garten', 'Englischer Garten, Munich, Germany'); + await insertPlace(past.id, 'Marienplatz', 'Marienplatz, Munich, Germany'); + await insertPlace(future.id, 'Englischer Garten', 'Englischer Garten, Munich, Germany'); const stats = await atlas.stats(user.id); @@ -1834,7 +1822,7 @@ describe('getStats first and last visit (#1535)', () => { it('ATLAS-UNIT-056: a trip still under way ends today', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Rome now', start_date: isoOffsetDays(-2), end_date: isoOffsetDays(5) }); - insertPlace(testDb, trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); + await insertPlace(trip.id, 'Colosseum', 'Piazza del Colosseo, Rome, Italy'); const stats = await atlas.stats(user.id); @@ -1847,7 +1835,7 @@ describe('getStats first and last visit (#1535)', () => { it('ATLAS-UNIT-057: a planned country keeps the dates of its planned trip', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Tokyo soon', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const stats = await atlas.stats(user.id); @@ -1861,8 +1849,8 @@ describe('getStats first and last visit (#1535)', () => { it('ATLAS-UNIT-058: a country marked by hand takes no dates from a trip that has not happened yet', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Japan, next month', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); - testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP'); + await insertPlace(trip.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertRow(orm, VisitedCountries, { user: user.id, country_code: 'JP' }); const stats = await atlas.stats(user.id); @@ -1877,10 +1865,10 @@ describe('getStats first and last visit (#1535)', () => { const { user } = createUser(testDb); const flown = createTrip(testDb, user.id, { title: 'Tokyo 2023', start_date: PAST_START, end_date: PAST_END }); const reservation = createReservation(testDb, flown.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo const planned = createTrip(testDb, user.id, { title: 'Japan again', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, planned.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(planned.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); const stats = await atlas.stats(user.id); @@ -1896,10 +1884,10 @@ describe('getStats first and last visit (#1535)', () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Tokyo', start_date: PAST_START, end_date: PAST_END }); const reservation = createReservation(testDb, trip.id, { type: 'flight' }); - insertReservationEndpoint(testDb, reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels - insertReservationEndpoint(testDb, reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo + await insertReservationEndpoint(reservation.id, 'from', 0, 50.9014, 4.4844); // Brussels + await insertReservationEndpoint(reservation.id, 'to', 1, 35.6762, 139.6503); // Tokyo const someday = createTrip(testDb, user.id, { title: 'Someday: Paris' }); - insertPlace(testDb, someday.id, 'Louvre', '75001 Paris, France'); + await insertPlace(someday.id, 'Louvre', '75001 Paris, France'); const stats = await atlas.stats(user.id); @@ -1913,8 +1901,8 @@ describe('getCountryPlaces — status (#1048)', () => { const { user } = createUser(testDb); const past = createTrip(testDb, user.id, { title: 'Paris 2023', start_date: PAST_START, end_date: PAST_END }); const future = createTrip(testDb, user.id, { title: 'Tokyo soon', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, past.id, 'Louvre', '75001 Paris, France'); - insertPlace(testDb, future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(past.id, 'Louvre', '75001 Paris, France'); + await insertPlace(future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); expect((await atlas.countryPlaces(user.id, 'FR')).status).toBe('visited'); expect((await atlas.countryPlaces(user.id, 'JP')).status).toBe('planned'); @@ -1925,7 +1913,7 @@ describe('getCountryPlaces — status (#1048)', () => { it('ATLAS-UNIT-041: a manual mark makes the sheet visited even for a future-only country', async () => { const { user } = createUser(testDb); const future = createTrip(testDb, user.id, { title: 'Tokyo soon', start_date: FUTURE_START, end_date: FUTURE_END }); - insertPlace(testDb, future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); + await insertPlace(future.id, 'Senso-ji', 'Asakusa, Tokyo, Japan'); await atlas.markCountry(user.id, 'JP'); const result = await atlas.countryPlaces(user.id, 'JP'); @@ -1940,14 +1928,10 @@ describe('getVisitedRegions — status (#1048)', () => { it('ATLAS-UNIT-042: a region inherits its trip status; a manually marked one is visited', async () => { const { user } = createUser(testDb); const future = createTrip(testDb, user.id, { title: 'Paris soon', start_date: FUTURE_START, end_date: FUTURE_END }); - const place = insertPlaceWithCoords(testDb, future.id, 'Paris Hotel', 48.85, 2.35); + const place = await insertPlaceWithCoords(future.id, 'Paris Hotel', 48.85, 2.35); // Pre-seed the region cache so nothing geocodes in the background (see ATLAS-UNIT-020). - testDb - .prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)') - .run(place.id, 'FR', 'FR-75', 'Île-de-France'); - testDb - .prepare('INSERT INTO visited_regions (user_id, region_code, region_name, country_code) VALUES (?, ?, ?, ?)') - .run(user.id, 'DE-BY', 'Bayern', 'DE'); + await stampRegion(place.id, 'FR', 'FR-75', 'Île-de-France'); + await insertRow(orm, VisitedRegions, { user: user.id, region_code: 'DE-BY', region_name: 'Bayern', country_code: 'DE' }); const result = await atlas.visitedRegions(user.id); @@ -1959,10 +1943,8 @@ describe('getVisitedRegions — status (#1048)', () => { it('ATLAS-UNIT-043: marking a planned region by hand upgrades it to visited', async () => { const { user } = createUser(testDb); const future = createTrip(testDb, user.id, { title: 'Paris soon', start_date: FUTURE_START, end_date: FUTURE_END }); - const place = insertPlaceWithCoords(testDb, future.id, 'Paris Hotel', 48.85, 2.35); - testDb - .prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)') - .run(place.id, 'FR', 'FR-75', 'Île-de-France'); + const place = await insertPlaceWithCoords(future.id, 'Paris Hotel', 48.85, 2.35); + await stampRegion(place.id, 'FR', 'FR-75', 'Île-de-France'); expect((await atlas.visitedRegions(user.id)).regions['FR'][0].status).toBe('planned'); @@ -1996,10 +1978,8 @@ describe('lastTrip', () => { const recent = createTrip(testDb, user.id, { title: 'Recent', start_date: PAST_START, end_date: PAST_END }); createTrip(testDb, user.id, { title: 'Booked', start_date: FUTURE_START, end_date: FUTURE_END }); - const place = insertPlaceWithCoords(testDb, recent.id, 'Paris Hotel', 48.85, 2.35); - testDb - .prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)') - .run(place.id, 'fr', 'FR-75', 'Ile-de-France'); + const place = await insertPlaceWithCoords(recent.id, 'Paris Hotel', 48.85, 2.35); + await stampRegion(place.id, 'fr', 'FR-75', 'Ile-de-France'); const last = await atlas.lastTrip(user.id); expect(last).toMatchObject({ title: 'Recent', start_date: PAST_START, end_date: PAST_END }); @@ -2010,10 +1990,9 @@ describe('lastTrip', () => { it('ATLAS-LAST-004: orders a multi-country trip by how many places sit in each', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Interrail', start_date: PAST_START, end_date: PAST_END }); - const stamp = testDb.prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)'); - stamp.run(insertPlaceWithCoords(testDb, trip.id, 'Wien', 48.2, 16.37).id, 'AT', 'AT-9', 'Wien'); - stamp.run(insertPlaceWithCoords(testDb, trip.id, 'Praha', 50.08, 14.44).id, 'CZ', 'CZ-10', 'Praha'); - stamp.run(insertPlaceWithCoords(testDb, trip.id, 'Brno', 49.19, 16.61).id, 'CZ', 'CZ-64', 'Brno'); + await stampRegion((await insertPlaceWithCoords(trip.id, 'Wien', 48.2, 16.37)).id, 'AT', 'AT-9', 'Wien'); + await stampRegion((await insertPlaceWithCoords(trip.id, 'Praha', 50.08, 14.44)).id, 'CZ', 'CZ-10', 'Praha'); + await stampRegion((await insertPlaceWithCoords(trip.id, 'Brno', 49.19, 16.61)).id, 'CZ', 'CZ-64', 'Brno'); // Two Czech stops beat one Austrian, so CZ leads and becomes `country`. expect((await atlas.lastTrip(user.id))!.countries).toEqual(['CZ', 'AT']); @@ -2022,7 +2001,7 @@ describe('lastTrip', () => { it('ATLAS-LAST-005: a trip whose places were never geocoded reports no countries, not a wrong one', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id, { title: 'Roadtrip', start_date: PAST_START, end_date: PAST_END }); - insertPlace(testDb, trip.id, 'That diner off the highway'); + await insertPlace(trip.id, 'That diner off the highway'); expect((await atlas.lastTrip(user.id))!.countries).toEqual([]); }); @@ -2030,7 +2009,7 @@ describe('lastTrip', () => { const { user: owner } = createUser(testDb); const { user: member } = createUser(testDb); const trip = createTrip(testDb, owner.id, { title: 'Shared', start_date: PAST_START, end_date: PAST_END }); - testDb.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(trip.id, member.id); + await insertRow(orm, TripMembers, { trip: trip.id, user: member.id }); expect((await atlas.lastTrip(member.id))?.title).toBe('Shared'); }); @@ -2052,35 +2031,32 @@ describe('lastTrip', () => { // in this file exercises with a populated reservation_travelers table. describe('personal figures on a shared trip in stats() (#1966, AT6)', () => { const endpoint = (reservationId: number, role: 'from' | 'to', sequence: number, lat: number, lng: number) => - testDb.prepare( - 'INSERT INTO reservation_endpoints (reservation_id, role, sequence, name, lat, lng) VALUES (?, ?, ?, ?, ?, ?)' - ).run(reservationId, role, sequence, `Endpoint ${sequence}`, lat, lng); + makeReservationEndpoint(orm, reservationId, { role, sequence, name: `Endpoint ${sequence}`, lat, lng, code: null }); - const assignTo = (reservationId: number, userId: number) => - testDb.prepare('INSERT INTO reservation_travelers (reservation_id, user_id) VALUES (?, ?)').run(reservationId, userId); + const assignTo = (reservationId: number, userId: number) => addReservationTraveler(orm, reservationId, userId); /** A shared trip: Alice flies Brussels→Tokyo, Bob flies Brussels→Sydney. */ - function sharedTrip() { + async function sharedTrip() { const { user: alice } = createUser(testDb); const { user: bob } = createUser(testDb); const trip = createTrip(testDb, alice.id, { title: 'Shared trip' }); - testDb.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(trip.id, bob.id); + await insertRow(orm, TripMembers, { trip: trip.id, user: bob.id }); const toTokyo = createReservation(testDb, trip.id, { type: 'flight', title: 'BRU-NRT' }); - endpoint(toTokyo.id, 'from', 0, 50.9014, 4.4844); // Brussels - endpoint(toTokyo.id, 'to', 1, 35.6762, 139.6503); // Tokyo + await endpoint(toTokyo.id, 'from', 0, 50.9014, 4.4844); // Brussels + await endpoint(toTokyo.id, 'to', 1, 35.6762, 139.6503); // Tokyo const toSydney = createReservation(testDb, trip.id, { type: 'flight', title: 'BRU-SYD' }); - endpoint(toSydney.id, 'from', 0, 50.9014, 4.4844); // Brussels - endpoint(toSydney.id, 'to', 1, -33.8688, 151.2093); // Sydney + await endpoint(toSydney.id, 'from', 0, 50.9014, 4.4844); // Brussels + await endpoint(toSydney.id, 'to', 1, -33.8688, 151.2093); // Sydney return { alice, bob, toTokyo, toSydney }; } it('ATLAS-TRAVOWNS-001: an assigned traveler sees only their own flight leg, not their trip-mate\'s', async () => { - const { alice, bob, toTokyo, toSydney } = sharedTrip(); - assignTo(toTokyo.id, alice.id); - assignTo(toSydney.id, bob.id); + const { alice, bob, toTokyo, toSydney } = await sharedTrip(); + await assignTo(toTokyo.id, alice.id); + await assignTo(toSydney.id, bob.id); const aliceCodes = (await atlas.stats(alice.id)).countries.map((c: { code: string }) => c.code); const bobCodes = (await atlas.stats(bob.id)).countries.map((c: { code: string }) => c.code); @@ -2095,7 +2071,7 @@ describe('personal figures on a shared trip in stats() (#1966, AT6)', () => { }); it('ATLAS-TRAVOWNS-002: a booking with nobody named on it still counts for every trip member (pre-4.0 backward compat)', async () => { - const { alice, bob } = sharedTrip(); + const { alice, bob } = await sharedTrip(); // Neither flight is assigned to anyone. const aliceCodes = (await atlas.stats(alice.id)).countries.map((c: { code: string }) => c.code); @@ -2123,8 +2099,7 @@ describe('nextTrip', () => { const { user } = createUser(testDb); createTrip(testDb, user.id, { title: 'Later', start_date: isoOffsetDays(90), end_date: isoOffsetDays(95) }); const soon = createTrip(testDb, user.id, { title: 'Soon', start_date: FUTURE_START, end_date: FUTURE_END }); - const stamp = testDb.prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)'); - stamp.run(insertPlaceWithCoords(testDb, soon.id, 'Lisboa', 38.72, -9.14).id, 'pt', 'PT-11', 'Lisboa'); + await stampRegion((await insertPlaceWithCoords(soon.id, 'Lisboa', 38.72, -9.14)).id, 'pt', 'PT-11', 'Lisboa'); expect(await atlas.nextTrip(user.id)).toEqual({ title: 'Soon', @@ -2159,8 +2134,8 @@ describe('nextTrip', () => { const { user: member } = createUser(testDb); const first = createTrip(testDb, owner.id, { title: 'First', start_date: FUTURE_START, end_date: FUTURE_END }); const second = createTrip(testDb, owner.id, { title: 'Second', start_date: FUTURE_START, end_date: FUTURE_END }); - testDb.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(first.id, member.id); - testDb.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(second.id, member.id); + await insertRow(orm, TripMembers, { trip: first.id, user: member.id }); + await insertRow(orm, TripMembers, { trip: second.id, user: member.id }); expect((await atlas.nextTrip(member.id))?.title).toBe('First'); }); });