Spaces:
Sleeping
Sleeping
File size: 6,022 Bytes
1f5ea39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /**
* Day reorder + insert integration tests (#589) — exercises the real
* dayService against the real schema. Covers: position renumber, dates pinned
* to slots while content rides along by id, booking-date re-stamp, permutation
* validation, the accommodation-inversion guard, and insert (dated + dateless).
*/
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest';
const { testDb, dbMock } = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const Database = require('better-sqlite3');
const db = new Database(':memory:');
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA foreign_keys = ON');
db.exec('PRAGMA busy_timeout = 5000');
return { testDb: db, dbMock: { db, closeDb: () => {}, reinitialize: () => {}, canAccessTrip: vi.fn() } };
});
vi.mock('../../src/db/database', () => dbMock);
vi.mock('../../src/config', () => ({
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',
ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
updateJwtSecret: () => {},
DEFAULT_LANGUAGE: 'en',
}));
import { createTables } from '../../src/db/schema';
import { runMigrations } from '../../src/db/migrations';
import { resetTestDb } from '../helpers/test-db';
import { createUser, createTrip, createPlace, createDay, createDayAssignment, createReservation, createDayAccommodation } from '../helpers/factories';
import { reorderDays, insertDay, DayReorderError } from '../../src/services/dayService';
let userId: number;
beforeAll(() => {
createTables(testDb);
runMigrations(testDb);
});
beforeEach(() => {
resetTestDb(testDb);
userId = createUser(testDb).user.id;
});
afterAll(() => testDb.close());
const orderedDays = (tripId: number) =>
testDb.prepare('SELECT id, day_number, date FROM days WHERE trip_id = ? ORDER BY day_number').all(tripId) as
{ id: number; day_number: number; date: string | null }[];
describe('reorderDays', () => {
it('permutes positions, pins dates to slots, and content rides along by id', () => {
const trip = createTrip(testDb, userId, { start_date: '2026-03-01', end_date: '2026-03-03' });
const [d1, d2, d3] = orderedDays(trip.id);
const place = createPlace(testDb, trip.id);
createDayAssignment(testDb, d2.id, place.id); // place sits on day 2
// Move day 2 to the front: [d2, d1, d3]
reorderDays(trip.id, [d2.id, d1.id, d3.id]);
const after = orderedDays(trip.id);
expect(after.map(d => d.id)).toEqual([d2.id, d1.id, d3.id]);
// Dates stay pinned to their calendar slots
expect(after.map(d => d.date)).toEqual(['2026-03-01', '2026-03-02', '2026-03-03']);
// The place rides along with its day row (still attached to d2.id, now at slot 1)
const onD2 = testDb.prepare('SELECT * FROM day_assignments WHERE day_id = ?').all(d2.id);
expect(onD2).toHaveLength(1);
});
it('re-stamps a booking\'s date onto its day\'s new date, keeping the time', () => {
const trip = createTrip(testDb, userId, { start_date: '2026-03-01', end_date: '2026-03-03' });
const [d1, d2, d3] = orderedDays(trip.id);
const res = createReservation(testDb, trip.id, { day_id: d2.id, type: 'restaurant' });
testDb.prepare('UPDATE reservations SET reservation_time = ? WHERE id = ?').run('2026-03-02T19:00', res.id);
reorderDays(trip.id, [d2.id, d1.id, d3.id]); // d2 moves to the 2026-03-01 slot
const r = testDb.prepare('SELECT reservation_time FROM reservations WHERE id = ?').get(res.id) as { reservation_time: string };
expect(r.reservation_time).toBe('2026-03-01T19:00');
});
it('rejects an orderedIds list that is not a permutation of the trip days', () => {
const trip = createTrip(testDb, userId, { start_date: '2026-03-01', end_date: '2026-03-03' });
const [d1, d2] = orderedDays(trip.id);
expect(() => reorderDays(trip.id, [d1.id, d2.id])).toThrow(DayReorderError);
});
it('blocks a move that would make an accommodation end before it starts, and rolls back', () => {
const trip = createTrip(testDb, userId, { start_date: '2026-03-01', end_date: '2026-03-03' });
const [d1, d2, d3] = orderedDays(trip.id);
const place = createPlace(testDb, trip.id);
createDayAccommodation(testDb, trip.id, place.id, d1.id, d2.id); // stay spans day 1 -> day 2
// Put the start day (d1) after the end day (d2): [d2, d3, d1]
expect(() => reorderDays(trip.id, [d2.id, d3.id, d1.id])).toThrow(DayReorderError);
// Transaction rolled back: original order intact
expect(orderedDays(trip.id).map(d => d.id)).toEqual([d1.id, d2.id, d3.id]);
});
});
describe('insertDay', () => {
it('inserts an empty day at a position on a dateless trip and shifts the rest', () => {
const trip = createTrip(testDb, userId);
const d1 = createDay(testDb, trip.id);
const d2 = createDay(testDb, trip.id);
const d3 = createDay(testDb, trip.id);
const created = insertDay(trip.id, 1);
const after = orderedDays(trip.id);
expect(after).toHaveLength(4);
expect(after[0].id).toBe(created.id);
expect(after[0].date).toBeNull();
expect(after.slice(1).map(d => d.id)).toEqual([d1.id, d2.id, d3.id]);
});
it('inserts at the front of a dated trip: dates stay contiguous and the trip extends', () => {
const trip = createTrip(testDb, userId, { start_date: '2026-03-01', end_date: '2026-03-03' });
const [d1, d2, d3] = orderedDays(trip.id);
const created = insertDay(trip.id, 1);
const after = orderedDays(trip.id);
expect(after).toHaveLength(4);
expect(after[0].id).toBe(created.id);
expect(after.map(d => d.date)).toEqual(['2026-03-01', '2026-03-02', '2026-03-03', '2026-03-04']);
// Old content shifted down a slot
expect(after.slice(1).map(d => d.id)).toEqual([d1.id, d2.id, d3.id]);
// Trip range extended by one day
const t = testDb.prepare('SELECT end_date FROM trips WHERE id = ?').get(trip.id) as { end_date: string };
expect(t.end_date).toBe('2026-03-04');
});
});
|