426 lines
14 KiB
JavaScript
426 lines
14 KiB
JavaScript
// 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()
|
|
}
|
|
})); |