/** * @ai-summary Unit tests for BackupClassificationService * @ai-context Tests pure timestamp-based classification functions */ import { classifyBackup, calculateExpiration, isFirstBackupOfDay, isSunday, isFirstDayOfMonth, classifyAndCalculateExpiration, } from '../../domain/backup-classification.service'; import { TIERED_RETENTION } from '../../domain/backup.types'; describe('BackupClassificationService', () => { describe('classifyBackup', () => { it('should classify regular hourly backup (non-midnight)', () => { // Tuesday, January 7, 2026 at 14:30 UTC const timestamp = new Date('2026-01-07T14:30:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly']); }); it('should classify midnight backup as hourly + daily', () => { // Wednesday, January 8, 2026 at 00:00 UTC const timestamp = new Date('2026-01-08T00:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly', 'daily']); }); it('should classify Sunday midnight backup as hourly + daily + weekly', () => { // Sunday, January 4, 2026 at 00:00 UTC const timestamp = new Date('2026-01-04T00:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly', 'daily', 'weekly']); }); it('should classify 1st of month midnight backup as hourly + daily + monthly', () => { // Thursday, January 1, 2026 at 00:00 UTC (not Sunday) const timestamp = new Date('2026-01-01T00:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly', 'daily', 'monthly']); }); it('should classify Sunday 1st of month midnight as all categories', () => { // Sunday, February 1, 2026 at 00:00 UTC const timestamp = new Date('2026-02-01T00:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly', 'daily', 'weekly', 'monthly']); }); it('should not classify non-midnight on 1st as monthly', () => { // Thursday, January 1, 2026 at 10:00 UTC const timestamp = new Date('2026-01-01T10:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly']); }); it('should not classify non-midnight on Sunday as weekly', () => { // Sunday, January 4, 2026 at 15:00 UTC const timestamp = new Date('2026-01-04T15:00:00.000Z'); const categories = classifyBackup(timestamp); expect(categories).toEqual(['hourly']); }); }); describe('calculateExpiration', () => { const baseTimestamp = new Date('2026-01-05T00:00:00.000Z'); it('should calculate 8 hours for hourly-only backup', () => { const expiresAt = calculateExpiration(['hourly'], baseTimestamp); const expectedDate = new Date('2026-01-05T08:00:00.000Z'); expect(expiresAt).toEqual(expectedDate); }); it('should calculate 7 days for daily backup', () => { const expiresAt = calculateExpiration(['hourly', 'daily'], baseTimestamp); const expectedDate = new Date('2026-01-12T00:00:00.000Z'); expect(expiresAt).toEqual(expectedDate); }); it('should calculate 4 weeks for weekly backup', () => { const expiresAt = calculateExpiration(['hourly', 'daily', 'weekly'], baseTimestamp); const expectedDate = new Date('2026-02-02T00:00:00.000Z'); expect(expiresAt).toEqual(expectedDate); }); it('should calculate 12 months for monthly backup', () => { const expiresAt = calculateExpiration( ['hourly', 'daily', 'weekly', 'monthly'], baseTimestamp ); const expectedDate = new Date('2027-01-05T00:00:00.000Z'); expect(expiresAt).toEqual(expectedDate); }); it('should use longest retention when monthly is present (even without weekly)', () => { const expiresAt = calculateExpiration(['hourly', 'daily', 'monthly'], baseTimestamp); const expectedDate = new Date('2027-01-05T00:00:00.000Z'); expect(expiresAt).toEqual(expectedDate); }); }); describe('isFirstBackupOfDay', () => { it('should return true for midnight UTC', () => { const timestamp = new Date('2026-01-05T00:00:00.000Z'); expect(isFirstBackupOfDay(timestamp)).toBe(true); }); it('should return false for non-midnight', () => { const timestamp = new Date('2026-01-05T01:00:00.000Z'); expect(isFirstBackupOfDay(timestamp)).toBe(false); }); it('should return true for midnight with minutes/seconds', () => { // 00:30:45 is still hour 0 const timestamp = new Date('2026-01-05T00:30:45.000Z'); expect(isFirstBackupOfDay(timestamp)).toBe(true); }); }); describe('isSunday', () => { it('should return true for Sunday', () => { // January 4, 2026 is a Sunday const timestamp = new Date('2026-01-04T12:00:00.000Z'); expect(isSunday(timestamp)).toBe(true); }); it('should return false for non-Sunday', () => { // January 5, 2026 is a Monday const timestamp = new Date('2026-01-05T12:00:00.000Z'); expect(isSunday(timestamp)).toBe(false); }); }); describe('isFirstDayOfMonth', () => { it('should return true for 1st of month', () => { const timestamp = new Date('2026-01-01T12:00:00.000Z'); expect(isFirstDayOfMonth(timestamp)).toBe(true); }); it('should return false for non-1st', () => { const timestamp = new Date('2026-01-15T12:00:00.000Z'); expect(isFirstDayOfMonth(timestamp)).toBe(false); }); }); describe('classifyAndCalculateExpiration', () => { it('should return both categories and expiresAt', () => { // Sunday, February 1, 2026 at 00:00 UTC - all categories const timestamp = new Date('2026-02-01T00:00:00.000Z'); const result = classifyAndCalculateExpiration(timestamp); expect(result.categories).toEqual(['hourly', 'daily', 'weekly', 'monthly']); expect(result.expiresAt).toEqual(new Date('2027-02-01T00:00:00.000Z')); }); it('should work for hourly-only backup', () => { const timestamp = new Date('2026-01-07T14:30:00.000Z'); const result = classifyAndCalculateExpiration(timestamp); expect(result.categories).toEqual(['hourly']); expect(result.expiresAt).toEqual(new Date('2026-01-07T22:30:00.000Z')); }); }); describe('TIERED_RETENTION constants', () => { it('should have correct retention values', () => { expect(TIERED_RETENTION.hourly).toBe(8); expect(TIERED_RETENTION.daily).toBe(7); expect(TIERED_RETENTION.weekly).toBe(4); expect(TIERED_RETENTION.monthly).toBe(12); }); }); });