import { test, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import request from 'supertest'; import { createApp } from '../src/server/app.js'; let tmpDir; let app; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tripplan-stays-')); app = createApp({ dbPath: path.join(tmpDir, 'test.db'), sessionSecret: 'test-secret' }); }); afterEach(() => { try { if (app.locals.db) app.locals.db.close(); } catch { /* ignore */ } fs.rmSync(tmpDir, { recursive: true, force: true }); }); async function createAccount() { const agent = request.agent(app); const res = await agent.post('/api/auth/account').send({}); assert.equal(res.status, 201); return { agent, user: res.body.user }; } async function makeTrip(agent) { return (await agent.post('/api/trips').send({ name: 'S', start_date: '2026-08-01', end_date: '2026-08-31' })).body.trip; } // --------------------------------------------------------------------------- // end_date validation // --------------------------------------------------------------------------- test('end_date: accepted (>= date), rejected (< date), cleared via null', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); const base = `/api/trips/${trip.id}/entries`; const ok = await agent.post(base).send({ date: '2026-08-01', end_date: '2026-08-03', type: 'stay', title: 'Venice' }); assert.equal(ok.status, 201); assert.equal(ok.body.entry.end_date, '2026-08-03'); const bad = await agent.post(base).send({ date: '2026-08-05', end_date: '2026-08-02', type: 'stay', title: 'X' }); assert.equal(bad.status, 400); const cleared = await agent.patch(`/api/entries/${ok.body.entry.id}`).send({ end_date: null }); assert.equal(cleared.status, 200); assert.equal(cleared.body.entry.end_date, null); }); test('end_date: PATCHing date past a stored end_date is rejected', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); const entry = (await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', end_date: '2026-08-03', type: 'stay', title: 'Venice', })).body.entry; // Moving date to 08-10 leaves stored end_date (08-03) < date -> 400. const conflict = await agent.patch(`/api/entries/${entry.id}`).send({ date: '2026-08-10' }); assert.equal(conflict.status, 400); // Moving date and end_date together is fine. const ok = await agent.patch(`/api/entries/${entry.id}`).send({ date: '2026-08-10', end_date: '2026-08-12' }); assert.equal(ok.status, 200); assert.equal(ok.body.entry.date, '2026-08-10'); assert.equal(ok.body.entry.end_date, '2026-08-12'); }); test('a >24h flight round-trips end_date', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); const res = await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', end_date: '2026-08-02', type: 'flight', title: 'SYD-LHR', start_time: '21:00', end_time: '05:30', }); assert.equal(res.status, 201); assert.equal(res.body.entry.date, '2026-08-01'); assert.equal(res.body.entry.end_date, '2026-08-02'); }); // --------------------------------------------------------------------------- // Stay type + areas summary // --------------------------------------------------------------------------- test('stay entries accepted; summary reports stays count and areas in order', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); // Venice 3 days (name from location_name), then Berlin 7 days (name from title fallback). await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', end_date: '2026-08-03', type: 'stay', title: 'Venice stay', location_name: 'Venice', sort_order: 0, }); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-04', end_date: '2026-08-10', type: 'stay', title: 'Berlin', sort_order: 1, }); // A single-day stay (no end_date) -> days 1. await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-11', type: 'stay', title: 'Layover', location_name: 'Doha', }); const res = await agent.get(`/api/trips/${trip.id}/route`); assert.equal(res.status, 200); assert.equal(res.body.summary.stays, 3); assert.deepEqual(res.body.summary.areas, [ { name: 'Venice', days: 3 }, { name: 'Berlin', days: 7 }, { name: 'Doha', days: 1 }, ]); }); test('stay with location feeds the route as one stop', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', end_date: '2026-08-05', type: 'stay', title: 'Venice', location_name: 'Venice', lat: 45.44, lng: 12.32, }); const res = await agent.get(`/api/trips/${trip.id}/route`); assert.equal(res.body.stops.length, 1); assert.equal(res.body.stops[0].lat, 45.44); assert.equal(res.body.summary.stays, 1); });