diff --git a/backend/test/api.test.js b/backend/test/api.test.js index 15af501..554f527 100644 --- a/backend/test/api.test.js +++ b/backend/test/api.test.js @@ -1421,6 +1421,83 @@ 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'); + }); +}); + // ------------------------------------------------------------------ Feature: Lot route edge cases describe('Lot edge cases', () => { test('POST /lot returns 400 when required fields are missing', async () => { @@ -1480,6 +1557,7 @@ describe('Lot edge cases', () => { const res = await post('/lot/nonexistent-lot-id/transfer', { to_entity: 'Mill Corp', entity_type: 'wet_mill' }); assert.equal(res.status, 404); assert.equal(res.body.error, 'Lot not found'); + }); });