From 5fd110ed2353cb2491ee510b451242bfc9ee0a0c Mon Sep 17 00:00:00 2001 From: eric Date: Sun, 5 Apr 2026 18:01:48 -0400 Subject: [PATCH] test: add 8 export route tests for CSV/JSON payroll export Covers: default CSV, explicit CSV, JSON array, 404 non-existent farm, 400 invalid format, case-insensitive format param, unpaid payment status default, and empty farm header-only CSV. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/test/api.test.js | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/backend/test/api.test.js b/backend/test/api.test.js index e55d0cb..41ad622 100644 --- a/backend/test/api.test.js +++ b/backend/test/api.test.js @@ -1250,3 +1250,77 @@ describe('Multi-farm support and data isolation', () => { }); }); +// ------------------------------------------------------------------ export + +describe('Export', () => { + test('GET /farm/:id/export returns CSV by default', async () => { + const res = await get(`/farm/${farmId}/export`); + assert.equal(res.status, 200); + assert.ok(typeof res.body === 'string', 'CSV response should be a string'); + assert.ok(res.body.includes('worker_name,shift_date,checked_in_at,amount_sats,payment_status'), + 'CSV should contain header row'); + }); + + test('GET /farm/:id/export?format=csv returns CSV with header', async () => { + const res = await get(`/farm/${farmId}/export?format=csv`); + assert.equal(res.status, 200); + assert.ok(typeof res.body === 'string'); + const lines = res.body.trim().split('\r\n'); + assert.ok(lines.length >= 1, 'Should have at least a header line'); + assert.equal(lines[0], 'worker_name,shift_date,checked_in_at,amount_sats,payment_status'); + }); + + test('GET /farm/:id/export?format=json returns JSON array', async () => { + const res = await get(`/farm/${farmId}/export?format=json`); + assert.equal(res.status, 200); + assert.ok(Array.isArray(res.body), 'JSON export should be an array'); + if (res.body.length > 0) { + const row = res.body[0]; + assert.ok('worker_name' in row); + assert.ok('shift_date' in row); + assert.ok('checked_in_at' in row); + assert.ok('amount_sats' in row); + assert.ok('payment_status' in row); + } + }); + + test('GET /farm/:id/export returns 404 for non-existent farm', async () => { + const res = await get('/farm/999999/export'); + assert.equal(res.status, 404); + assert.equal(res.body.error, 'Farm not found'); + }); + + test('GET /farm/:id/export?format=xml returns 400 for invalid format', async () => { + const res = await get(`/farm/${farmId}/export?format=xml`); + assert.equal(res.status, 400); + assert.ok(res.body.error.includes('Invalid format')); + }); + + test('GET /farm/:id/export?format=JSON is case-insensitive', async () => { + const res = await get(`/farm/${farmId}/export?format=JSON`); + assert.equal(res.status, 200); + assert.ok(Array.isArray(res.body), 'Uppercase JSON should work'); + }); + + test('JSON export rows default to unpaid when no payment exists', async () => { + const res = await get(`/farm/${farmId}/export?format=json`); + assert.equal(res.status, 200); + if (res.body.length > 0) { + const unpaidRows = res.body.filter(r => r.payment_status === 'unpaid'); + assert.ok(unpaidRows.length > 0, 'Checkins without payments should be unpaid'); + } + }); + + test('CSV export for farm with no checkins returns header only', async () => { + const farmRes = await post('/farm', { name: 'Empty Export Farm', location: 'Nowhere', altitude_m: 500, owner_name: 'Nobody' }); + assert.equal(farmRes.status, 201); + const emptyFarmId = farmRes.body.id; + + const res = await get(`/farm/${emptyFarmId}/export`); + assert.equal(res.status, 200); + const lines = res.body.trim().split('\r\n'); + assert.equal(lines.length, 1, 'Should only have header row for empty farm'); + assert.equal(lines[0], 'worker_name,shift_date,checked_in_at,amount_sats,payment_status'); + }); +}); +