mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
fix(trips): preserve day content when trip date range changes
Rewrites generateDays to remap days positionally by day_number instead of matching by date identity. Previously any date range shift with no overlap would cascade-delete all day_assignments, day_notes, and day_accommodations. New behaviour: - Shift/partial overlap: existing days remapped to new dates in order - Shrink: overflow days become dateless (date=NULL) instead of deleted, preserving all child data for manual reassignment - Grow: existing days kept, new empty days appended - Clear dates: all days nullified, content intact Also fixes a UNIQUE(trip_id, day_number) collision that would occur when spare dateless days remained after growing into a partially-dateless trip (maxAssigned base was wrong). Closes #646
This commit is contained in:
@@ -49,7 +49,7 @@ import { createApp } from '../../src/app';
|
||||
import { createTables } from '../../src/db/schema';
|
||||
import { runMigrations } from '../../src/db/migrations';
|
||||
import { resetTestDb } from '../helpers/test-db';
|
||||
import { createUser, createAdmin, createTrip, addTripMember, createPlace, createReservation, createTag, createDayAccommodation, createBudgetItem, createPackingItem, createDayNote } from '../helpers/factories';
|
||||
import { createUser, createAdmin, createTrip, addTripMember, createPlace, createReservation, createTag, createDayAccommodation, createBudgetItem, createPackingItem, createDayNote, createDayAssignment } from '../helpers/factories';
|
||||
import { authCookie } from '../helpers/auth';
|
||||
import { loginAttempts, mfaAttempts } from '../../src/routes/auth';
|
||||
import { invalidatePermissionsCache } from '../../src/services/permissions';
|
||||
@@ -430,6 +430,65 @@ describe('Update trip', () => {
|
||||
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('TRIP-023 — Shifting trip date range preserves day assignments positionally', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2026-08-01', end_date: '2026-08-05' });
|
||||
|
||||
const days = testDb.prepare('SELECT * FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number; date: string }[];
|
||||
expect(days).toHaveLength(5);
|
||||
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const assignment = createDayAssignment(testDb, days[0].id, place.id);
|
||||
const note = createDayNote(testDb, days[1].id, trip.id, { text: 'pack sunscreen' });
|
||||
|
||||
// Shift forward 10 days (zero overlap with original range)
|
||||
const res = await request(app)
|
||||
.put(`/api/trips/${trip.id}`)
|
||||
.set('Cookie', authCookie(user.id))
|
||||
.send({ start_date: '2026-08-11', end_date: '2026-08-15' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const daysAfter = testDb.prepare('SELECT * FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number; date: string | null }[];
|
||||
expect(daysAfter).toHaveLength(5);
|
||||
expect(daysAfter.map(d => d.date)).toEqual(['2026-08-11', '2026-08-12', '2026-08-13', '2026-08-14', '2026-08-15']);
|
||||
|
||||
const assignmentsAfter = testDb.prepare('SELECT * FROM day_assignments WHERE id = ?').get(assignment.id) as { day_id: number } | undefined;
|
||||
expect(assignmentsAfter).toBeDefined();
|
||||
expect(assignmentsAfter!.day_id).toBe(daysAfter[0].id);
|
||||
|
||||
const notesAfter = testDb.prepare('SELECT * FROM day_notes WHERE id = ?').get(note.id) as { day_id: number } | undefined;
|
||||
expect(notesAfter).toBeDefined();
|
||||
expect(notesAfter!.day_id).toBe(daysAfter[1].id);
|
||||
});
|
||||
|
||||
it('TRIP-024 — Shrinking trip date range keeps overflow days as dateless with content intact', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { start_date: '2026-09-01', end_date: '2026-09-05' });
|
||||
|
||||
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);
|
||||
const a4 = createDayAssignment(testDb, days[3].id, place.id);
|
||||
const a5 = createDayAssignment(testDb, days[4].id, place.id);
|
||||
|
||||
// Shrink from 5 to 3 days
|
||||
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-03' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const daysAfter = testDb.prepare('SELECT * FROM days WHERE trip_id = ? ORDER BY day_number').all(trip.id) as { id: number; date: string | null }[];
|
||||
expect(daysAfter).toHaveLength(5);
|
||||
expect(daysAfter.filter(d => d.date !== null)).toHaveLength(3);
|
||||
expect(daysAfter.filter(d => d.date === null)).toHaveLength(2);
|
||||
|
||||
// Overflow assignments survived
|
||||
const all = testDb.prepare('SELECT * FROM day_assignments WHERE id IN (?, ?)').all(a4.id, a5.id) as { id: number }[];
|
||||
expect(all).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user