import { jest } from '@jest/globals'; // Mock dependencies jest.unstable_mockModule('../../config.js', () => ({ loadConfig: jest.fn(() => ({ server: { logLevel: 'info' } })) })); jest.unstable_mockModule('../../auth/auth.js', () => ({ KeycloakAuth: jest.fn().mockImplementation(() => ({ verifyToken: jest.fn() })), createAuthMiddleware: jest.fn(() => (socket, next) => next()) })); jest.unstable_mockModule('../../utils.js', () => ({ generateHostOTP: jest.fn() })); jest.unstable_mockModule('../../activity/activitymanager.js', () => ({ ActivityManager: jest.fn().mockImplementation(() => ({ setActivity: jest.fn(), clearActivity: jest.fn(), getObjectActivities: jest.fn(), subscribeToObjectActivity: jest.fn(), removeObjectActivityListener: jest.fn(), removeAllListeners: jest.fn() })) })); jest.unstable_mockModule('../../updates/updatemanager.js', () => ({ UpdateManager: jest.fn().mockImplementation(() => ({ subscribeToObjectNew: jest.fn(), subscribeToObjectDelete: jest.fn(), subscribeToObjectUpdate: jest.fn(), subscribeToAllObjectUpdates: jest.fn(), removeObjectNewListener: jest.fn(), removeObjectDeleteListener: jest.fn(), removeObjectUpdateListener: jest.fn(), removeAllObjectUpdatesListener: jest.fn(), removeAllListeners: jest.fn() })) })); jest.unstable_mockModule('../../actions/actionmanager.js', () => ({ ActionManager: jest.fn().mockImplementation(() => ({ sendObjectAction: jest.fn(), removeAllListeners: jest.fn() })) })); jest.unstable_mockModule('../../events/eventmanager.js', () => ({ EventManager: jest.fn().mockImplementation(() => ({ subscribeToObjectEvent: jest.fn(), removeObjectEventsListener: jest.fn(), removeAllListeners: jest.fn() })) })); jest.unstable_mockModule('../../stats/statsmanager.js', () => ({ StatsManager: jest.fn().mockImplementation(() => ({ subscribeToStats: jest.fn(), removeStatsListener: jest.fn(), removeAllListeners: jest.fn() })) })); jest.unstable_mockModule('../../notification/notificationmanager.js', () => ({ NotificationManager: jest.fn().mockImplementation(() => ({ subscribe: jest.fn().mockResolvedValue({ success: true }), removeAllListeners: jest.fn().mockResolvedValue({ success: true }) })) })); jest.unstable_mockModule('../../server/servermanager.js', () => ({ ServerManager: jest.fn().mockImplementation(() => ({ getServerVersion: jest.fn(() => ({ version: '1.0.0', buildNumber: 'dev' })) })) })); jest.unstable_mockModule('../../usersettings/usersettingsmanager.js', () => ({ UserSettingsManager: jest.fn().mockImplementation(() => ({ getUserSettings: jest.fn(), updateUserSettings: jest.fn() })) })); jest.unstable_mockModule('log4js', () => ({ default: { getLogger: () => ({ level: 'info', debug: jest.fn(), error: jest.fn(), warn: jest.fn(), trace: jest.fn(), info: jest.fn() }) } })); const { SocketUser } = await import('../socketuser.js'); const { generateHostOTP } = await import('../../utils.js'); describe('SocketUser', () => { let mockSocket; let mockSocketManager; let socketUser; beforeEach(() => { jest.clearAllMocks(); mockSocket = { id: 'test-user-socket-id', use: jest.fn(), on: jest.fn(), emit: jest.fn() }; mockSocketManager = {}; socketUser = new SocketUser(mockSocket, mockSocketManager); }); it('should initialize correctly and setup event handlers', () => { expect(mockSocket.use).toHaveBeenCalled(); expect(mockSocket.on).toHaveBeenCalledWith( 'authenticate', expect.any(Function) ); expect(mockSocket.on).toHaveBeenCalledWith( 'setActivity', expect.any(Function) ); expect(mockSocket.on).toHaveBeenCalledWith( 'disconnect', expect.any(Function) ); expect(mockSocket.on).toHaveBeenCalledWith( 'getServerVersion', expect.any(Function) ); expect(mockSocket.on).toHaveBeenCalledWith( 'getUserSettings', expect.any(Function) ); expect(mockSocket.on).toHaveBeenCalledWith( 'updateUserSettings', expect.any(Function) ); }); describe('handleAuthenticateEvent', () => { it('should authenticate user with valid token', async () => { const data = { token: 'valid-token' }; const callback = jest.fn(); const mockUser = { _id: 'user-id-obj', username: 'testuser' }; socketUser.keycloakAuth.verifyToken.mockResolvedValue({ valid: true, user: mockUser }); await socketUser.handleAuthenticateEvent(data, callback); expect(socketUser.keycloakAuth.verifyToken).toHaveBeenCalledWith( 'valid-token' ); expect(socketUser.authenticated).toBe(true); expect(socketUser.user).toEqual(mockUser); expect(socketUser.id).toBe('user-id-obj'); expect(socketUser.notificationManager.subscribe).toHaveBeenCalled(); expect(callback).toHaveBeenCalledWith({ valid: true, user: mockUser }); }); it('should not authenticate user with invalid token', async () => { const data = { token: 'invalid-token' }; const callback = jest.fn(); socketUser.keycloakAuth.verifyToken.mockResolvedValue({ valid: false }); await socketUser.handleAuthenticateEvent(data, callback); expect(socketUser.authenticated).toBe(false); expect(callback).toHaveBeenCalledWith({ valid: false }); }); }); describe('activity event handlers', () => { beforeEach(() => { socketUser.user = { _id: 'user-id' }; }); it('handleSetActivityEvent should call activityManager.setActivity', async () => { const data = { _id: 'obj-1', type: 'printer', mode: 'viewing' }; await socketUser.handleSetActivityEvent(data); expect(socketUser.activityManager.setActivity).toHaveBeenCalledWith({ ...data, user: 'user-id' }); }); it('handleClearActivityEvent should call activityManager.clearActivity', async () => { const data = { _id: 'obj-1', type: 'printer' }; await socketUser.handleClearActivityEvent(data); expect(socketUser.activityManager.clearActivity).toHaveBeenCalledWith({ ...data, user: 'user-id' }); }); }); describe('object type subscription handlers', () => { it('should pass filters to type subscription methods', async () => { const data = { objectType: 'note', filter: { 'parent._id': 'parent-id' } }; const callback = jest.fn(); await socketUser.handleSubscribeToObjectTypeUpdateEvent(data, callback); expect( socketUser.updateManager.subscribeToObjectNew ).toHaveBeenCalledWith('note', data.filter); expect( socketUser.updateManager.subscribeToObjectDelete ).toHaveBeenCalledWith('note', data.filter); expect(callback).toHaveBeenCalledWith({ success: true }); }); it('should pass filters to type unsubscribe methods', async () => { const data = { objectType: 'note', filter: { 'parent._id': 'parent-id' } }; await socketUser.handleUnsubscribeToObjectTypeUpdateEvent(data); expect( socketUser.updateManager.removeObjectNewListener ).toHaveBeenCalledWith('note', data.filter); expect( socketUser.updateManager.removeObjectDeleteListener ).toHaveBeenCalledWith('note', data.filter); }); }); describe('handleGenerateHostOtpEvent', () => { it('should call generateHostOTP and callback', async () => { const data = { _id: 'host-id' }; const callback = jest.fn(); generateHostOTP.mockResolvedValue('otp-123'); await socketUser.handleGenerateHostOtpEvent(data, callback); expect(generateHostOTP).toHaveBeenCalledWith('host-id'); expect(callback).toHaveBeenCalledWith('otp-123'); }); }); describe('handleGetServerVersionEvent', () => { it('should return server version details', async () => { const callback = jest.fn(); await socketUser.handleGetServerVersionEvent({}, callback); expect(socketUser.serverManager.getServerVersion).toHaveBeenCalled(); expect(callback).toHaveBeenCalledWith({ version: '1.0.0', buildNumber: 'dev' }); }); }); describe('user settings event handlers', () => { it('returns user settings through the acknowledgement callback', async () => { const callback = jest.fn(); const settings = { viewMode: { payments: 'cards' } }; socketUser.userSettingsManager.getUserSettings.mockResolvedValue( settings ); await socketUser.handleGetUserSettingsEvent({}, callback); expect(callback).toHaveBeenCalledWith({ success: true, settings }); }); it('updates user settings through the acknowledgement callback', async () => { const callback = jest.fn(); const data = { category: 'viewMode', key: 'payments', value: 'cards' }; const settings = { viewMode: { payments: 'cards' } }; socketUser.userSettingsManager.updateUserSettings.mockResolvedValue( settings ); await socketUser.handleUpdateUserSettingsEvent(data, callback); expect( socketUser.userSettingsManager.updateUserSettings ).toHaveBeenCalledWith(data); expect(callback).toHaveBeenCalledWith({ success: true, settings }); }); it('returns user settings errors through the acknowledgement callback', async () => { const callback = jest.fn(); socketUser.userSettingsManager.getUserSettings.mockRejectedValue( new Error('database unavailable') ); await socketUser.handleGetUserSettingsEvent({}, callback); expect(callback).toHaveBeenCalledWith({ success: false, error: 'database unavailable' }); }); }); describe('handleDisconnect', () => { it('should remove all listeners', async () => { await socketUser.handleDisconnect(); expect(socketUser.actionManager.removeAllListeners).toHaveBeenCalled(); expect(socketUser.updateManager.removeAllListeners).toHaveBeenCalled(); expect(socketUser.eventManager.removeAllListeners).toHaveBeenCalled(); expect(socketUser.statsManager.removeAllListeners).toHaveBeenCalled(); expect( socketUser.notificationManager.removeAllListeners ).toHaveBeenCalled(); }); it('should cancel tracked scan sessions', async () => { socketUser.scanSessions.set('session-1', 'host-1'); socketUser.scanSessions.set('session-2', 'host-2'); await socketUser.handleDisconnect(); expect(socketUser.actionManager.sendObjectAction).toHaveBeenCalledWith( 'host-1', 'host', { type: 'scanNetworkStop', data: { sessionId: 'session-1' } } ); expect(socketUser.actionManager.sendObjectAction).toHaveBeenCalledWith( 'host-2', 'host', { type: 'scanNetworkStop', data: { sessionId: 'session-2' } } ); expect(socketUser.scanSessions.size).toBe(0); }); }); });