import { OnlineDetectionService } from '../../../../src/domain/services/online-detection.service'; import { TimeWindow } from '../../../../src/domain/value-objects/time-window.vo'; describe('OnlineDetectionService', () => { let service: OnlineDetectionService; beforeEach(() => { service = new OnlineDetectionService(); }); describe('isOnline', () => { it('should return true for recent heartbeat', () => { const now = new Date(); const recentTimestamp = Math.floor(now.getTime() / 1000) - 60; // 60 seconds ago expect(service.isOnline(recentTimestamp, now)).toBe(true); }); it('should return true for heartbeat within window', () => { const now = new Date(); const withinWindow = Math.floor(now.getTime() / 1000) - 179; // 179 seconds ago (within 180 window) expect(service.isOnline(withinWindow, now)).toBe(true); }); it('should return false for heartbeat at window boundary', () => { const now = new Date(); const atBoundary = Math.floor(now.getTime() / 1000) - 180; // Exactly 180 seconds ago expect(service.isOnline(atBoundary, now)).toBe(false); }); it('should return false for old heartbeat', () => { const now = new Date(); const oldTimestamp = Math.floor(now.getTime() / 1000) - 300; // 5 minutes ago expect(service.isOnline(oldTimestamp, now)).toBe(false); }); it('should return false for very old heartbeat', () => { const now = new Date(); const veryOldTimestamp = Math.floor(now.getTime() / 1000) - 3600; // 1 hour ago expect(service.isOnline(veryOldTimestamp, now)).toBe(false); }); it('should return true for heartbeat just now', () => { const now = new Date(); const justNow = Math.floor(now.getTime() / 1000); expect(service.isOnline(justNow, now)).toBe(true); }); it('should use current time when no time provided', () => { const justNow = Math.floor(Date.now() / 1000); expect(service.isOnline(justNow)).toBe(true); }); }); describe('getOnlineThreshold', () => { it('should return correct threshold timestamp', () => { const now = new Date('2025-01-01T12:00:00.000Z'); const threshold = service.getOnlineThreshold(now); const expectedTimestamp = Math.floor(now.getTime() / 1000) - TimeWindow.DEFAULT_ONLINE_WINDOW_SECONDS; expect(threshold).toBe(expectedTimestamp); }); it('should use current time when no time provided', () => { const beforeCall = Math.floor(Date.now() / 1000) - TimeWindow.DEFAULT_ONLINE_WINDOW_SECONDS; const threshold = service.getOnlineThreshold(); const afterCall = Math.floor(Date.now() / 1000) - TimeWindow.DEFAULT_ONLINE_WINDOW_SECONDS; expect(threshold).toBeGreaterThanOrEqual(beforeCall); expect(threshold).toBeLessThanOrEqual(afterCall); }); }); describe('getWindowSeconds', () => { it('should return default window seconds', () => { expect(service.getWindowSeconds()).toBe(TimeWindow.DEFAULT_ONLINE_WINDOW_SECONDS); }); it('should return 180 seconds', () => { expect(service.getWindowSeconds()).toBe(180); }); }); describe('integration scenarios', () => { it('should correctly determine online status for multiple users', () => { const now = new Date(); const nowTimestamp = Math.floor(now.getTime() / 1000); const users = [ { id: 1, lastHeartbeat: nowTimestamp - 30, expectedOnline: true }, // 30s ago { id: 2, lastHeartbeat: nowTimestamp - 90, expectedOnline: true }, // 90s ago { id: 3, lastHeartbeat: nowTimestamp - 150, expectedOnline: true }, // 150s ago { id: 4, lastHeartbeat: nowTimestamp - 180, expectedOnline: false }, // 180s ago (boundary) { id: 5, lastHeartbeat: nowTimestamp - 200, expectedOnline: false }, // 200s ago ]; users.forEach((user) => { expect(service.isOnline(user.lastHeartbeat, now)).toBe(user.expectedOnline); }); }); it('should work correctly with getOnlineThreshold', () => { const now = new Date(); const threshold = service.getOnlineThreshold(now); // Users with timestamp > threshold should be online expect(service.isOnline(threshold + 1, now)).toBe(true); // Users with timestamp <= threshold should be offline expect(service.isOnline(threshold, now)).toBe(false); expect(service.isOnline(threshold - 1, now)).toBe(false); }); }); });