diff --git a/server/tests/e2e/files.e2e.test.ts b/server/tests/e2e/files.e2e.test.ts index e543106dc..99ad72f98 100644 --- a/server/tests/e2e/files.e2e.test.ts +++ b/server/tests/e2e/files.e2e.test.ts @@ -25,7 +25,11 @@ const { db } = vi.hoisted(() => { // FilesService runs its real SQL against these (FILE_SELECT joins reservations // and users; the link batch reads file_links; findForeignLinkTarget probes // reservations/places/day_assignments). - tmp.exec('CREATE TABLE trips (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, title TEXT, currency TEXT);'); + // An ORM insert into trips names every column the entity gives a default + // (is_archived, reminder_days and the two timestamps); the real schema has all four. + tmp.exec(`CREATE TABLE trips (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, title TEXT, currency TEXT, + is_archived INTEGER DEFAULT 0, reminder_days INTEGER DEFAULT 3, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP);`); // TripAccessGuard now reads TripsRepository.findAccessible directly // (Plan 3c Task 0b), a real join against trip_members. tmp.exec('CREATE TABLE trip_members (trip_id INTEGER NOT NULL, user_id INTEGER NOT NULL);'); diff --git a/server/tests/integration/days.test.ts b/server/tests/integration/days.test.ts index 850a720a9..e951be44f 100644 --- a/server/tests/integration/days.test.ts +++ b/server/tests/integration/days.test.ts @@ -40,6 +40,7 @@ import { DayAccommodations } from '../../src/db/entities/DayAccommodations.entit import { Days } from '../../src/db/entities/Days.entity'; import { Reservations } from '../../src/db/entities/Reservations.entity'; import { Trips } from '../../src/db/entities/Trips.entity'; +import { legacyBoundIntegerText } from '../../src/nest/common/row-id'; let nestApp: INestApplication; let app: Application; @@ -560,7 +561,7 @@ describe('Accommodations', () => { expect(res.status).toBe(201); // Linked reservation should exist - const reservation = await findRow(orm(), Reservations, { accommodation_id: String(res.body.accommodation.id) }); + const reservation = await findRow(orm(), Reservations, { accommodation_id: legacyBoundIntegerText(res.body.accommodation.id) }); expect(reservation).not.toBeNull(); expect(reservation!.type).toBe('hotel'); expect(reservation!.confirmation_number).toBe('CONF-XYZ'); @@ -619,7 +620,7 @@ describe('Accommodations', () => { .send({ place_id: place.id, start_day_id: day1.id, end_day_id: day2.id }); const accommodationId = createRes.body.accommodation.id; - const reservationBefore = await findRow(orm(), Reservations, { accommodation_id: String(accommodationId) }); + const reservationBefore = await findRow(orm(), Reservations, { accommodation_id: legacyBoundIntegerText(accommodationId) }); expect(reservationBefore).not.toBeNull(); const deleteRes = await request(app) diff --git a/server/tests/unit/idempotency-cleanup.test.ts b/server/tests/unit/idempotency-cleanup.test.ts index ab61156f9..3233010f6 100644 --- a/server/tests/unit/idempotency-cleanup.test.ts +++ b/server/tests/unit/idempotency-cleanup.test.ts @@ -13,6 +13,7 @@ import { purgeExpiredIdempotencyKeys } from '../../src/nest/common/idempotency-c import { IdempotencyCleanupJob } from '../../src/nest/common/idempotency-cleanup.job'; import { IdempotencyKeys } from '../../src/db/entities/IdempotencyKeys.entity'; import { countRows, deleteRows, findRows, insertRow } from '../helpers/factories/rows'; +import { makeUser } from '../helpers/factories/users'; import type { IdempotencyKeysRepository } from '../../src/db/repositories/IdempotencyKeys.repository'; import type { CronRegistrarService } from '../../src/nest/scheduling/cron-registrar.service'; @@ -23,17 +24,20 @@ const NOW_SEC = Math.floor(NOW / 1000); const testDb = createSnapshotTestDb(); let t: TestOrm; let idempotencyKeys: IdempotencyKeysRepository; +/** The user every fixture key belongs to (the table's user_id is a foreign key). */ +let userId: number; beforeAll(async () => { t = await createTestOrm(testDb); idempotencyKeys = t.repo(IdempotencyKeys); + userId = (await makeUser(t)).user.id; }); afterAll(async () => { await t.close(); testDb.close(); }); async function insertKey(key: string, ageSeconds: number, nowSec = NOW_SEC): Promise { await insertRow(t, IdempotencyKeys, { key, - user: 1, + user: userId, method: 'POST', path: '/x', status_code: 200, @@ -48,14 +52,12 @@ async function remainingKeys(): Promise { } beforeEach(async () => { - testDb.pragma('foreign_keys = OFF'); // fixtures reference a user we don't seed here await deleteRows(t, IdempotencyKeys); t.clear(); }); afterEach(async () => { await deleteRows(t, IdempotencyKeys); - testDb.pragma('foreign_keys = ON'); delete process.env.IDEMPOTENCY_TTL_SECONDS; }); diff --git a/server/tests/unit/nest/day-removal.service.test.ts b/server/tests/unit/nest/day-removal.service.test.ts index 3e6615441..06467dd4f 100644 --- a/server/tests/unit/nest/day-removal.service.test.ts +++ b/server/tests/unit/nest/day-removal.service.test.ts @@ -61,6 +61,7 @@ import { Reservations } from '../../../src/db/entities/Reservations.entity'; import { ReservationEndpoints } from '../../../src/db/entities/ReservationEndpoints.entity'; import { RoadtripDayBoundaries } from '../../../src/db/entities/RoadtripDayBoundaries.entity'; import { Trips } from '../../../src/db/entities/Trips.entity'; +import { legacyBoundIntegerText } from '../../../src/nest/common/row-id'; let days: DaysService; let journey: JourneyDomainService; @@ -178,7 +179,7 @@ async function bookedNight(tripId: number, startDayId: number, endDayId: number) const place = createPlace(testDb, tripId, { name: 'Harbour Hotel' }); const { accommodation } = await accommodations.createAccommodation(tripId, { place_id: place.id, start_day_id: startDayId, end_day_id: endDayId }); const stayId = (accommodation as { id: number }).id; - const hotelBooking = await findRow(await orm(), Reservations, { accommodation_id: String(stayId) }); + const hotelBooking = await findRow(await orm(), Reservations, { accommodation_id: legacyBoundIntegerText(stayId) }); if (!hotelBooking) throw new Error('createAccommodation should have booked the stay'); const reservationId = hotelBooking.id; const budgetId = (await makeBudgetItem(await orm(), tripId, { diff --git a/server/tests/unit/nest/days.service.test.ts b/server/tests/unit/nest/days.service.test.ts index b2dbf4093..cc317c0a6 100644 --- a/server/tests/unit/nest/days.service.test.ts +++ b/server/tests/unit/nest/days.service.test.ts @@ -93,6 +93,7 @@ import { RealtimeService } from '../../../src/nest/realtime/realtime.service'; import { QueryHelpersService } from '../../../src/nest/query-helpers/query-helpers.service'; import { makeAccommodationsService } from '../../helpers/accommodations-service'; import type { Day } from '../../../src/types'; +import { legacyBoundIntegerText } from '../../../src/nest/common/row-id'; import { createTestUnitOfWork, createTestAppSettingsRepo, createTestDaysRepo, createTestDayAssignmentsRepo, createTestDayNotesRepo, createTestTripsRepo, @@ -596,7 +597,7 @@ describe('restampReservationDates', () => { describe('resyncAccommodationDays', () => { /** The hotel reservation accommodations/ auto-creates next to a stay. */ const linkHotel = (tripId: number, accId: number, dayId: number | null, time: string | null) => - addReservation(tripId, { day: dayId, title: 'Hotel', type: 'hotel', accommodation_id: String(accId), reservation_time: time }); + addReservation(tripId, { day: dayId, title: 'Hotel', type: 'hotel', accommodation_id: legacyBoundIntegerText(accId), reservation_time: time }); it('DAY-SVC-040 — returns before touching a hotel booking when the trip has no stays', async () => { const { user } = createUser(testDb); diff --git a/server/tests/unit/nest/places.service.test.ts b/server/tests/unit/nest/places.service.test.ts index 31923861e..d56aed118 100644 --- a/server/tests/unit/nest/places.service.test.ts +++ b/server/tests/unit/nest/places.service.test.ts @@ -62,6 +62,7 @@ import fs from 'fs'; import { PermissionsService } from '../../../src/nest/permissions/permissions.service'; import { RealtimeService } from '../../../src/nest/realtime/realtime.service'; import { PlacesService } from '../../../src/nest/places/places.service'; +import { legacyBoundIntegerText } from '../../../src/nest/common/row-id'; import { MapsService } from '../../../src/nest/maps/maps.service'; import { QueryHelpersService } from '../../../src/nest/query-helpers/query-helpers.service'; import { JourneyDomainService } from '../../../src/nest/journey/journey-domain.service'; @@ -208,7 +209,7 @@ describe('list', () => { const trip = createTrip(testDb, user.id); const tour = createPlace(testDb, trip.id, { name: 'Facet-backed tour' }) as any; const place = createPlace(testDb, trip.id, { name: 'Ordinary place' }) as any; - await insertRow(orm, Tours, { place_id: tour.id, tourTypeRef: 'hike' }); + await insertRow(orm, Tours, { place: tour.id, tourTypeRef: 'hike' }); const byId = new Map(((await svc.list(String(trip.id), {})) as any[]).map(item => [item.id, item])); @@ -244,7 +245,7 @@ describe('list', () => { const paris = createPlace(testDb, trip.id, { name: 'Louvre', lat: 48.8606, lng: 2.3376 }); const nowhere = createPlace(testDb, trip.id, { name: 'Unplaced' }); await updateRows(orm, Places, { id: nowhere.id }, { lat: null, lng: null, address: null }); - await insertRow(orm, PlaceRegions, { place_id: berlin.id, country_code: 'DE', region_code: 'DE-BE', region_name: 'Berlin' }); + await insertRow(orm, PlaceRegions, { place: berlin.id, country_code: 'DE', region_code: 'DE-BE', region_name: 'Berlin' }); const byId = new Map(((await svc.list(String(trip.id), {})) as any[]).map(p => [p.id, p])); expect(byId.get(berlin.id)).toMatchObject({ country_code: 'DE', region_name: 'Berlin' }); expect(byId.get(paris.id)).toMatchObject({ country_code: 'FR', region_name: null }); @@ -680,12 +681,12 @@ describe('remove', () => { const { accommodation } = (await accommodations.createAccommodation(trip.id, { place_id: place.id, start_day_id: day.id, end_day_id: day.id, })) as any; - expect(await findRow(orm, Reservations, { accommodation_id: String(accommodation.id) })).toBeTruthy(); + expect(await findRow(orm, Reservations, { accommodation_id: legacyBoundIntegerText(accommodation.id) })).toBeTruthy(); await svc.remove(String(trip.id), String(place.id)); expect(await findRow(orm, DayAccommodations, { id: accommodation.id })).toBeNull(); - expect(await findRow(orm, Reservations, { accommodation_id: String(accommodation.id) })).toBeNull(); + expect(await findRow(orm, Reservations, { accommodation_id: legacyBoundIntegerText(accommodation.id) })).toBeNull(); expect(await findRows(orm, DayAssignments, { day: day.id })).toEqual([]); }); @@ -697,7 +698,7 @@ describe('remove', () => { const keptTour = createPlace(testDb, trip.id, { name: 'Tour to keep' }) as any; const keptPlace = createPlace(testDb, trip.id, { name: 'Ordinary place' }) as any; for (const place of [tour, keptTour]) { - await insertRow(orm, Tours, { place_id: place.id, tourTypeRef: 'hike', max_hiking_difficulty: 4 }); + await insertRow(orm, Tours, { place: place.id, tourTypeRef: 'hike', max_hiking_difficulty: 4 }); await insertRow(orm, TourWaypoints, { place: place.id, lat: 48, lng: 11, role: 'start', sequence: 0 }); await insertRow(orm, DayAssignments, { day: day.id, place: place.id, order_index: 0 }); } @@ -709,12 +710,12 @@ describe('remove', () => { expect(result).toMatchObject({ deleted: true, deletedTourPlaceIds: [tour.id] }); expect(await findRow(orm, Places, { id: tour.id })).toBeNull(); - expect(await findRow(orm, Tours, { place_id: tour.id })).toBeNull(); + expect(await findRow(orm, Tours, { place: tour.id })).toBeNull(); expect(await findRows(orm, TourWaypoints, { place: tour.id })).toEqual([]); expect(await findRows(orm, DayAssignments, { place: tour.id })).toEqual([]); expect(await findRow(orm, TripFiles, { id: tourFileId })).toMatchObject({ id: tourFileId, place_id: null }); expect(await findRows(orm, FileLinks, { file: tourFileId })).toEqual([]); - expect(await findRow(orm, Tours, { place_id: keptTour.id })).toBeTruthy(); + expect(await findRow(orm, Tours, { place: keptTour.id })).toBeTruthy(); expect(await findRows(orm, TourWaypoints, { place: keptTour.id })).toHaveLength(1); expect(await findRows(orm, DayAssignments, { place: keptTour.id })).toHaveLength(1); expect(await findRow(orm, Places, { id: keptPlace.id })).toBeTruthy(); @@ -801,7 +802,7 @@ describe('remove', () => { const { accommodation } = (await accommodations.createAccommodation(trip.id, { place_id: hotel.id, start_day_id: day.id, end_day_id: day.id, })) as { accommodation: { id: number } }; - const reservation = await findRow(orm, Reservations, { accommodation_id: String(accommodation.id) }) as { id: number }; + const reservation = await findRow(orm, Reservations, { accommodation_id: legacyBoundIntegerText(accommodation.id) }) as { id: number }; // cancelStaysAt (PL16) calls AccommodationsService.deleteAccommodation // FIRST inside this transaction — its own `uow.transactional` call nests @@ -833,14 +834,14 @@ describe('removeMany', () => { const ordinary = createPlace(testDb, trip.id, { name: 'Ordinary place' }) as any; const keptTour = createPlace(testDb, trip.id, { name: 'Tour to keep' }) as any; for (const place of [tour, keptTour]) { - await insertRow(orm, Tours, { place_id: place.id, tourTypeRef: 'hike' }); + await insertRow(orm, Tours, { place: place.id, tourTypeRef: 'hike' }); } const result = await svc.removeMany(String(trip.id), [ordinary.id, tour.id]); expect(result.deleted).toEqual([ordinary.id, tour.id]); expect(result.deletedTourPlaceIds).toEqual([tour.id]); - expect(await findRow(orm, Tours, { place_id: keptTour.id })).toBeTruthy(); + expect(await findRow(orm, Tours, { place: keptTour.id })).toBeTruthy(); expect(await findRow(orm, Places, { id: keptTour.id })).toBeTruthy(); }); @@ -868,7 +869,7 @@ describe('removeMany', () => { const { accommodation } = (await accommodations.createAccommodation(trip.id, { place_id: hotel.id, start_day_id: day.id, end_day_id: day.id, })) as { accommodation: { id: number } }; - const reservation = await findRow(orm, Reservations, { accommodation_id: String(accommodation.id) }) as { id: number }; + const reservation = await findRow(orm, Reservations, { accommodation_id: legacyBoundIntegerText(accommodation.id) }) as { id: number }; // An expense hung off the reservation rather than the place: linkedExpenseIds // selects on budget_items.place_id and never finds this one. const itemId = Number((await insertRow(orm, BudgetItems, { trip: trip.id, name: 'Hotel stay', total_price: 240, reservation: reservation.id })));