first commit
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-28 08:55:05 +07:00
commit 32a36cceff
444 files changed
+67186

No files matched your search

+239
View File
@@ -0,0 +1,239 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Cycle from '../Cycle.js';
import { query } from '../../database/db.js';
describe('Cycle Model', () => {
// Clean up test data before and after each test
beforeEach(async () => {
await query('DELETE FROM cycles WHERE id LIKE $1', ['TEST-%']);
});
afterEach(async () => {
await query('DELETE FROM cycles WHERE id LIKE $1', ['TEST-%']);
});
describe('create', () => {
it('should create a new cycle with all fields', async () => {
const cycleData = {
id: 'TEST-CYCLE-001',
totalDays: 35,
currentDay: 1,
startDate: '2026-04-01',
chickInWeight: 40,
docInCount: 10000,
status: 'Active',
};
const cycle = await Cycle.create(cycleData);
expect(cycle).toBeDefined();
expect(cycle.id).toBe('TEST-CYCLE-001');
expect(cycle.totalDays).toBe(35);
expect(cycle.currentDay).toBeGreaterThanOrEqual(1);
expect(cycle.docInCount).toBe(10000);
expect(cycle.status).toBe('Active');
});
it('should create a cycle with optional fields as null', async () => {
const cycleData = {
id: 'TEST-CYCLE-002',
totalDays: 35,
currentDay: 0,
startDate: '2026-05-01',
status: 'Upcoming',
};
const cycle = await Cycle.create(cycleData);
expect(cycle).toBeDefined();
expect(cycle.id).toBe('TEST-CYCLE-002');
expect(cycle.chickInWeight).toBeNull();
expect(cycle.docInCount).toBeNull();
expect(cycle.endDate).toBeNull();
});
});
describe('getById', () => {
it('should retrieve a cycle by ID', async () => {
// Create test cycle
await Cycle.create({
id: 'TEST-CYCLE-003',
totalDays: 35,
currentDay: 5,
startDate: '2026-04-01',
status: 'Active',
});
const cycle = await Cycle.getById('TEST-CYCLE-003');
expect(cycle).toBeDefined();
expect(cycle.id).toBe('TEST-CYCLE-003');
expect(cycle.totalDays).toBe(35);
});
it('should return null for non-existent cycle', async () => {
const cycle = await Cycle.getById('DOES-NOT-EXIST');
expect(cycle).toBeNull();
});
});
describe('getAll', () => {
it('should retrieve all cycles ordered by start date', async () => {
// Create multiple test cycles
await Cycle.create({
id: 'TEST-CYCLE-004',
totalDays: 35,
currentDay: 1,
startDate: '2026-04-01',
status: 'Active',
});
await Cycle.create({
id: 'TEST-CYCLE-005',
totalDays: 35,
currentDay: 0,
startDate: '2026-05-01',
status: 'Upcoming',
});
const cycles = await Cycle.getAll();
const testCycles = cycles.filter((c) => c.id.startsWith('TEST-'));
expect(testCycles.length).toBeGreaterThanOrEqual(2);
// Verify ordering (most recent start date first)
const testCycle005 = testCycles.find((c) => c.id === 'TEST-CYCLE-005');
const testCycle004 = testCycles.find((c) => c.id === 'TEST-CYCLE-004');
expect(testCycles.indexOf(testCycle005)).toBeLessThan(testCycles.indexOf(testCycle004));
});
});
describe('update', () => {
it('should update an existing cycle', async () => {
// Create initial cycle
await Cycle.create({
id: 'TEST-CYCLE-006',
totalDays: 35,
currentDay: 5,
startDate: '2026-04-01',
status: 'Active',
docInCount: 10000,
});
// Update the cycle
const updated = await Cycle.update('TEST-CYCLE-006', {
totalDays: 40,
currentDay: 10,
startDate: '2026-04-01',
status: 'Active',
docInCount: 9500,
});
expect(updated).toBeDefined();
expect(updated.totalDays).toBe(40);
expect(updated.docInCount).toBe(9500);
});
it('should return null when updating non-existent cycle', async () => {
const result = await Cycle.update('DOES-NOT-EXIST', {
totalDays: 35,
currentDay: 1,
startDate: '2026-04-01',
status: 'Active',
});
expect(result).toBeNull();
});
});
describe('delete', () => {
it('should delete an existing cycle', async () => {
// Create cycle to delete
await Cycle.create({
id: 'TEST-CYCLE-007',
totalDays: 35,
currentDay: 1,
startDate: '2026-04-01',
status: 'Active',
});
const deleted = await Cycle.delete('TEST-CYCLE-007');
expect(deleted).toBe(true);
// Verify it's gone
const cycle = await Cycle.getById('TEST-CYCLE-007');
expect(cycle).toBeNull();
});
it('should return false when deleting non-existent cycle', async () => {
const deleted = await Cycle.delete('DOES-NOT-EXIST');
expect(deleted).toBe(false);
});
});
describe('getActive', () => {
it('should retrieve the active cycle', async () => {
// Create an active cycle
await Cycle.create({
id: 'TEST-CYCLE-008',
totalDays: 35,
currentDay: 10,
startDate: '2026-04-01',
status: 'Active',
});
const activeCycle = await Cycle.getActive();
// Should return either our test cycle or another active cycle
expect(activeCycle).toBeDefined();
expect(activeCycle.status).toBe('Active');
});
});
describe('formatCycle', () => {
it('should calculate current day dynamically for active cycles', () => {
const today = new Date();
const startDate = new Date(today);
startDate.setDate(startDate.getDate() - 10); // 10 days ago
const rawCycle = {
id: 'TEST-FORMAT',
total_days: 35,
current_day: 1,
start_date: startDate.toISOString().split('T')[0],
end_date: null,
chick_in_weight: 40,
doc_in_count: 10000,
status: 'Active',
created_at: new Date(),
updated_at: new Date(),
};
const formatted = Cycle.formatCycle(rawCycle);
expect(formatted.currentDay).toBe(10);
expect(formatted.id).toBe('TEST-FORMAT');
expect(formatted.status).toBe('Active');
});
it('should not calculate current day for completed cycles', () => {
const rawCycle = {
id: 'TEST-FORMAT-2',
total_days: 35,
current_day: 35,
start_date: '2026-03-01',
end_date: '2026-04-05',
chick_in_weight: 40,
doc_in_count: 10000,
status: 'Completed',
created_at: new Date(),
updated_at: new Date(),
};
const formatted = Cycle.formatCycle(rawCycle);
expect(formatted.currentDay).toBe(35);
expect(formatted.status).toBe('Completed');
});
});
});
+233
View File
@@ -0,0 +1,233 @@
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');
});
});
});