// node_api/tests/depreciation.test.js const { DepreciationCalculator } = require('../jobs/depreciationCalculator'); const { DepreciationJob } = require('../jobs/depreciationJob'); describe('DepreciationCalculator', () => { let calculator; beforeEach(() => { calculator = new DepreciationCalculator(); }); describe('Straight Line Depreciation', () => { test('should calculate monthly straight-line depreciation correctly', () => { const asset = { id: 'test-asset-1', acquisition_cost: 10000, salvage_value: 1000, expected_useful_life_months: 60, depreciation_method: 'straight_line' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.method).toBe('straight_line'); expect(result.monthlyDepreciation).toBe(150); // (10000 - 1000) / 60 expect(result.calculation.acquisitionCost).toBe(10000); expect(result.calculation.salvageValue).toBe(1000); expect(result.calculation.usefulLifeMonths).toBe(60); }); test('should use depreciation rate when provided', () => { const asset = { id: 'test-asset-2', acquisition_cost: 10000, expected_useful_life_months: 60, depreciation_method: 'straight_line', depreciation_percentage_rate: 20 }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.monthlyDepreciation).toBeCloseTo(166.67, 2); // (10000 * 20%) / 12 }); test('should handle zero salvage value', () => { const asset = { id: 'test-asset-3', acquisition_cost: 12000, expected_useful_life_months: 48, depreciation_method: 'straight_line' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.monthlyDepreciation).toBe(250); // 12000 / 48 }); }); describe('Declining Balance Depreciation', () => { test('should calculate declining balance depreciation correctly', () => { const asset = { id: 'test-asset-4', acquisition_cost: 10000, depreciation_percentage_rate: 20, total_accumulated_depreciation: 0, depreciation_method: 'declining_balance' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.method).toBe('declining_balance'); expect(result.monthlyDepreciation).toBeCloseTo(166.67, 2); // 10000 * (20% / 12) expect(result.calculation.currentBookValue).toBe(10000); }); test('should handle existing accumulated depreciation', () => { const asset = { id: 'test-asset-5', acquisition_cost: 10000, depreciation_percentage_rate: 20, total_accumulated_depreciation: 2000, depreciation_method: 'declining_balance' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.monthlyDepreciation).toBeCloseTo(133.33, 2); // 8000 * (20% / 12) expect(result.calculation.currentBookValue).toBe(8000); }); }); describe('Sum of Years Depreciation', () => { test('should calculate sum of years depreciation correctly', () => { const asset = { id: 'test-asset-6', acquisition_cost: 10000, salvage_value: 1000, expected_useful_life_months: 60, // 5 years acquisition_date: new Date('2020-01-01'), depreciation_method: 'sum_of_years' }; const calculationDate = new Date('2020-02-01'); const result = calculator.calculateMonthlyDepreciation(asset, calculationDate); expect(result.method).toBe('sum_of_years'); expect(result.calculation.usefulLifeYears).toBe(5); expect(result.calculation.sumOfYears).toBe(15); // 1+2+3+4+5 expect(result.calculation.remainingYears).toBe(5); // First year depreciation: (10000-1000) * (5/15) = 3000 annual, 250 monthly expect(result.monthlyDepreciation).toBe(250); }); }); describe('Units of Production Depreciation', () => { test('should calculate units of production depreciation correctly', () => { const asset = { id: 'test-asset-7', acquisition_cost: 10000, salvage_value: 1000, expected_total_units: 90000, current_month_units: 1000, depreciation_method: 'units_of_production' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.method).toBe('units_of_production'); expect(result.calculation.depreciationPerUnit).toBe(0.1); // (10000-1000)/90000 expect(result.monthlyDepreciation).toBe(100); // 0.1 * 1000 }); test('should handle zero monthly units', () => { const asset = { id: 'test-asset-8', acquisition_cost: 10000, salvage_value: 1000, expected_total_units: 90000, current_month_units: 0, depreciation_method: 'units_of_production' }; const result = calculator.calculateMonthlyDepreciation(asset); expect(result.monthlyDepreciation).toBe(0); }); }); describe('Asset Validation', () => { test('should validate straight-line depreciation parameters', () => { const asset = { id: 'test-asset-9', acquisition_cost: 10000, depreciation_method: 'straight_line' // Missing expected_useful_life_months }; const errors = calculator.validateAsset(asset); expect(errors).toContain('Expected useful life is required for straight-line depreciation'); }); test('should validate declining balance parameters', () => { const asset = { id: 'test-asset-10', acquisition_cost: 10000, depreciation_method: 'declining_balance' // Missing depreciation_percentage_rate }; const errors = calculator.validateAsset(asset); expect(errors).toContain('Depreciation rate is required for declining balance method'); }); test('should validate units of production parameters', () => { const asset = { id: 'test-asset-11', acquisition_cost: 10000, depreciation_method: 'units_of_production' // Missing expected_total_units }; const errors = calculator.validateAsset(asset); expect(errors).toContain('Expected total units is required for units of production method'); }); }); describe('Asset Eligibility', () => { test('should not depreciate fully depreciated assets', () => { const asset = { id: 'test-asset-12', acquisition_cost: 10000, total_accumulated_depreciation: 10000, salvage_value: 0, status: 'active', acquisition_date: new Date('2020-01-01'), expected_useful_life_months: 60 }; const shouldDepreciate = calculator.shouldDepreciate(asset); expect(shouldDepreciate).toBe(false); }); test('should not depreciate inactive assets', () => { const asset = { id: 'test-asset-13', acquisition_cost: 10000, total_accumulated_depreciation: 0, status: 'retired', acquisition_date: new Date('2020-01-01'), expected_useful_life_months: 60 }; const shouldDepreciate = calculator.shouldDepreciate(asset); expect(shouldDepreciate).toBe(false); }); test('should not depreciate assets with future acquisition dates', () => { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const asset = { id: 'test-asset-14', acquisition_cost: 10000, total_accumulated_depreciation: 0, status: 'active', acquisition_date: tomorrow, expected_useful_life_months: 60 }; const shouldDepreciate = calculator.shouldDepreciate(asset); expect(shouldDepreciate).toBe(false); }); test('should not depreciate assets beyond useful life', () => { const asset = { id: 'test-asset-15', acquisition_cost: 10000, total_accumulated_depreciation: 0, status: 'active', acquisition_date: new Date('2020-01-01'), expected_useful_life_months: 12 }; const calculationDate = new Date('2022-01-01'); // 24 months later const shouldDepreciate = calculator.shouldDepreciate(asset, calculationDate); expect(shouldDepreciate).toBe(false); }); test('should depreciate eligible assets', () => { const recentDate = new Date(); recentDate.setMonth(recentDate.getMonth() - 10); // 10 months ago const asset = { id: 'test-asset-16', acquisition_cost: 10000, total_accumulated_depreciation: 1000, status: 'active', acquisition_date: recentDate, expected_useful_life_months: 60 }; const shouldDepreciate = calculator.shouldDepreciate(asset); expect(shouldDepreciate).toBe(true); }); }); describe('Helper Methods', () => { test('should calculate months difference correctly', () => { const startDate = new Date('2020-01-01'); const endDate = new Date('2020-06-01'); const monthsDiff = calculator.getMonthsDifference(startDate, endDate); expect(monthsDiff).toBe(5); }); test('should handle year boundaries in months difference', () => { const startDate = new Date('2020-10-01'); const endDate = new Date('2021-03-01'); const monthsDiff = calculator.getMonthsDifference(startDate, endDate); expect(monthsDiff).toBe(5); }); }); describe('Error Handling', () => { test('should throw error for unsupported depreciation method', () => { const asset = { id: 'test-asset-17', acquisition_cost: 10000, depreciation_method: 'unsupported_method' }; expect(() => { calculator.calculateMonthlyDepreciation(asset); }).toThrow('Unsupported depreciation method: unsupported_method'); }); test('should throw error for missing acquisition cost', () => { const asset = { id: 'test-asset-18', depreciation_method: 'straight_line', expected_useful_life_months: 60 }; expect(() => { calculator.calculateMonthlyDepreciation(asset); }).toThrow('Missing required parameters for straight-line depreciation'); }); }); }); describe('DepreciationJob Integration', () => { let job; let mockAsset; beforeEach(() => { job = new DepreciationJob(); mockAsset = { id: 'test-asset-job-1', name: 'Test Asset', acquisition_cost: 10000, salvage_value: 1000, expected_useful_life_months: 60, depreciation_method: 'straight_line', total_accumulated_depreciation: 0, net_book_value: 10000, status: 'active', acquisition_date: new Date('2020-01-01') }; }); describe('Asset Processing', () => { test('should process asset depreciation correctly', async () => { // Mock database methods job.database.depreciationRecordExists = jest.fn().mockResolvedValue(false); job.database.createDepreciationRecord = jest.fn().mockResolvedValue('record-id-123'); const result = await job.processAssetDepreciation(mockAsset, new Date('2020-02-01')); expect(result.skipped).toBe(false); expect(result.method).toBe('straight_line'); expect(result.monthlyDepreciation).toBe(150); // (10000-1000)/60 expect(result.accumulatedDepreciation).toBe(150); expect(result.netBookValue).toBe(9850); expect(job.database.createDepreciationRecord).toHaveBeenCalledWith( expect.objectContaining({ asset_id: mockAsset.id, monthly_depreciation: 150, accumulated_depreciation_after: 150, net_book_value_after: 9850 }) ); }); test('should skip asset if record already exists', async () => { job.database.depreciationRecordExists = jest.fn().mockResolvedValue(true); const result = await job.processAssetDepreciation(mockAsset, new Date('2020-02-01')); expect(result.skipped).toBe(true); expect(result.reason).toBe('Depreciation record already exists for this month'); }); test('should skip asset if validation fails', async () => { const invalidAsset = { ...mockAsset, acquisition_cost: null }; job.database.depreciationRecordExists = jest.fn().mockResolvedValue(false); const result = await job.processAssetDepreciation(invalidAsset, new Date('2020-02-01')); expect(result.skipped).toBe(true); expect(result.reason).toContain('Validation failed'); }); }); describe('Salvage Value Handling', () => { test('should not depreciate below salvage value', async () => { const nearlyFullyDepreciatedAsset = { ...mockAsset, total_accumulated_depreciation: 8900, net_book_value: 1100 }; job.database.depreciationRecordExists = jest.fn().mockResolvedValue(false); job.database.createDepreciationRecord = jest.fn().mockResolvedValue('record-id-124'); const result = await job.processAssetDepreciation(nearlyFullyDepreciatedAsset, new Date('2020-02-01')); expect(result.skipped).toBe(false); expect(result.monthlyDepreciation).toBe(100); // Only depreciate down to salvage value expect(result.netBookValue).toBe(1000); // Salvage value }); }); }); // Mock implementations for testing jest.mock('../jobs/depreciationDatabase', () => ({ DepreciationDatabase: jest.fn().mockImplementation(() => ({ getAssetsForDepreciation: jest.fn(), depreciationRecordExists: jest.fn(), createDepreciationRecord: jest.fn(), getDepreciationSummary: jest.fn(), getMonthlyDepreciationReport: jest.fn(), getDepreciationIssues: jest.fn() })) })); jest.mock('../utils/logger', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), job: jest.fn(), depreciation: jest.fn() } }));