mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(trips): shortening a trip says first which days go and what is on them
The trip dialog on the desktop and the trip sheet on a phone now ask before a save whose new dates remove days with something on them. The question names those days the way the day list does and lists what goes with them in the same rows the delete day question uses: planned places stay in the place list, notes and day texts are deleted, bookings stay under Bookings, and a stay that checks in or out on a removed day goes as a whole while its booking and expense remain, shown in the danger tone. A moved start gets both questions in one step, with the booking hint following the chosen shift mode, and a save that only drops empty days asks nothing. The rule that lays the days out moved into shared as planDayGrid, with resolveDayGridRange deciding whether the rows are rebuilt at all. generateDays on the server now carries that plan out in one transaction, so the warning names exactly the days the save takes, and a seeded fuzz test holds the new rebuild to the old one over 300 random day grids. The update_trip tool says in its description what shortening takes and lists removed_days in its answer. Alongside, the dialog sends a day count only when it changed, so a stale count no longer trims a day just added, stays are reloaded after every date change including the one made on a phone, and the date shift tip now points to adding a day under Reorder days.
This commit is contained in:
@@ -239,6 +239,18 @@ describe('Trips e2e (real auth guard + temp SQLite)', () => {
|
||||
expect(db.prepare('SELECT end_date FROM trips WHERE id = ?').get(week.body.trip.id)).toEqual({ end_date: '2026-07-07' });
|
||||
});
|
||||
|
||||
it('200 update with an earlier end drops the last days, and the answer stays { trip }', async () => {
|
||||
const week = await request(server).post('/api/trips').set('Cookie', sessionCookie(1))
|
||||
.send({ title: 'Week', start_date: '2026-07-01', end_date: '2026-07-07' });
|
||||
const kept = db.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number LIMIT 5').all(week.body.trip.id);
|
||||
const res = await request(server).put(`/api/trips/${week.body.trip.id}`).set('Cookie', sessionCookie(1))
|
||||
.send({ end_date: '2026-07-05' });
|
||||
expect(res.status).toBe(200);
|
||||
expect(Object.keys(res.body)).toEqual(['trip']);
|
||||
expect(res.body.trip).toMatchObject({ start_date: '2026-07-01', end_date: '2026-07-05', day_count: 5 });
|
||||
expect(db.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number').all(week.body.trip.id)).toEqual(kept);
|
||||
});
|
||||
|
||||
it('404 on a missing trip', async () => {
|
||||
const res = await request(server).get('/api/trips/77').set('Cookie', sessionCookie(1));
|
||||
expect(res.status).toBe(404);
|
||||
|
||||
@@ -501,6 +501,33 @@ describe('Update trip', () => {
|
||||
const all = testDb.prepare('SELECT * FROM day_assignments WHERE id IN (?, ?)').all(a4.id, a5.id) as { id: number }[];
|
||||
expect(all).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('TRIP-028: An earlier end takes the last day with its stay, while the hotel booking of that stay remains', async () => {
|
||||
// Pins what the trip dialog warns about before such a save: the stay goes as
|
||||
// a whole, even with nights still inside the trip, and its booking does not.
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2026-09-01', end_date: '2026-09-06' });
|
||||
const days = testDb.prepare('SELECT * FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number }[];
|
||||
const place = createPlace(testDb, trip.id, { name: 'Harbour Hotel' });
|
||||
const stay = createDayAccommodation(testDb, trip.id, place.id, days[3].id, days[5].id);
|
||||
const booking = createReservation(testDb, trip.id, { title: 'Harbour Hotel booking', type: 'hotel', day_id: days[3].id });
|
||||
testDb.prepare('UPDATE reservations SET accommodation_id = ? WHERE id = ?').run(stay.id, booking.id);
|
||||
const sightseeing = createDayAssignment(testDb, days[5].id, place.id);
|
||||
|
||||
const res = await request(app)
|
||||
.put(`/api/trips/${trip.id}`)
|
||||
.set('Cookie', authCookie(user.id))
|
||||
.send({ start_date: '2026-09-01', end_date: '2026-09-05' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const daysAfter = testDb.prepare('SELECT id, date FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number; date: string }[];
|
||||
expect(daysAfter.map(d => d.id)).toEqual(days.slice(0, 5).map(d => d.id));
|
||||
expect(daysAfter.at(-1)!.date).toBe('2026-09-05');
|
||||
expect(testDb.prepare('SELECT 1 FROM day_accommodations WHERE id = ?').get(stay.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT 1 FROM day_assignments WHERE id = ?').get(sightseeing.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT title FROM reservations WHERE id = ?').get(booking.id)).toEqual({ title: 'Harbour Hotel booking' });
|
||||
expect(testDb.prepare('SELECT 1 FROM places WHERE id = ?').get(place.id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -421,6 +421,33 @@ describe('Tool: update_trip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('says in its description what shortening a dated trip takes, and where the result lists it', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const tool = (await h.client.listTools()).tools.find(t => t.name === 'update_trip');
|
||||
expect(tool?.description).toContain('Shortening a dated trip deletes its last days by position');
|
||||
expect(tool?.description).toContain('removed_days');
|
||||
});
|
||||
});
|
||||
|
||||
it('lists the days an earlier end removed, as they stood before, and none for a rename', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2026-07-01', end_date: '2026-07-05' });
|
||||
const days = testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number }[];
|
||||
await withHarness(user.id, async (h) => {
|
||||
type Answer = { trip: { end_date: string; title: string }; removed_days?: unknown[] };
|
||||
const shortened = parseToolResult(await h.client.callTool({ name: 'update_trip', arguments: { tripId: trip.id, end_date: '2026-07-03' } })) as Answer;
|
||||
expect(shortened.trip.end_date).toBe('2026-07-03');
|
||||
expect(shortened.removed_days).toEqual([
|
||||
{ id: days[3].id, day_number: 4, date: '2026-07-04', reason: 'overflow' },
|
||||
{ id: days[4].id, day_number: 5, date: '2026-07-05', reason: 'overflow' },
|
||||
]);
|
||||
const renamed = parseToolResult(await h.client.callTool({ name: 'update_trip', arguments: { tripId: trip.id, title: 'Shorter' } })) as Answer;
|
||||
expect(renamed.trip.title).toBe('Shorter');
|
||||
expect(renamed).not.toHaveProperty('removed_days');
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a date range longer than MAX_TRIP_DAYS and leaves the trip untouched', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Untouched', start_date: '2026-07-01', end_date: '2026-07-07' });
|
||||
|
||||
@@ -54,7 +54,7 @@ import { createTables } from '../../../src/db/schema';
|
||||
import { runMigrations } from '../../../src/db/migrations';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser, createTrip, createReservation, createPlace, createDay, createDayAssignment, createDayNote, addTripMember } from '../../helpers/factories';
|
||||
import { MAX_TRIP_DAYS } from '@trek/shared';
|
||||
import { MAX_TRIP_DAYS, resolveDayGridRange, tripSpanDays } from '@trek/shared';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { DaysService } from '../../../src/nest/days/days.service';
|
||||
import { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
@@ -398,6 +398,240 @@ describe('generateDays', () => {
|
||||
expect(stillDateless).toHaveLength(1);
|
||||
expect(getAssignments(stillDateless[0].id)[0].id).toBe(assignment.id);
|
||||
});
|
||||
|
||||
// ── generateDays carries out the shared planDayGrid plan ──────────────────
|
||||
// The trip dialog warns about lost days by the same plan, so the plan has to
|
||||
// be exactly what the rebuild did before it was written down in shared.
|
||||
|
||||
function addUndatedDay(tripId: number) {
|
||||
const next = (testDb.prepare('SELECT COALESCE(MAX(day_number), 0) + 1 AS n FROM days WHERE trip_id = ?').get(tripId) as { n: number }).n;
|
||||
const id = Number(testDb.prepare('INSERT INTO days (trip_id, day_number, date) VALUES (?, ?, NULL)').run(tripId, next).lastInsertRowid);
|
||||
return id;
|
||||
}
|
||||
|
||||
function addStay(tripId: number, placeId: number, startDayId: number, endDayId: number) {
|
||||
return Number(testDb.prepare(
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id) VALUES (?, ?, ?, ?)',
|
||||
).run(tripId, placeId, startDayId, endDayId).lastInsertRowid);
|
||||
}
|
||||
|
||||
const stayExists = (id: number) => !!testDb.prepare('SELECT 1 FROM day_accommodations WHERE id = ?').get(id);
|
||||
|
||||
it('TRIP-SVC-070: a stay from a removed day to a spare day goes, and the spare day it left empty goes too', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2025-07-01', end_date: '2025-07-05' });
|
||||
const days = getDays(trip.id);
|
||||
const spare = addUndatedDay(trip.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const stay = addStay(trip.id, place.id, days[4].id, spare);
|
||||
|
||||
const plan = svc.generateDays(trip.id, '2025-07-01', '2025-07-04');
|
||||
|
||||
expect(plan.removed.map(r => [r.id, r.reason])).toEqual([[days[4].id, 'overflow'], [spare, 'spare']]);
|
||||
expect(stayExists(stay)).toBe(false);
|
||||
expect(getDays(trip.id).map(d => d.id)).toEqual(days.slice(0, 4).map(d => d.id));
|
||||
});
|
||||
|
||||
it('TRIP-SVC-071: only moving the dates drops an empty spare day and keeps one with a note', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2025-07-01', end_date: '2025-07-03' });
|
||||
const empty = addUndatedDay(trip.id);
|
||||
const noted = addUndatedDay(trip.id);
|
||||
createDayNote(testDb, noted, trip.id, { text: 'Buffer' });
|
||||
|
||||
const plan = svc.generateDays(trip.id, '2025-07-11', '2025-07-13');
|
||||
|
||||
expect(plan.removed).toEqual([{ id: empty, day_number: 4, date: null, reason: 'spare' }]);
|
||||
const after = getDays(trip.id);
|
||||
expect(after.map(d => d.date)).toEqual(['2025-07-11', '2025-07-12', '2025-07-13', null]);
|
||||
expect(after[3]).toMatchObject({ id: noted, day_number: 4 });
|
||||
expect(getNotes(noted)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('TRIP-SVC-072: without dates only empty days are trimmed, the highest numbers first', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const ids = Array.from({ length: 6 }, () => addUndatedDay(trip.id));
|
||||
const place = createPlace(testDb, trip.id);
|
||||
createDayAssignment(testDb, ids[1], place.id);
|
||||
createDayAssignment(testDb, ids[5], place.id);
|
||||
|
||||
const plan = svc.generateDays(trip.id, null, null, 3);
|
||||
|
||||
expect(plan.removed.map(r => r.id)).toEqual([ids[4], ids[3], ids[2]]);
|
||||
expect(getDays(trip.id).map(d => [d.id, d.day_number])).toEqual([[ids[0], 1], [ids[1], 2], [ids[5], 3]]);
|
||||
});
|
||||
|
||||
it('TRIP-SVC-073: generateDays returns the plan it carried out', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2025-07-01', end_date: '2025-07-03' });
|
||||
const before = getDays(trip.id);
|
||||
const spare = addUndatedDay(trip.id);
|
||||
|
||||
const plan = svc.generateDays(trip.id, '2025-07-01', '2025-07-06');
|
||||
|
||||
const after = getDays(trip.id);
|
||||
expect(plan.removed).toEqual([]);
|
||||
expect(plan.rows.map(r => r.date)).toEqual(after.map(d => d.date));
|
||||
expect(plan.rows.slice(0, 4).map(r => r.id)).toEqual([...before.map(d => d.id), spare]);
|
||||
// New rows come back without an id and are the rows the insert created.
|
||||
expect(plan.rows.slice(4).map(r => r.id)).toEqual([null, null]);
|
||||
expect(after.slice(4).every(d => !before.some(b => b.id === d.id) && d.id !== spare)).toBe(true);
|
||||
|
||||
const shrink = svc.generateDays(trip.id, '2025-07-01', '2025-07-02');
|
||||
expect(shrink.removed.map(r => r.id)).toEqual(after.slice(2).map(d => d.id));
|
||||
expect(getDays(trip.id).map(d => d.id)).toEqual(shrink.rows.map(r => r.id));
|
||||
});
|
||||
|
||||
it('TRIP-SVC-074: fuzz, 300 random day grids end exactly where the rebuild before the shared plan left them', () => {
|
||||
// The rebuild as it stood before planDayGrid, kept here as the oracle.
|
||||
function legacyGenerateDays(tripId: number, startDate: string | null, endDate: string | null, dayCount?: number) {
|
||||
const existing = testDb.prepare('SELECT id, day_number, date FROM days WHERE trip_id = ?').all(tripId) as { id: number; day_number: number; date: string | null }[];
|
||||
const setDayNumber = testDb.prepare('UPDATE days SET day_number = ? WHERE id = ?');
|
||||
const renumber = (list: { id: number }[]) => {
|
||||
list.forEach((d, i) => setDayNumber.run(-(i + 1), d.id));
|
||||
list.forEach((d, i) => setDayNumber.run(i + 1, d.id));
|
||||
};
|
||||
if (!startDate || !endDate) {
|
||||
for (const d of existing.filter(d => d.date)) testDb.prepare('UPDATE days SET date = NULL WHERE id = ?').run(d.id);
|
||||
const all = testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number').all(tripId) as { id: number }[];
|
||||
const target = Math.min(Math.max(dayCount ?? (all.length || 7), 1), MAX_TRIP_DAYS);
|
||||
const needed = target - all.length;
|
||||
if (needed > 0) {
|
||||
for (let i = 0; i < needed; i++) testDb.prepare('INSERT INTO days (trip_id, day_number, date) VALUES (?, ?, NULL)').run(tripId, all.length + i + 1);
|
||||
} else if (needed < 0) {
|
||||
const candidates = testDb.prepare(
|
||||
`SELECT d.id FROM days d WHERE d.trip_id = ?
|
||||
AND NOT EXISTS (SELECT 1 FROM day_assignments da WHERE da.day_id = d.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM day_notes dn WHERE dn.day_id = d.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM day_accommodations dac WHERE dac.start_day_id = d.id OR dac.end_day_id = d.id)
|
||||
ORDER BY d.day_number DESC LIMIT ?`,
|
||||
).all(tripId, -needed) as { id: number }[];
|
||||
for (const d of candidates) testDb.prepare('DELETE FROM days WHERE id = ?').run(d.id);
|
||||
}
|
||||
renumber(testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number').all(tripId) as { id: number }[]);
|
||||
return;
|
||||
}
|
||||
const numDays = tripSpanDays(startDate, endDate);
|
||||
const targetDates = Array.from({ length: Math.max(numDays, 0) }, (_, i) => addDaysIso(startDate, i));
|
||||
const dated = existing.filter(d => d.date).sort((a, b) => a.day_number - b.day_number);
|
||||
const dateless = existing.filter(d => !d.date).sort((a, b) => a.day_number - b.day_number);
|
||||
[...dated, ...dateless].forEach((d, i) => setDayNumber.run(-(i + 1), d.id));
|
||||
const assignDay = testDb.prepare('UPDATE days SET date = ?, day_number = ? WHERE id = ?');
|
||||
let datelessIdx = 0;
|
||||
targetDates.forEach((date, i) => {
|
||||
if (i < dated.length) assignDay.run(date, i + 1, dated[i].id);
|
||||
else if (datelessIdx < dateless.length) assignDay.run(date, i + 1, dateless[datelessIdx++].id);
|
||||
else testDb.prepare('INSERT INTO days (trip_id, day_number, date) VALUES (?, ?, ?)').run(tripId, i + 1, date);
|
||||
});
|
||||
for (let i = targetDates.length; i < dated.length; i++) testDb.prepare('DELETE FROM days WHERE id = ?').run(dated[i].id);
|
||||
const isEmpty = testDb.prepare(
|
||||
`SELECT NOT EXISTS (SELECT 1 FROM day_assignments da WHERE da.day_id = @id)
|
||||
AND NOT EXISTS (SELECT 1 FROM day_notes dn WHERE dn.day_id = @id)
|
||||
AND NOT EXISTS (SELECT 1 FROM day_accommodations dac WHERE dac.start_day_id = @id OR dac.end_day_id = @id) AS empty`,
|
||||
);
|
||||
const maxAssigned = Math.max(targetDates.length, dated.length);
|
||||
let kept = 0;
|
||||
for (let i = datelessIdx; i < dateless.length; i++) {
|
||||
if ((isEmpty.get({ id: dateless[i].id }) as { empty: number }).empty) testDb.prepare('DELETE FROM days WHERE id = ?').run(dateless[i].id);
|
||||
else setDayNumber.run(maxAssigned + ++kept, dateless[i].id);
|
||||
}
|
||||
renumber(testDb.prepare('SELECT id FROM days WHERE trip_id = ? ORDER BY day_number').all(tripId) as { id: number }[]);
|
||||
}
|
||||
|
||||
// A seeded generator, so a failure names a grid that can be replayed.
|
||||
let seed = 20260923;
|
||||
const rand = () => {
|
||||
seed = (seed + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
const int = (lo: number, hi: number) => lo + Math.floor(rand() * (hi - lo + 1));
|
||||
|
||||
const snapshot = (tripId: number) => ({
|
||||
days: testDb.prepare('SELECT id, day_number, date FROM days WHERE trip_id = ? ORDER BY day_number').all(tripId),
|
||||
stays: testDb.prepare('SELECT id FROM day_accommodations WHERE trip_id = ? ORDER BY id').all(tripId),
|
||||
assignments: testDb.prepare(
|
||||
'SELECT da.id, da.day_id FROM day_assignments da JOIN days d ON d.id = da.day_id WHERE d.trip_id = ? ORDER BY da.id',
|
||||
).all(tripId),
|
||||
notes: testDb.prepare('SELECT id, day_id FROM day_notes WHERE trip_id = ? ORDER BY id').all(tripId),
|
||||
});
|
||||
const ROLLBACK = new Error('rollback');
|
||||
|
||||
const { user } = createUser(testDb);
|
||||
let removing = 0;
|
||||
for (let round = 0; round < 300; round++) {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const dayIds: number[] = [];
|
||||
const count = int(0, 9);
|
||||
for (let n = 1; n <= count; n++) {
|
||||
const date = rand() < 0.7 ? addDaysIso('2026-03-20', int(0, 20)) : null;
|
||||
const id = Number(testDb.prepare('INSERT INTO days (trip_id, day_number, date) VALUES (?, ?, ?)').run(trip.id, n, date).lastInsertRowid);
|
||||
dayIds.push(id);
|
||||
if (rand() < 0.3) createDayAssignment(testDb, id, place.id);
|
||||
if (rand() < 0.2) createDayNote(testDb, id, trip.id);
|
||||
}
|
||||
if (dayIds.length > 0) {
|
||||
for (let k = int(0, 3); k > 0; k--) addStay(trip.id, place.id, dayIds[int(0, dayIds.length - 1)], dayIds[int(0, dayIds.length - 1)]);
|
||||
}
|
||||
let range: [string | null, string | null, number | undefined];
|
||||
if (rand() < 0.25) {
|
||||
range = [null, null, rand() < 0.4 ? undefined : int(1, 12)];
|
||||
} else {
|
||||
const start = addDaysIso('2026-03-15', int(0, 20));
|
||||
range = [start, addDaysIso(start, int(0, 11)), rand() < 0.8 ? undefined : int(1, 12)];
|
||||
}
|
||||
|
||||
const before = snapshot(trip.id);
|
||||
let expected: ReturnType<typeof snapshot> | undefined;
|
||||
try {
|
||||
testDb.transaction(() => {
|
||||
legacyGenerateDays(trip.id, ...range);
|
||||
expected = snapshot(trip.id);
|
||||
throw ROLLBACK;
|
||||
})();
|
||||
} catch (err) {
|
||||
if (err !== ROLLBACK) throw err;
|
||||
}
|
||||
expect(snapshot(trip.id), `round ${round}: the oracle must leave no trace`).toEqual(before);
|
||||
|
||||
const plan = svc.generateDays(trip.id, ...range);
|
||||
const actual = snapshot(trip.id);
|
||||
expect(actual, `round ${round}: ${JSON.stringify({ range, before: before.days })}`).toEqual(expected);
|
||||
const survivors = new Set((actual.days as { id: number }[]).map(d => d.id));
|
||||
expect(plan.removed.map(r => r.id).sort((a, b) => a - b), `round ${round}: removed`)
|
||||
.toEqual((before.days as { id: number }[]).map(d => d.id).filter(id => !survivors.has(id)).sort((a, b) => a - b));
|
||||
if (plan.removed.length > 0) removing++;
|
||||
}
|
||||
// The grids are random, not easy: plenty of them lose days.
|
||||
expect(removing).toBeGreaterThan(60);
|
||||
});
|
||||
|
||||
it('TRIP-SVC-075: resolveRange reads a range the way the shared resolveDayGridRange does, a day_count of 0 included', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const dated = svc.getRaw(createTrip(testDb, user.id, { start_date: '2025-07-01', end_date: '2025-07-05' }).id)!;
|
||||
const undated = svc.getRaw(createTrip(testDb, user.id).id)!;
|
||||
const resolve = (trip: typeof dated, data: Parameters<typeof svc.updateTrip>[2]) => svc['resolveRange'](trip, data);
|
||||
const cases: [typeof dated, Parameters<typeof svc.updateTrip>[2]][] = [
|
||||
[dated, {}],
|
||||
[dated, { title: 'Renamed' }],
|
||||
[dated, { end_date: '2025-07-03' }],
|
||||
[dated, { start_date: '2025-07-03', end_date: '2025-07-05' }],
|
||||
[dated, { start_date: null, end_date: null }],
|
||||
[dated, { start_date: null, end_date: null, day_count: 3 }],
|
||||
[undated, { day_count: 0 }],
|
||||
[undated, { day_count: 4 }],
|
||||
[undated, { day_count: MAX_TRIP_DAYS + 1 }],
|
||||
[undated, { start_date: '2025-07-01', end_date: '2025-07-02' }],
|
||||
];
|
||||
for (const [trip, data] of cases) expect(resolve(trip, data), JSON.stringify(data)).toEqual(resolveDayGridRange(trip, data));
|
||||
expect(resolve(undated, { day_count: 0 }).regenerate).toBe(false);
|
||||
// The refusals stay on the server side of the rule.
|
||||
expect(() => resolve(dated, { start_date: '2025-07-05', end_date: '2025-07-01' })).toThrow('End date must be after start date');
|
||||
expect(() => resolve(dated, { end_date: '2025-06-30' })).toThrow('End date must be after start date');
|
||||
});
|
||||
});
|
||||
|
||||
// ── deleteOldCover — path containment ──────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user