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-rental-')); 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: 'R', start_date: '2026-08-01', end_date: '2026-08-10' })).body.trip; } const RENTAL = { brand: 'Toyota', model: 'Yaris Cross', car_type: 'SUV', booking_ref: 'RC-889231', included_km: 1500, pickup: { date: '2026-08-01', time: '09:00', location_name: 'CNX Airport', lat: 18.77, lng: 98.96 }, dropoff: { date: '2026-08-07', time: '18:00', location_name: 'Old Town', lat: 18.79, lng: 98.98 }, }; // --------------------------------------------------------------------------- // Rental validation // --------------------------------------------------------------------------- test('rental: accepted on rental entry, parsed back, cleared via null', 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', type: 'rental', title: 'Car', rental: RENTAL, }); assert.equal(res.status, 201); const r = res.body.entry.rental; assert.equal(r.brand, 'Toyota'); assert.equal(r.included_km, 1500); assert.equal(r.pickup.location_name, 'CNX Airport'); assert.equal(r.dropoff.date, '2026-08-07'); const cleared = await agent.patch(`/api/entries/${res.body.entry.id}`).send({ rental: null }); assert.equal(cleared.status, 200); assert.equal(cleared.body.entry.rental, null); }); test('rental: rejected on non-rental entries', 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', type: 'hotel', title: 'Nope', rental: RENTAL, }); assert.equal(res.status, 400); }); test('rental: bad shapes rejected', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); const base = `/api/trips/${trip.id}/entries`; const longBrand = await agent.post(base).send({ date: '2026-08-01', type: 'rental', title: 'x', rental: { brand: 'z'.repeat(61) }, }); assert.equal(longBrand.status, 400); const negKm = await agent.post(base).send({ date: '2026-08-01', type: 'rental', title: 'x', rental: { included_km: -5 }, }); assert.equal(negKm.status, 400); const noDate = await agent.post(base).send({ date: '2026-08-01', type: 'rental', title: 'x', rental: { pickup: { time: '09:00' } }, }); assert.equal(noDate.status, 400); const halfCoord = await agent.post(base).send({ date: '2026-08-01', type: 'rental', title: 'x', rental: { pickup: { date: '2026-08-01', lat: 18.77 } }, }); assert.equal(halfCoord.status, 400); }); test('rental: minimal object with only included_km null is accepted', 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', type: 'rental', title: 'Car', rental: { brand: 'Kia', included_km: null }, }); assert.equal(res.status, 201); assert.equal(res.body.entry.rental.included_km, null); }); // --------------------------------------------------------------------------- // Summary + costs // --------------------------------------------------------------------------- test('route summary: rentals count and includedKm sum', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', type: 'rental', title: 'Car A', rental: { included_km: 1500 }, }); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-05', type: 'rental', title: 'Car B', rental: { included_km: 300 }, }); const res = await agent.get(`/api/trips/${trip.id}/route`); assert.equal(res.status, 200); assert.equal(res.body.summary.rentals, 2); assert.equal(res.body.summary.includedKm, 1800); }); test('route summary: includedKm is null when no rental specifies one', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', type: 'rental', title: 'Car', rental: { brand: 'Kia' }, }); const res = await agent.get(`/api/trips/${trip.id}/route`); assert.equal(res.body.summary.rentals, 1); assert.equal(res.body.summary.includedKm, null); }); test('costs byType includes rental', async () => { const { agent } = await createAccount(); const trip = await makeTrip(agent); await agent.post(`/api/trips/${trip.id}/entries`).send({ date: '2026-08-01', type: 'rental', title: 'Car', price: 240, rental: { brand: 'Kia' }, }); const c = (await agent.get(`/api/trips/${trip.id}/costs`)).body; assert.equal(c.totalCost, 240); assert.equal(c.byType.rental, 240); });