farmcontrol-ws/src/socket/__tests__/socketuser.test.js
Tom Butcher 4bfc7fae2a
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
Implemented about page.
2026-06-21 13:19:18 +01:00

265 lines
7.9 KiB
JavaScript

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('../../lock/lockmanager.js', () => ({
LockManager: jest.fn().mockImplementation(() => ({
lockObject: jest.fn(),
unlockObject: jest.fn(),
getObjectLock: jest.fn()
}))
}));
jest.unstable_mockModule('../../updates/updatemanager.js', () => ({
UpdateManager: jest.fn().mockImplementation(() => ({
subscribeToObjectNew: jest.fn(),
subscribeToObjectDelete: jest.fn(),
subscribeToObjectUpdate: jest.fn(),
removeObjectNewListener: jest.fn(),
removeObjectDeleteListener: jest.fn(),
removeObjectUpdateListener: 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('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 = {
templateManager: {
renderTemplate: jest.fn(),
renderPDF: jest.fn()
}
};
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('lock', expect.any(Function));
expect(mockSocket.on).toHaveBeenCalledWith(
'disconnect',
expect.any(Function)
);
expect(mockSocket.on).toHaveBeenCalledWith(
'getServerVersion',
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('lock event handlers', () => {
beforeEach(() => {
socketUser.user = { _id: 'user-id' };
});
it('handleLockEvent should call lockManager.lockObject', async () => {
const data = { _id: 'obj-1', type: 'printer' };
await socketUser.handleLockEvent(data);
expect(socketUser.lockManager.lockObject).toHaveBeenCalledWith({
...data,
user: 'user-id'
});
});
it('handleUnlockEvent should call lockManager.unlockObject', async () => {
const data = { _id: 'obj-1' };
await socketUser.handleUnlockEvent(data);
expect(socketUser.lockManager.unlockObject).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('handleDisconnect', () => {
it('should remove all listeners', async () => {
await socketUser.handleDisconnect();
expect(socketUser.actionManager.removeAllListeners).toHaveBeenCalled();
expect(socketUser.eventManager.removeAllListeners).toHaveBeenCalled();
expect(socketUser.statsManager.removeAllListeners).toHaveBeenCalled();
expect(socketUser.notificationManager.removeAllListeners).toHaveBeenCalled();
});
});
});