Implement Jest testing framework and add initial test cases for various modules. Update configuration for test environment in config.json and add test results handling in Jenkinsfile. Include .gitignore entry for test results. Enhance package.json with Jest dependencies.
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
This commit is contained in:
parent
0ed5eb86dd
commit
f6e7fb3a41
2
.gitignore
vendored
2
.gitignore
vendored
@ -132,3 +132,5 @@ dist
|
|||||||
*.DS_STORE
|
*.DS_STORE
|
||||||
|
|
||||||
*.env
|
*.env
|
||||||
|
|
||||||
|
test-results.xml
|
||||||
|
|||||||
20
Jenkinsfile
vendored
20
Jenkinsfile
vendored
@ -31,6 +31,22 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stage('Run Tests') {
|
||||||
|
steps {
|
||||||
|
nodejs(nodeJSInstallationName: 'Node23') {
|
||||||
|
sh '''
|
||||||
|
export NODE_ENV=test
|
||||||
|
yarn test
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
junit 'test-results.xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Deploy via SSH') {
|
stage('Deploy via SSH') {
|
||||||
steps {
|
steps {
|
||||||
sshPublisher(publishers: [
|
sshPublisher(publishers: [
|
||||||
@ -41,9 +57,9 @@ pipeline {
|
|||||||
cleanRemote: false,
|
cleanRemote: false,
|
||||||
excludes: 'node_modules/**',
|
excludes: 'node_modules/**',
|
||||||
execCommand: '''
|
execCommand: '''
|
||||||
cd /opt/farmcontrol-ws
|
cd /home/farmcontrol/farmcontrol-ws
|
||||||
yarn install --production
|
yarn install --production
|
||||||
pm2 restart ecosystem.config.js --env production || pm2 start ecosystem.config.js --env production
|
sudo systemctl restart farmcontrol-ws
|
||||||
''',
|
''',
|
||||||
execTimeout: 120000,
|
execTimeout: 120000,
|
||||||
flatten: false,
|
flatten: false,
|
||||||
|
|||||||
22
config.json
22
config.json
@ -21,6 +21,28 @@
|
|||||||
},
|
},
|
||||||
"otpExpiryMins": 0.5
|
"otpExpiryMins": 0.5
|
||||||
},
|
},
|
||||||
|
"test": {
|
||||||
|
"server": {
|
||||||
|
"port": 9091,
|
||||||
|
"logLevel": "error"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": false,
|
||||||
|
"keycloak": {
|
||||||
|
"url": "http://localhost:8080",
|
||||||
|
"realm": "test",
|
||||||
|
"clientId": "test-client"
|
||||||
|
},
|
||||||
|
"requiredRoles": []
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"mongo": {
|
||||||
|
"url": "mongodb://127.0.0.1:27017/farmcontrol-test"
|
||||||
|
},
|
||||||
|
"redis": { "host": "localhost", "port": 6379, "password": "" }
|
||||||
|
},
|
||||||
|
"otpExpiryMins": 0.5
|
||||||
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"server": {
|
"server": {
|
||||||
"port": 8081,
|
"port": 8081,
|
||||||
|
|||||||
23
jest.config.cjs
Normal file
23
jest.config.cjs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: 'node',
|
||||||
|
transform: {},
|
||||||
|
moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
|
||||||
|
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
|
||||||
|
verbose: true,
|
||||||
|
reporters: [
|
||||||
|
'default',
|
||||||
|
[
|
||||||
|
'jest-junit',
|
||||||
|
{
|
||||||
|
outputDirectory: '.',
|
||||||
|
outputName: 'test-results.xml',
|
||||||
|
suiteName: 'farmcontrol-ws-tests',
|
||||||
|
classNameTemplate: '{classname}',
|
||||||
|
titleTemplate: '{title}',
|
||||||
|
ancestorSeparator: ' › ',
|
||||||
|
usePathForSuiteName: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
@ -5,7 +5,7 @@
|
|||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"dev": "nodemon src/index.js",
|
"dev": "nodemon src/index.js",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
@ -43,11 +43,16 @@
|
|||||||
"socketio-jwt": "^4.6.2"
|
"socketio-jwt": "^4.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@jest/globals": "^30.2.0",
|
||||||
|
"babel-jest": "^30.2.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
|
"jest": "^30.2.0",
|
||||||
|
"jest-junit": "^16.0.0",
|
||||||
"nodemon": "^3.1.11",
|
"nodemon": "^3.1.11",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"standard": "^17.1.2"
|
"standard": "^17.1.2",
|
||||||
|
"supertest": "^7.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/__tests__/config.test.js
Normal file
60
src/__tests__/config.test.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
describe('Config Module', () => {
|
||||||
|
let originalEnv;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalEnv = process.env.NODE_ENV;
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env.NODE_ENV = originalEnv;
|
||||||
|
delete process.env.KEYCLOAK_CLIENT_SECRET;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadConfig', () => {
|
||||||
|
it('should load test config when NODE_ENV=test', async () => {
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
const { loadConfig } = await import('../config.js');
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
expect(config).toBeDefined();
|
||||||
|
expect(config.server).toBeDefined();
|
||||||
|
expect(config.server.port).toBe(9091);
|
||||||
|
expect(config.server.logLevel).toBe('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override keycloak client secret from environment variable', async () => {
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
process.env.KEYCLOAK_CLIENT_SECRET = 'test-secret';
|
||||||
|
const { loadConfig } = await import('../config.js');
|
||||||
|
const config = loadConfig();
|
||||||
|
|
||||||
|
expect(config.auth.keycloak.clientSecret).toBe('test-secret');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if environment config does not exist', async () => {
|
||||||
|
process.env.NODE_ENV = 'nonexistent';
|
||||||
|
const { loadConfig } = await import('../config.js');
|
||||||
|
|
||||||
|
// Suppress console.error during this test as it's expected
|
||||||
|
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
|
expect(() => loadConfig()).toThrow(
|
||||||
|
"Configuration for environment 'nonexistent' not found in config.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
consoleSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getEnvironment', () => {
|
||||||
|
it('should return current NODE_ENV', async () => {
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
const { getEnvironment } = await import('../config.js');
|
||||||
|
expect(getEnvironment()).toBe('test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
164
src/actions/__tests__/actionmanager.test.js
Normal file
164
src/actions/__tests__/actionmanager.test.js
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true })
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../utils.js', () => ({
|
||||||
|
generateEtcId: jest.fn(() => 'test-etc-id')
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { ActionManager } = await import('../actionmanager.js');
|
||||||
|
const { natsServer } = await import('../../database/nats.js');
|
||||||
|
const { generateEtcId } = await import('../../utils.js');
|
||||||
|
|
||||||
|
describe('ActionManager', () => {
|
||||||
|
let mockSocketClient;
|
||||||
|
let actionManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockSocketClient = {
|
||||||
|
id: 'test-socket-id',
|
||||||
|
socketId: 'test-socket-id',
|
||||||
|
socket: {
|
||||||
|
emit: jest.fn((event, data, cb) => {
|
||||||
|
if (cb) cb({ status: 'success' });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
actionManager = new ActionManager(mockSocketClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToObjectActions', () => {
|
||||||
|
it('should subscribe to object actions and emit when received', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
|
||||||
|
await actionManager.subscribeToObjectActions(id, objectType);
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate NATS message
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const actionData = { some: 'action', actionId: 'act-1' };
|
||||||
|
|
||||||
|
natsCallback('printers.obj-123.actions', actionData);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith(
|
||||||
|
'objectAction',
|
||||||
|
expect.objectContaining({
|
||||||
|
_id: id,
|
||||||
|
objectType: objectType,
|
||||||
|
action: actionData
|
||||||
|
}),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that the emit callback publishes back to NATS
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions.act-1',
|
||||||
|
expect.objectContaining({
|
||||||
|
...actionData,
|
||||||
|
result: { status: 'success' }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeObjectActionsListener', () => {
|
||||||
|
it('should remove subscription and update internal state', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
|
||||||
|
await actionManager.subscribeToObjectActions(id, objectType);
|
||||||
|
await actionManager.removeObjectActionsListener(id, objectType);
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
expect(actionManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendObjectAction', () => {
|
||||||
|
it('should publish action and subscribe for response', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
const action = { type: 'start' };
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
await actionManager.sendObjectAction(id, objectType, action, callback);
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions.test-etc-id',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions',
|
||||||
|
expect.objectContaining({
|
||||||
|
...action,
|
||||||
|
actionId: 'test-etc-id'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate NATS response
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
await natsCallback('printers.obj-123.actions.test-etc-id', {
|
||||||
|
result: 'ok'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith('ok');
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.actions.test-etc-id',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeAllListeners', () => {
|
||||||
|
it('should remove all active subscriptions', async () => {
|
||||||
|
await actionManager.subscribeToObjectActions('1', 'printer');
|
||||||
|
await actionManager.subscribeToObjectActions('2', 'printer');
|
||||||
|
|
||||||
|
await actionManager.removeAllListeners();
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledTimes(2);
|
||||||
|
expect(actionManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -12,11 +12,9 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const CONFIG_PATH = path.resolve(__dirname, '../config.json');
|
const CONFIG_PATH = path.resolve(__dirname, '../config.json');
|
||||||
|
|
||||||
// Determine environment
|
|
||||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
||||||
|
|
||||||
// Load config file
|
// Load config file
|
||||||
export function loadConfig() {
|
export function loadConfig() {
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(CONFIG_PATH)) {
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
throw new Error(`Configuration file not found at ${CONFIG_PATH}`);
|
throw new Error(`Configuration file not found at ${CONFIG_PATH}`);
|
||||||
@ -25,13 +23,13 @@ export function loadConfig() {
|
|||||||
const configData = fs.readFileSync(CONFIG_PATH, 'utf8');
|
const configData = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||||
const config = JSON.parse(configData);
|
const config = JSON.parse(configData);
|
||||||
|
|
||||||
if (!config[NODE_ENV]) {
|
if (!config[env]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Configuration for environment '${NODE_ENV}' not found in config.json`
|
`Configuration for environment '${env}' not found in config.json`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const envConfig = config[NODE_ENV];
|
const envConfig = config[env];
|
||||||
|
|
||||||
// Override keycloak client secret with environment variable if available
|
// Override keycloak client secret with environment variable if available
|
||||||
if (process.env.KEYCLOAK_CLIENT_SECRET) {
|
if (process.env.KEYCLOAK_CLIENT_SECRET) {
|
||||||
@ -53,5 +51,5 @@ export function loadConfig() {
|
|||||||
|
|
||||||
// Get current environment
|
// Get current environment
|
||||||
export function getEnvironment() {
|
export function getEnvironment() {
|
||||||
return NODE_ENV;
|
return process.env.NODE_ENV || 'development';
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/events/__tests__/eventmanager.test.js
Normal file
134
src/events/__tests__/eventmanager.test.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true })
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { EventManager } = await import('../eventmanager.js');
|
||||||
|
const { natsServer } = await import('../../database/nats.js');
|
||||||
|
|
||||||
|
describe('EventManager', () => {
|
||||||
|
let mockSocketClient;
|
||||||
|
let eventManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockSocketClient = {
|
||||||
|
socketId: 'test-socket-id',
|
||||||
|
socket: {
|
||||||
|
emit: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventManager = new EventManager(mockSocketClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToObjectEvent', () => {
|
||||||
|
it('should subscribe to object events and emit when received', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
const eventType = 'status';
|
||||||
|
|
||||||
|
await eventManager.subscribeToObjectEvent(id, objectType, eventType);
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.events.status',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate NATS message
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const eventData = { status: 'online' };
|
||||||
|
|
||||||
|
natsCallback('printers.obj-123.events.status', eventData);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith('objectEvent', {
|
||||||
|
_id: id,
|
||||||
|
objectType: objectType,
|
||||||
|
event: eventData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeObjectEventsListener', () => {
|
||||||
|
it('should remove specific subscription', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
const eventType = 'status';
|
||||||
|
|
||||||
|
await eventManager.subscribeToObjectEvent(id, objectType, eventType);
|
||||||
|
await eventManager.removeObjectEventsListener(id, objectType, eventType);
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.events.status',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
expect(eventManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendObjectEvent', () => {
|
||||||
|
it('should publish event to NATS', async () => {
|
||||||
|
const id = 'obj-123';
|
||||||
|
const objectType = 'printer';
|
||||||
|
const event = { type: 'alert', message: 'low filament' };
|
||||||
|
|
||||||
|
const result = await eventManager.sendObjectEvent(id, objectType, event);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith(
|
||||||
|
'printers.obj-123.events.alert',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors when publishing fails', async () => {
|
||||||
|
natsServer.publish.mockRejectedValueOnce(new Error('NATS error'));
|
||||||
|
|
||||||
|
const result = await eventManager.sendObjectEvent('id', 'type', {
|
||||||
|
type: 't'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('error', 'NATS error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeAllListeners', () => {
|
||||||
|
it('should remove all subscriptions', async () => {
|
||||||
|
await eventManager.subscribeToObjectEvent('1', 'printer', 'e1');
|
||||||
|
await eventManager.subscribeToObjectEvent('2', 'printer', 'e2');
|
||||||
|
|
||||||
|
await eventManager.removeAllListeners();
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledTimes(2);
|
||||||
|
expect(eventManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
226
src/lock/__tests__/lockmanager.test.js
Normal file
226
src/lock/__tests__/lockmanager.test.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/redis.js', () => ({
|
||||||
|
redisServer: {
|
||||||
|
setKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
getKey: jest.fn().mockResolvedValue(null),
|
||||||
|
deleteKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info',
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { LockManager } = await import('../lockmanager.js');
|
||||||
|
const { natsServer } = await import('../../database/nats.js');
|
||||||
|
const { redisServer } = await import('../../database/redis.js');
|
||||||
|
|
||||||
|
describe('LockManager', () => {
|
||||||
|
let mockSocketClient;
|
||||||
|
let lockManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockSocketClient = {
|
||||||
|
id: 'test-socket-id',
|
||||||
|
socket: {
|
||||||
|
emit: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
lockManager = new LockManager(mockSocketClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('lockObject', () => {
|
||||||
|
it('should lock an object and publish via NATS', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await lockManager.lockObject(testObject);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(redisServer.setKey).toHaveBeenCalledWith(
|
||||||
|
'locks:products:test-id-123',
|
||||||
|
expect.objectContaining({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
locked: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith(
|
||||||
|
'locks.products.test-id-123',
|
||||||
|
expect.objectContaining({
|
||||||
|
locked: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors when locking fails', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.setKey.mockRejectedValueOnce(new Error('Redis error'));
|
||||||
|
|
||||||
|
await expect(lockManager.lockObject(testObject)).rejects.toThrow('Redis error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('unlockObject', () => {
|
||||||
|
it('should unlock an object when user matches', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockResolvedValueOnce({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
locked: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lockManager.unlockObject(testObject);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(redisServer.deleteKey).toHaveBeenCalledWith('locks:products:test-id-123');
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith(
|
||||||
|
'locks.products.test-id-123',
|
||||||
|
expect.objectContaining({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
locked: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not unlock when user does not match', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockResolvedValueOnce({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'different-user',
|
||||||
|
locked: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lockManager.unlockObject(testObject);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
expect(redisServer.deleteKey).not.toHaveBeenCalled();
|
||||||
|
expect(natsServer.publish).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors when unlocking fails', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockRejectedValueOnce(new Error('Redis error'));
|
||||||
|
|
||||||
|
await expect(lockManager.unlockObject(testObject)).rejects.toThrow('Redis error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getObjectLock', () => {
|
||||||
|
it('should return locked status when object is locked', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockResolvedValueOnce({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
locked: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await lockManager.getObjectLock(testObject);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
user: 'user-123',
|
||||||
|
locked: true,
|
||||||
|
});
|
||||||
|
expect(redisServer.getKey).toHaveBeenCalledWith('locks:products:test-id-123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unlocked status when object is not locked', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
|
const result = await lockManager.getObjectLock(testObject);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
_id: 'test-id-123',
|
||||||
|
locked: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors when getting lock status fails', async () => {
|
||||||
|
const testObject = {
|
||||||
|
_id: 'test-id-123',
|
||||||
|
type: 'product',
|
||||||
|
};
|
||||||
|
|
||||||
|
redisServer.getKey.mockRejectedValueOnce(new Error('Redis error'));
|
||||||
|
|
||||||
|
await expect(lockManager.getObjectLock(testObject)).rejects.toThrow('Redis error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setupLocksListeners', () => {
|
||||||
|
it('should subscribe to NATS lock changes', () => {
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'locks.>',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
215
src/socket/__tests__/sockethost.test.js
Normal file
215
src/socket/__tests__/sockethost.test.js
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
136
src/socket/__tests__/socketmanager.test.js
Normal file
136
src/socket/__tests__/socketmanager.test.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('socket.io', () => ({
|
||||||
|
Server: jest.fn(() => ({
|
||||||
|
on: jest.fn(),
|
||||||
|
emit: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../socketuser.js', () => ({
|
||||||
|
SocketUser: jest.fn((socket, manager) => ({
|
||||||
|
id: socket.id,
|
||||||
|
socket,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../sockethost.js', () => ({
|
||||||
|
SocketHost: jest.fn((socket, manager) => ({
|
||||||
|
id: socket.id,
|
||||||
|
socket,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/redis.js', () => ({
|
||||||
|
redisServer: {
|
||||||
|
setKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
getKey: jest.fn().mockResolvedValue(null),
|
||||||
|
deleteKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
getKeysByPattern: jest.fn().mockResolvedValue([]),
|
||||||
|
connect: jest.fn().mockResolvedValue(undefined),
|
||||||
|
disconnect: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../lock/lockmanager.js', () => ({
|
||||||
|
LockManager: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../templates/templatemanager.js', () => ({
|
||||||
|
TemplateManager: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info',
|
||||||
|
corsOrigins: '*',
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { SocketManager } = await import('../socketmanager.js');
|
||||||
|
const { Server } = await import('socket.io');
|
||||||
|
|
||||||
|
describe('SocketManager', () => {
|
||||||
|
let mockServer;
|
||||||
|
let socketManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockServer = {};
|
||||||
|
socketManager = new SocketManager(mockServer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize correctly', () => {
|
||||||
|
expect(Server).toHaveBeenCalledWith(mockServer, expect.any(Object));
|
||||||
|
expect(socketManager.io.on).toHaveBeenCalledWith('connection', expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('connection handling', () => {
|
||||||
|
it('should add a user when auth type is user', async () => {
|
||||||
|
const mockSocket = {
|
||||||
|
id: 'user-socket',
|
||||||
|
handshake: { auth: { type: 'user' } },
|
||||||
|
on: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectionHandler = socketManager.io.on.mock.calls[0][1];
|
||||||
|
await connectionHandler(mockSocket);
|
||||||
|
|
||||||
|
expect(socketManager.socketUsers.has('user-socket')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a host when auth type is host', async () => {
|
||||||
|
const mockSocket = {
|
||||||
|
id: 'host-socket',
|
||||||
|
handshake: { auth: { type: 'host' } },
|
||||||
|
on: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectionHandler = socketManager.io.on.mock.calls[0][1];
|
||||||
|
await connectionHandler(mockSocket);
|
||||||
|
|
||||||
|
expect(socketManager.socketHosts.has('host-socket')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendToUser', () => {
|
||||||
|
it('should emit event to all matching user connections', () => {
|
||||||
|
const mockUserSocket = {
|
||||||
|
id: 's1',
|
||||||
|
emit: jest.fn(),
|
||||||
|
};
|
||||||
|
socketManager.socketUsers.set('s1', {
|
||||||
|
user: { _id: 'u1' },
|
||||||
|
socket: mockUserSocket,
|
||||||
|
});
|
||||||
|
|
||||||
|
socketManager.sendToUser('u1', 'testEvent', { data: 'ok' });
|
||||||
|
|
||||||
|
expect(mockUserSocket.emit).toHaveBeenCalledWith('testEvent', { data: 'ok' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
187
src/socket/__tests__/socketuser.test.js
Normal file
187
src/socket/__tests__/socketuser.test.js
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
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('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));
|
||||||
|
});
|
||||||
|
|
||||||
|
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(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('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('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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
123
src/stats/__tests__/statsmanager.test.js
Normal file
123
src/stats/__tests__/statsmanager.test.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true })
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { StatsManager } = await import('../statsmanager.js');
|
||||||
|
const { natsServer } = await import('../../database/nats.js');
|
||||||
|
|
||||||
|
describe('StatsManager', () => {
|
||||||
|
let mockSocketClient;
|
||||||
|
let statsManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockSocketClient = {
|
||||||
|
socketId: 'test-socket-id',
|
||||||
|
socket: {
|
||||||
|
emit: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
statsManager = new StatsManager(mockSocketClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToStats', () => {
|
||||||
|
it('should subscribe to stats updates and emit when received', async () => {
|
||||||
|
const type = 'printer';
|
||||||
|
|
||||||
|
await statsManager.subscribeToStats(type);
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.stats',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate NATS message
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const statsData = { count: 5, active: 2 };
|
||||||
|
|
||||||
|
natsCallback('printers.stats', statsData);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith('modelStats', {
|
||||||
|
objectType: type,
|
||||||
|
stats: statsData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeStatsListener', () => {
|
||||||
|
it('should remove stats subscription', async () => {
|
||||||
|
const type = 'printer';
|
||||||
|
|
||||||
|
await statsManager.subscribeToStats(type);
|
||||||
|
await statsManager.removeStatsListener(type);
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.stats',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
expect(statsManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendStats', () => {
|
||||||
|
it('should publish stats to NATS', async () => {
|
||||||
|
const type = 'printer';
|
||||||
|
const stats = { total: 10 };
|
||||||
|
|
||||||
|
const result = await statsManager.sendStats(type, stats);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
expect(natsServer.publish).toHaveBeenCalledWith('printers.stats', stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors when publishing fails', async () => {
|
||||||
|
natsServer.publish.mockRejectedValueOnce(new Error('NATS error'));
|
||||||
|
|
||||||
|
const result = await statsManager.sendStats('type', {});
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('error', 'NATS error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeAllListeners', () => {
|
||||||
|
it('should remove all subscriptions', async () => {
|
||||||
|
await statsManager.subscribeToStats('type1');
|
||||||
|
await statsManager.subscribeToStats('type2');
|
||||||
|
|
||||||
|
await statsManager.removeAllListeners();
|
||||||
|
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledTimes(2);
|
||||||
|
expect(statsManager.subscriptions.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
184
src/templates/__tests__/templatemanager.test.js
Normal file
184
src/templates/__tests__/templatemanager.test.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Mock fs before importing TemplateManager
|
||||||
|
jest.unstable_mockModule('fs', () => ({
|
||||||
|
default: {
|
||||||
|
readFileSync: jest.fn(filePath => {
|
||||||
|
if (filePath.endsWith('basetemplate.ejs'))
|
||||||
|
return '<html><%- content %></html>';
|
||||||
|
if (filePath.endsWith('styles.css')) return 'body { color: red; }';
|
||||||
|
if (filePath.endsWith('previewtemplate.ejs'))
|
||||||
|
return '<div class="preview"><%- content %></div>';
|
||||||
|
if (filePath.endsWith('rendertemplate.ejs'))
|
||||||
|
return '<div class="render"><%- content %></div>';
|
||||||
|
if (filePath.endsWith('contentplaceholder.ejs'))
|
||||||
|
return '<div class="placeholder"></div>';
|
||||||
|
if (filePath.endsWith('previewpagination.js'))
|
||||||
|
return 'console.log("pagination");';
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('ejs', () => ({
|
||||||
|
default: {
|
||||||
|
render: jest.fn(async (content, data) => `rendered: ${content}`),
|
||||||
|
compile: jest.fn(() => jest.fn())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('posthtml', () => ({
|
||||||
|
default: jest.fn(() => ({
|
||||||
|
process: jest.fn(async content => ({ html: `transformed: ${content}` }))
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
publish: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true })
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/redis.js', () => ({
|
||||||
|
redisServer: {
|
||||||
|
setKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
getKey: jest.fn().mockResolvedValue(null),
|
||||||
|
deleteKey: jest.fn().mockResolvedValue(undefined),
|
||||||
|
getKeysByPattern: jest.fn().mockResolvedValue([])
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/database.js', () => ({
|
||||||
|
getObject: jest.fn(),
|
||||||
|
listObjects: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule(
|
||||||
|
'../../database/schemas/management/documenttemplate.schema.js',
|
||||||
|
() => ({
|
||||||
|
documentTemplateModel: { modelName: 'DocumentTemplate' }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../utils.js', () => ({
|
||||||
|
getModelByName: jest.fn(() => ({
|
||||||
|
schema: {
|
||||||
|
obj: { name: {}, status: {} }
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../pdffactory.js', () => ({
|
||||||
|
generatePDF: jest.fn().mockResolvedValue(Buffer.from('pdf-data'))
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { TemplateManager } = await import('../templatemanager.js');
|
||||||
|
const { getObject } = await import('../../database/database.js');
|
||||||
|
const ejs = (await import('ejs')).default;
|
||||||
|
const { generatePDF } = await import('../pdffactory.js');
|
||||||
|
|
||||||
|
describe('TemplateManager', () => {
|
||||||
|
let templateManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
templateManager = new TemplateManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderTemplate', () => {
|
||||||
|
it('should render a template successfully', async () => {
|
||||||
|
const mockTemplate = {
|
||||||
|
documentSize: { width: 100, height: 100, infiniteHeight: false },
|
||||||
|
global: false,
|
||||||
|
objectType: 'printer'
|
||||||
|
};
|
||||||
|
|
||||||
|
getObject.mockResolvedValue(mockTemplate);
|
||||||
|
|
||||||
|
const result = await templateManager.renderTemplate(
|
||||||
|
'temp-id',
|
||||||
|
'some content',
|
||||||
|
{ name: 'Test' }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getObject).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ id: 'temp-id' })
|
||||||
|
);
|
||||||
|
expect(ejs.render).toHaveBeenCalled();
|
||||||
|
expect(result).toHaveProperty('html');
|
||||||
|
expect(result.width).toBe(100);
|
||||||
|
expect(result.height).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error if template not found', async () => {
|
||||||
|
getObject.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await templateManager.renderTemplate('invalid', 'content');
|
||||||
|
|
||||||
|
expect(result).toEqual({ error: 'Document template not found.' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateTemplate', () => {
|
||||||
|
it('should return true for valid EJS', () => {
|
||||||
|
expect(templateManager.validateTemplate('<div><%= name %></div>')).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for invalid EJS', () => {
|
||||||
|
ejs.compile.mockImplementationOnce(() => {
|
||||||
|
throw new Error('syntax error');
|
||||||
|
});
|
||||||
|
expect(templateManager.validateTemplate('<% invalid %>')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderPDF', () => {
|
||||||
|
it('should render a PDF successfully', async () => {
|
||||||
|
const mockTemplate = {
|
||||||
|
documentSize: { width: 100, height: 100, infiniteHeight: false },
|
||||||
|
global: false,
|
||||||
|
objectType: 'printer'
|
||||||
|
};
|
||||||
|
getObject.mockResolvedValue(mockTemplate);
|
||||||
|
|
||||||
|
const result = await templateManager.renderPDF('temp-id', 'content');
|
||||||
|
|
||||||
|
expect(generatePDF).toHaveBeenCalled();
|
||||||
|
expect(result.pdf).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatDate', () => {
|
||||||
|
it('should format date correctly', () => {
|
||||||
|
const date = new Date('2023-01-01T12:00:00Z');
|
||||||
|
const formatted = templateManager.formatDate(date, 'YYYY-MM-DD');
|
||||||
|
expect(formatted).toBe('2023-01-01');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
146
src/updates/__tests__/updatemanager.test.js
Normal file
146
src/updates/__tests__/updatemanager.test.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../database/nats.js', () => ({
|
||||||
|
natsServer: {
|
||||||
|
subscribe: jest.fn().mockResolvedValue({ success: true }),
|
||||||
|
removeSubscription: jest.fn().mockResolvedValue({ success: true })
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('log4js', () => ({
|
||||||
|
default: {
|
||||||
|
getLogger: () => ({
|
||||||
|
level: 'info',
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
info: jest.fn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.unstable_mockModule('../../config.js', () => ({
|
||||||
|
loadConfig: jest.fn(() => ({
|
||||||
|
server: {
|
||||||
|
logLevel: 'info'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { UpdateManager } = await import('../updatemanager.js');
|
||||||
|
const { natsServer } = await import('../../database/nats.js');
|
||||||
|
|
||||||
|
describe('UpdateManager', () => {
|
||||||
|
let mockSocketClient;
|
||||||
|
let updateManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
mockSocketClient = {
|
||||||
|
socketId: 'test-socket-id',
|
||||||
|
socket: {
|
||||||
|
emit: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateManager = new UpdateManager(mockSocketClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToObjectNew', () => {
|
||||||
|
it('should subscribe to new object events and emit', async () => {
|
||||||
|
await updateManager.subscribeToObjectNew('printer');
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.new',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const data = { name: 'New Printer' };
|
||||||
|
natsCallback('printers.new', data);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith('objectNew', {
|
||||||
|
object: data,
|
||||||
|
objectType: 'printer'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToObjectDelete', () => {
|
||||||
|
it('should subscribe to delete events and emit', async () => {
|
||||||
|
await updateManager.subscribeToObjectDelete('printer');
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.delete',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const data = { _id: '123' };
|
||||||
|
natsCallback('printers.delete', data);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith(
|
||||||
|
'objectDelete',
|
||||||
|
{
|
||||||
|
object: data,
|
||||||
|
objectType: 'printer'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('subscribeToObjectUpdate', () => {
|
||||||
|
it('should subscribe to update events for specific object', async () => {
|
||||||
|
await updateManager.subscribeToObjectUpdate('123', 'printer');
|
||||||
|
|
||||||
|
expect(natsServer.subscribe).toHaveBeenCalledWith(
|
||||||
|
'printers.123.object',
|
||||||
|
'test-socket-id',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
const natsCallback = natsServer.subscribe.mock.calls[0][2];
|
||||||
|
const data = { status: 'idle' };
|
||||||
|
natsCallback('printers.123.object', data);
|
||||||
|
|
||||||
|
expect(mockSocketClient.socket.emit).toHaveBeenCalledWith(
|
||||||
|
'objectUpdate',
|
||||||
|
{
|
||||||
|
_id: '123',
|
||||||
|
objectType: 'printer',
|
||||||
|
object: data
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove methods', () => {
|
||||||
|
it('should remove new listener', async () => {
|
||||||
|
await updateManager.removeObjectNewListener('printer');
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.new',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove delete listener', async () => {
|
||||||
|
await updateManager.removeObjectDeleteListener('printer');
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.delete',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove update listener', async () => {
|
||||||
|
await updateManager.removeObjectUpdateListener('123', 'printer');
|
||||||
|
expect(natsServer.removeSubscription).toHaveBeenCalledWith(
|
||||||
|
'printers.123.object',
|
||||||
|
'test-socket-id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user