Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 160 additions & 30 deletions backend/test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1495,66 +1495,196 @@ describe('Export', () => {
});
});

// ------------------------------------------------------------------ Feature: Lot route edge cases
describe('Lot edge cases', () => {
test('POST /lot returns 400 when required fields are missing', async () => {
const res = await post('/lot', { shift_id: 'abc' });
assert.equal(res.status, 400);
assert.ok(res.body.error.includes('required'));
// ------------------------------------------------------------------ Shift validation edge cases

describe('Shift — validation edge cases', () => {
test('POST /shift returns 404 when farm does not exist', async () => {
const res = await post('/shift', { farm_id: 'nonexistent-farm', foreman_id: 'some-id' });
assert.equal(res.status, 404);
assert.match(res.body.error, /Farm not found/i);
});

test('POST /shift returns 404 when foreman does not exist', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const res = await post('/shift', { farm_id: farmId, foreman_id: 'nonexistent-foreman' });
assert.equal(res.status, 404);
assert.match(res.body.error, /Foreman not found/i);
});

test('POST /shift/:id/checkin returns 404 for nonexistent shift', async () => {
const res = await post('/shift/nonexistent-shift/checkin', { worker_id: 'w1' });
assert.equal(res.status, 404);
assert.match(res.body.error, /Shift not found/i);
});

test('POST /lot returns 400 when weight_kg is missing', async () => {
const res = await post('/lot', { shift_id: 'abc', grade: 'A' });
test('POST /shift/:id/checkin returns 400 when worker_id missing', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');
const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
assert.equal(shift.status, 201);

const res = await post(`/shift/${shift.body.id}/checkin`, {});
assert.equal(res.status, 400);
assert.ok(res.body.error.includes('required'));
assert.match(res.body.error, /worker_id/i);
});

test('POST /lot returns 404 for nonexistent shift', async () => {
const res = await post('/lot', { shift_id: 'nonexistent-shift', weight_kg: 50, grade: 'A' });
test('POST /shift/:id/close returns 404 for nonexistent shift', async () => {
const res = await post('/shift/nonexistent-shift/close', {});
assert.equal(res.status, 404);
assert.equal(res.body.error, 'Shift not found');
assert.match(res.body.error, /Shift not found/i);
});

test('POST /lot returns 409 when shift is still open', async () => {
// Create a fresh shift that is NOT closed
const farmRes = await post('/farm', { name: 'Lot Edge Farm', location: 'Edge', altitude_m: 800, owner_name: 'Edge Owner' });
const openShiftRes = await post('/shift', { farm_id: farmRes.body.id, foreman_id: foremanId, date: '2026-04-05' });
assert.equal(openShiftRes.status, 201);
test('POST /shift/:id/close returns 400 when no workers checked in', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');

const res = await post('/lot', { shift_id: openShiftRes.body.id, weight_kg: 50, grade: 'B' });
const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
assert.equal(shift.status, 201);

const res = await post(`/shift/${shift.body.id}/close`, {});
assert.equal(res.status, 400);
assert.match(res.body.error, /No workers checked in/i);
});

test('POST /shift/:id/checkin returns 409 on closed shift', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');
const worker = workers.body.find(w => w.role === 'worker');

const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
await post(`/shift/${shift.body.id}/checkin`, { worker_id: worker.id });
await post(`/shift/${shift.body.id}/close`, {});

const res = await post(`/shift/${shift.body.id}/checkin`, { worker_id: 'new-worker-id' });
assert.equal(res.status, 409);
assert.equal(res.body.error, 'Shift must be closed before creating a lot');
assert.match(res.body.error, /closed/i);
});

test('GET /lot/:id returns 404 for nonexistent lot', async () => {
const res = await get('/lot/nonexistent-lot-id');
test('GET /shift/:id returns 404 for nonexistent shift', async () => {
const res = await get('/shift/nonexistent-shift');
assert.equal(res.status, 404);
assert.match(res.body.error, /Shift not found/i);
});
});

// ------------------------------------------------------------------ Lot validation edge cases

describe('Lot — validation edge cases', () => {
test('POST /lot returns 404 when shift does not exist', async () => {
const res = await post('/lot', { shift_id: 'nonexistent', weight_kg: 100, grade: 'A' });
assert.equal(res.status, 404);
assert.equal(res.body.error, 'Lot not found');
assert.match(res.body.error, /Shift not found/i);
});

test('POST /lot returns 409 when shift is still open', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');

const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
assert.equal(shift.status, 201);

const res = await post('/lot', { shift_id: shift.body.id, weight_kg: 50, grade: 'B' });
assert.equal(res.status, 409);
assert.match(res.body.error, /closed/i);
});

test('POST /lot/:id/transfer returns 400 when to_entity is missing', async () => {
test('POST /lot/:id/transfer returns 400 when to_entity missing', async () => {
// Use existing lot from earlier tests
const farms = await get('/farm');
const farmId = farms.body[0].id;
const lots = await get(`/lot?farm_id=${farmId}`);
if (!lots.body.length) return;
const lotId = lots.body[0].id;

const res = await post(`/lot/${lotId}/transfer`, { entity_type: 'wet_mill' });
assert.equal(res.status, 400);
assert.ok(res.body.error.includes('required'));
assert.match(res.body.error, /to_entity/i);
});

test('POST /lot/:id/transfer returns 400 when entity_type is missing', async () => {
test('POST /lot/:id/transfer returns 400 when entity_type missing', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const lots = await get(`/lot?farm_id=${farmId}`);
if (!lots.body.length) return;
const lotId = lots.body[0].id;

const res = await post(`/lot/${lotId}/transfer`, { to_entity: 'Mill Corp' });
assert.equal(res.status, 400);
assert.ok(res.body.error.includes('required'));
assert.match(res.body.error, /to_entity|entity_type/i);
});

test('POST /lot/:id/transfer returns 400 for invalid entity_type', async () => {
const res = await post(`/lot/${lotId}/transfer`, { to_entity: 'Mill Corp', entity_type: 'spaceship' });
const farms = await get('/farm');
const farmId = farms.body[0].id;
const lots = await get(`/lot?farm_id=${farmId}`);
if (!lots.body.length) return;
const lotId = lots.body[0].id;

const res = await post(`/lot/${lotId}/transfer`, { to_entity: 'X', entity_type: 'spaceship' });
assert.equal(res.status, 400);
assert.ok(res.body.error.includes('entity_type'));
assert.match(res.body.error, /entity_type must be one of/i);
});

test('POST /lot/:id/transfer returns 404 for nonexistent lot', async () => {
const res = await post('/lot/nonexistent-lot-id/transfer', { to_entity: 'Mill Corp', entity_type: 'wet_mill' });
const res = await post('/lot/nonexistent-lot/transfer', { to_entity: 'X', entity_type: 'wet_mill' });
assert.equal(res.status, 404);
assert.equal(res.body.error, 'Lot not found');
assert.match(res.body.error, /Lot not found/i);
});

test('GET /lot/:id returns 404 for nonexistent lot', async () => {
const res = await get('/lot/nonexistent-lot');
assert.equal(res.status, 404);
assert.match(res.body.error, /Lot not found/i);
});
});

// ------------------------------------------------------------------ Payroll validation edge cases

describe('Payroll — validation edge cases', () => {
test('POST /payroll returns 404 when shift does not exist', async () => {
const res = await post('/payroll', { shift_id: 'nonexistent-shift' });
assert.equal(res.status, 404);
assert.match(res.body.error, /Shift not found/i);
});

test('POST /payroll returns 409 when shift is not closed', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');

const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
assert.equal(shift.status, 201);

const res = await post('/payroll', { shift_id: shift.body.id });
assert.equal(res.status, 409);
assert.match(res.body.error, /closed/i);
});

test('POST /payroll returns 400 when no eligible workers', async () => {
const farms = await get('/farm');
const farmId = farms.body[0].id;
const workers = await get(`/worker?farm_id=${farmId}`);
const foreman = workers.body.find(w => w.role === 'foreman');
const worker = workers.body.find(w => w.role === 'worker');

// Create shift, check in a worker, close it
const shift = await post('/shift', { farm_id: farmId, foreman_id: foreman.id });
await post(`/shift/${shift.body.id}/checkin`, { worker_id: worker.id });
await post(`/shift/${shift.body.id}/close`, {});

// Pay with a worker_ids list that matches nobody checked in
const res = await post('/payroll', { shift_id: shift.body.id, worker_ids: ['nobody-here'] });
assert.equal(res.status, 400);
assert.match(res.body.error, /No eligible workers/i);
});
});