From 259a8c6b4da8ad0d4e11ef2099a0389bd2868c15 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 12 Sep 2026 13:15:38 +0200 Subject: [PATCH] test(budget): settled_at through the HTTP pipe The e2e settlement round trip now sends settled_at on create and update and reads it back from the column, including a PUT that clears it to NULL. The service test covers the clear branch for null and the empty string the date picker sends. --- server/tests/e2e/budget.e2e.test.ts | 31 +++++++++++++++---- .../tests/unit/nest/budget.service.db.test.ts | 20 ++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/server/tests/e2e/budget.e2e.test.ts b/server/tests/e2e/budget.e2e.test.ts index 23c7c5958..04c4efd09 100644 --- a/server/tests/e2e/budget.e2e.test.ts +++ b/server/tests/e2e/budget.e2e.test.ts @@ -228,22 +228,41 @@ describe('Budget e2e (real auth guard + temp SQLite, real budget SQL)', () => { } }); - it('200 on settlement update with permission, persisting the new amount', async () => { + it('200 on settlement update with permission, persisting the new amount and day', async () => { const created = await request(server) .post(`/api/trips/${tripId}/budget/settlements`) .set('Cookie', sessionCookie(1)) - .send({ from_user_id: 2, to_user_id: 1, amount: 10 }); + .send({ from_user_id: 2, to_user_id: 1, amount: 10, settled_at: '2026-01-05' }); + expect(created.status).toBe(201); + expect(created.body.settlement.settled_at).toBe('2026-01-05'); + + const res = await request(server) + .put(`/api/trips/${tripId}/budget/settlements/${created.body.settlement.id}`) + .set('Cookie', sessionCookie(1)) + .send({ from_user_id: 2, to_user_id: 1, amount: 15, settled_at: '2026-01-09' }); + expect(res.status).toBe(200); + expect(res.body.settlement).toMatchObject({ id: created.body.settlement.id, from_user_id: 2, to_user_id: 1, amount: 15, settled_at: '2026-01-09' }); + + const row = db.prepare('SELECT amount, settled_at FROM budget_settlements WHERE id = ?').get(created.body.settlement.id); + expect(row).toEqual({ amount: 15, settled_at: '2026-01-09' }); + }); + + it('200 on settlement update that clears the day, leaving NULL in the column', async () => { + const created = await request(server) + .post(`/api/trips/${tripId}/budget/settlements`) + .set('Cookie', sessionCookie(1)) + .send({ from_user_id: 2, to_user_id: 1, amount: 10, settled_at: '2026-01-05' }); expect(created.status).toBe(201); const res = await request(server) .put(`/api/trips/${tripId}/budget/settlements/${created.body.settlement.id}`) .set('Cookie', sessionCookie(1)) - .send({ from_user_id: 2, to_user_id: 1, amount: 15 }); + .send({ from_user_id: 2, to_user_id: 1, amount: 10, settled_at: null }); expect(res.status).toBe(200); - expect(res.body.settlement).toMatchObject({ id: created.body.settlement.id, from_user_id: 2, to_user_id: 1, amount: 15 }); + expect(res.body.settlement.settled_at).toBeNull(); - const row = db.prepare('SELECT amount FROM budget_settlements WHERE id = ?').get(created.body.settlement.id); - expect(row).toEqual({ amount: 15 }); + const row = db.prepare('SELECT settled_at FROM budget_settlements WHERE id = ?').get(created.body.settlement.id); + expect(row).toEqual({ settled_at: null }); }); it('404 on settlement update when it does not exist', async () => { diff --git a/server/tests/unit/nest/budget.service.db.test.ts b/server/tests/unit/nest/budget.service.db.test.ts index 6cf9b7ae9..b5950cc76 100644 --- a/server/tests/unit/nest/budget.service.db.test.ts +++ b/server/tests/unit/nest/budget.service.db.test.ts @@ -790,6 +790,26 @@ describe('post-fold quirk fixes', () => { expect(untouched!.settled_at).toBe('2026-01-09'); }); + it('BUDGET-SVC-DB-043: clearing settled_at stores NULL, whether it arrives as null or an empty string', () => { + const { user: alice } = createUser(testDb, { username: 'alice' }); + const { user: bob } = createUser(testDb, { username: 'bob' }); + const trip = createTrip(testDb, alice.id); + const parties = { from_user_id: alice.id, to_user_id: bob.id, amount: 20 }; + + // The date picker's clear button sends '' and the contract allows null; both + // mean "no day of its own", never a stored empty string. + const blank = budget.insertSettlement(trip.id, { ...parties, settled_at: '' }, alice.id); + expect(blank!.settled_at).toBeNull(); + + const dated = budget.insertSettlement(trip.id, { ...parties, settled_at: '2026-01-05' }, alice.id); + expect(budget.applySettlementUpdate(dated!.id, trip.id, { ...parties, settled_at: null })!.settled_at).toBeNull(); + + budget.applySettlementUpdate(dated!.id, trip.id, { ...parties, settled_at: '2026-01-05' }); + expect(budget.applySettlementUpdate(dated!.id, trip.id, { ...parties, settled_at: '' })!.settled_at).toBeNull(); + const row = testDb.prepare('SELECT settled_at FROM budget_settlements WHERE id = ?').get(dated!.id) as { settled_at: string | null }; + expect(row.settled_at).toBeNull(); + }); + // ── Notes vs. itemized receipts (#1658) ──────────────────────────────────── it('BUDGET-SVC-DB-021: a note and a receipt are stored in their own columns', () => {