farmcontrol-ws/src/socket/__tests__/sockethost.test.js

216 lines
5.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', () => ({
CodeAuth: jest.fn().mockImplementation(() => ({
verifyCode: jest.fn(),
verifyOtp: jest.fn()
})),
createAuthMiddleware: jest.fn(() => (socket, next) => next())
}));
jest.unstable_mockModule('../../database/database.js', () => ({
newObject: jest.fn(),
editObject: jest.fn(),
getObject: jest.fn(),
listObjects: jest.fn()
}));
jest.unstable_mockModule(
'../../database/schemas/management/host.schema.js',
() => ({
hostModel: { modelName: 'host' }
})
);
jest.unstable_mockModule('../../updates/updatemanager.js', () => ({
UpdateManager: jest.fn().mockImplementation(() => ({
subscribeToObjectUpdate: jest.fn(),
unsubscribeToObjectUpdate: jest.fn()
}))
}));
jest.unstable_mockModule('../../actions/actionmanager.js', () => ({
ActionManager: jest.fn().mockImplementation(() => ({
subscribeToObjectActions: jest.fn(),
removeAllListeners: jest.fn()
}))
}));
jest.unstable_mockModule('../../events/eventmanager.js', () => ({
EventManager: jest.fn().mockImplementation(() => ({
sendObjectEvent: jest.fn(),
subscribeToObjectEvent: jest.fn(),
removeObjectEventsListener: jest.fn(),
removeAllListeners: jest.fn()
}))
}));
jest.unstable_mockModule('../../templates/templatemanager.js', () => ({
TemplateManager: jest.fn().mockImplementation(() => ({
renderPDF: jest.fn()
}))
}));
jest.unstable_mockModule('../../utils.js', () => ({
getModelByName: jest.fn(name => ({ modelName: name }))
}));
jest.unstable_mockModule('log4js', () => ({
default: {
getLogger: () => ({
level: 'info',
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
trace: jest.fn(),
info: jest.fn()
})
}
}));
const { SocketHost } = await import('../sockethost.js');
const { editObject, newObject, getObject, listObjects } = await import(
'../../database/database.js'
);
describe('SocketHost', () => {
let mockSocket;
let mockSocketManager;
let socketHost;
beforeEach(() => {
jest.clearAllMocks();
mockSocket = {
id: 'test-socket-id',
use: jest.fn(),
on: jest.fn(),
emit: jest.fn()
};
mockSocketManager = {};
socketHost = new SocketHost(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(
'disconnect',
expect.any(Function)
);
});
describe('handleAuthenticate', () => {
it('should authenticate with id and authCode', async () => {
const data = { id: 'host-1', authCode: 'code-123' };
const callback = jest.fn();
const mockHost = { _id: 'host-id-obj' };
socketHost.codeAuth.verifyCode.mockResolvedValue({
valid: true,
host: mockHost
});
editObject.mockResolvedValue({});
await socketHost.handleAuthenticate(data, callback);
expect(socketHost.codeAuth.verifyCode).toHaveBeenCalledWith(
'host-1',
'code-123'
);
expect(editObject).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith({ valid: true, host: mockHost });
expect(socketHost.authenticated).toBe(true);
});
it('should authenticate with otp', async () => {
const data = { otp: '123456' };
const callback = jest.fn();
const mockHost = { _id: 'host-id-obj' };
socketHost.codeAuth.verifyOtp.mockResolvedValue({
valid: true,
host: mockHost
});
editObject.mockResolvedValue({});
await socketHost.handleAuthenticate(data, callback);
expect(socketHost.codeAuth.verifyOtp).toHaveBeenCalledWith('123456');
expect(callback).toHaveBeenCalledWith({ valid: true, host: mockHost });
});
it('should return error if params are missing', async () => {
const data = {};
const callback = jest.fn();
await socketHost.handleAuthenticate(data, callback);
expect(callback).toHaveBeenCalledWith({
valid: false,
error: 'Missing params.'
});
});
});
describe('database operations handlers', () => {
beforeEach(() => {
socketHost.host = { _id: 'host-id' };
});
it('handleNewObject should call newObject and callback', async () => {
const data = { objectType: 'printer', newData: { name: 'P1' } };
const callback = jest.fn();
newObject.mockResolvedValue({ _id: 'new-id' });
await socketHost.handleNewObject(data, callback);
expect(newObject).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith({ _id: 'new-id' });
});
it('handleEditObject should call editObject and callback', async () => {
const data = {
objectType: 'printer',
_id: 'p1',
updateData: { status: 'idle' }
};
const callback = jest.fn();
editObject.mockResolvedValue({ success: true });
await socketHost.handleEditObject(data, callback);
expect(editObject).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith({ success: true });
});
});
describe('handleDisconnect', () => {
it('should set host offline if authenticated', async () => {
socketHost.authenticated = true;
socketHost.id = 'host-id';
socketHost.host = { _id: 'host-id' };
listObjects.mockResolvedValue([]); // for setDevicesState
await socketHost.handleDisconnect();
expect(editObject).toHaveBeenCalledWith(
expect.objectContaining({
id: 'host-id',
updateData: expect.objectContaining({ online: false })
})
);
expect(socketHost.actionManager.removeAllListeners).toHaveBeenCalled();
});
});
});