import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import Mortality from '../Mortality.js'; import Cycle from '../Cycle.js'; import { query } from '../../database/db.js'; describe('Mortality Model', () => { const TEST_CYCLE_ID = 'TEST-MORTALITY-CYCLE'; beforeEach(async () => { // Clean up test data await query('DELETE FROM mortality_records WHERE cycle_id = $1', [TEST_CYCLE_ID]); await query('DELETE FROM cycles WHERE id = $1', [TEST_CYCLE_ID]); // Create test cycle await Cycle.create({ id: TEST_CYCLE_ID, totalDays: 35, currentDay: 1, startDate: '2026-04-01', status: 'Active', docInCount: 10000, }); }); afterEach(async () => { await query('DELETE FROM mortality_records WHERE cycle_id = $1', [TEST_CYCLE_ID]); await query('DELETE FROM cycles WHERE id = $1', [TEST_CYCLE_ID]); }); describe('upsert', () => { it('should insert a new mortality record', async () => { const record = await Mortality.upsert(TEST_CYCLE_ID, 1, 50, true, 9950); expect(record).toBeDefined(); expect(record.cycleId).toBe(TEST_CYCLE_ID); expect(record.day).toBe(1); expect(record.mortalityCount).toBe(50); expect(record.chickenCount).toBe(9950); expect(record.isEdited).toBe(true); }); it('should update an existing mortality record', async () => { // Insert initial record await Mortality.upsert(TEST_CYCLE_ID, 1, 50, true, 9950); // Update the same record const updated = await Mortality.upsert(TEST_CYCLE_ID, 1, 55, true, 9945); expect(updated).toBeDefined(); expect(updated.mortalityCount).toBe(55); expect(updated.chickenCount).toBe(9945); }); it('should handle afkir and panen fields', async () => { const record = await Mortality.upsert( TEST_CYCLE_ID, 10, 20, true, 9500, null, 100, 'Culled due to disease', 30 ); expect(record).toBeDefined(); expect(record.afkir).toBe(30); expect(record.panen).toBe(100); expect(record.beratPanen).toBe(0); expect(record.keterangan).toBe('Culled due to disease'); }); it('should persist harvest weight', async () => { const record = await Mortality.upsert( TEST_CYCLE_ID, 12, 15, true, 9985, null, 80, 'Harvest day', 5, 123.45 ); expect(record).toBeDefined(); expect(record.beratPanen).toBe(123.45); }); }); describe('getByDay', () => { it('should retrieve a mortality record by cycle and day', async () => { await Mortality.upsert(TEST_CYCLE_ID, 5, 45, true, 9500); const record = await Mortality.getByDay(TEST_CYCLE_ID, 5); expect(record).toBeDefined(); expect(record.day).toBe(5); expect(record.mortalityCount).toBe(45); }); it('should return null for non-existent record', async () => { const record = await Mortality.getByDay(TEST_CYCLE_ID, 999); expect(record).toBeNull(); }); it('should handle kandang-specific records', async () => { // Create records for different kandangs await Mortality.upsert(TEST_CYCLE_ID, 1, 20, true, 4980, 1); await Mortality.upsert(TEST_CYCLE_ID, 1, 30, true, 4970, 2); const recordKandang1 = await Mortality.getByDay(TEST_CYCLE_ID, 1, 1); const recordKandang2 = await Mortality.getByDay(TEST_CYCLE_ID, 1, 2); expect(recordKandang1.mortalityCount).toBe(20); expect(recordKandang2.mortalityCount).toBe(30); expect(recordKandang1.kandangId).toBe(1); expect(recordKandang2.kandangId).toBe(2); }); }); describe('getByCycle', () => { it('should retrieve all mortality records for a cycle', async () => { // Create multiple records await Mortality.upsert(TEST_CYCLE_ID, 1, 50, true, 9950); await Mortality.upsert(TEST_CYCLE_ID, 2, 45, true, 9905); await Mortality.upsert(TEST_CYCLE_ID, 3, 40, true, 9865); const records = await Mortality.getByCycle(TEST_CYCLE_ID); expect(records).toHaveLength(3); expect(records[0].day).toBe(1); expect(records[1].day).toBe(2); expect(records[2].day).toBe(3); }); it('should filter by kandang when provided', async () => { await Mortality.upsert(TEST_CYCLE_ID, 1, 20, true, 4980, 1); await Mortality.upsert(TEST_CYCLE_ID, 2, 25, true, 4955, 1); await Mortality.upsert(TEST_CYCLE_ID, 1, 30, true, 4970, 2); const recordsKandang1 = await Mortality.getByCycle(TEST_CYCLE_ID, 1); const recordsKandang2 = await Mortality.getByCycle(TEST_CYCLE_ID, 2); expect(recordsKandang1).toHaveLength(2); expect(recordsKandang2).toHaveLength(1); expect(recordsKandang1.every((r) => r.kandangId === 1)).toBe(true); expect(recordsKandang2.every((r) => r.kandangId === 2)).toBe(true); }); }); describe('getTotalsByCycle', () => { it('should aggregate mati, afkir, and panen across kandangs', async () => { await Mortality.upsert(TEST_CYCLE_ID, 1, 20, true, 4980, 1, 5, 'Kandang 1', 3, 11.25); await Mortality.upsert(TEST_CYCLE_ID, 1, 30, true, 4970, 2, 7, 'Kandang 2', 4, 8.75); const totals = await Mortality.getTotalsByCycle(TEST_CYCLE_ID); expect(totals).toHaveLength(1); expect(totals[0].day).toBe(1); expect(totals[0].mortalityCount).toBe(50); expect(totals[0].afkir).toBe(7); expect(totals[0].panen).toBe(12); expect(totals[0].beratPanen).toBe(20); }); }); describe('delete', () => { it('should delete a mortality record', async () => { await Mortality.upsert(TEST_CYCLE_ID, 10, 50, true, 9500); const deleted = await Mortality.delete(TEST_CYCLE_ID, 10); expect(deleted).toBe(true); const record = await Mortality.getByDay(TEST_CYCLE_ID, 10); expect(record).toBeNull(); }); it('should return false when deleting non-existent record', async () => { const deleted = await Mortality.delete(TEST_CYCLE_ID, 999); expect(deleted).toBe(false); }); it('should delete kandang-specific records', async () => { await Mortality.upsert(TEST_CYCLE_ID, 1, 20, true, 4980, 1); await Mortality.upsert(TEST_CYCLE_ID, 1, 30, true, 4970, 2); const deleted = await Mortality.delete(TEST_CYCLE_ID, 1, 1); expect(deleted).toBe(true); // Verify only kandang 1 was deleted const record1 = await Mortality.getByDay(TEST_CYCLE_ID, 1, 1); const record2 = await Mortality.getByDay(TEST_CYCLE_ID, 1, 2); expect(record1).toBeNull(); expect(record2).toBeDefined(); }); }); describe('formatRecord', () => { it('should format database record to camelCase', () => { const dbRecord = { id: 1, cycle_id: TEST_CYCLE_ID, day: 5, mortality_count: 50, chicken_count: 9500, population: 9500, kandang_id: 1, is_edited: true, afkir: 10, panen: 100, berat_panen: 12.5, keterangan: 'Test note', created_at: new Date(), updated_at: new Date(), }; const formatted = Mortality.formatRecord(dbRecord); expect(formatted.cycleId).toBe(TEST_CYCLE_ID); expect(formatted.mortalityCount).toBe(50); expect(formatted.chickenCount).toBe(9500); expect(formatted.kandangId).toBe(1); expect(formatted.isEdited).toBe(true); expect(formatted.afkir).toBe(10); expect(formatted.panen).toBe(100); expect(formatted.beratPanen).toBe(12.5); expect(formatted.keterangan).toBe('Test note'); }); }); });