Improve InfluxDB test coverage

This commit is contained in:
Göran Sander
2025-12-16 19:31:54 +01:00
parent 4c778ecd94
commit de839c6970
4 changed files with 1127 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
import { jest, describe, test, expect } from '@jest/globals';
import { postErrorMetricsToInfluxdb } from '../error-metrics.js';
describe('error-metrics', () => {
describe('postErrorMetricsToInfluxdb', () => {
test('should resolve successfully with valid error stats', async () => {
const errorStats = {
HEALTH_API: {
total: 5,
servers: {
sense1: 3,
sense2: 2,
},
},
INFLUXDB_V3_WRITE: {
total: 2,
servers: {
_no_server_context: 2,
},
},
};
await expect(postErrorMetricsToInfluxdb(errorStats)).resolves.toBeUndefined();
});
test('should resolve successfully with empty error stats', async () => {
const errorStats = {};
await expect(postErrorMetricsToInfluxdb(errorStats)).resolves.toBeUndefined();
});
test('should resolve successfully with null input', async () => {
await expect(postErrorMetricsToInfluxdb(null)).resolves.toBeUndefined();
});
test('should resolve successfully with undefined input', async () => {
await expect(postErrorMetricsToInfluxdb(undefined)).resolves.toBeUndefined();
});
test('should resolve successfully with complex error stats', async () => {
const errorStats = {
API_TYPE_1: {
total: 100,
servers: {
server1: 25,
server2: 25,
server3: 25,
server4: 25,
},
},
API_TYPE_2: {
total: 0,
servers: {},
},
};
await expect(postErrorMetricsToInfluxdb(errorStats)).resolves.toBeUndefined();
});
});
});

View File

@@ -47,6 +47,81 @@ jest.unstable_mockModule('../v1/queue-metrics.js', () => ({
storeLogEventQueueMetricsV1: jest.fn(),
}));
jest.unstable_mockModule('../v1/health-metrics.js', () => ({
storeHealthMetricsV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/health-metrics.js', () => ({
storeHealthMetricsV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/health-metrics.js', () => ({
postHealthMetricsToInfluxdbV3: jest.fn(),
}));
jest.unstable_mockModule('../v1/sessions.js', () => ({
storeSessionsV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/sessions.js', () => ({
storeSessionsV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/sessions.js', () => ({
postProxySessionsToInfluxdbV3: jest.fn(),
}));
jest.unstable_mockModule('../v1/butler-memory.js', () => ({
storeButlerMemoryV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/butler-memory.js', () => ({
storeButlerMemoryV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/butler-memory.js', () => ({
postButlerSOSMemoryUsageToInfluxdbV3: jest.fn(),
}));
jest.unstable_mockModule('../v1/user-events.js', () => ({
storeUserEventV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/user-events.js', () => ({
storeUserEventV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/user-events.js', () => ({
postUserEventToInfluxdbV3: jest.fn(),
}));
jest.unstable_mockModule('../v1/log-events.js', () => ({
storeLogEventV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/log-events.js', () => ({
storeLogEventV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/log-events.js', () => ({
postLogEventToInfluxdbV3: jest.fn(),
}));
jest.unstable_mockModule('../v1/event-counts.js', () => ({
storeEventCountV1: jest.fn(),
storeRejectedEventCountV1: jest.fn(),
}));
jest.unstable_mockModule('../v2/event-counts.js', () => ({
storeEventCountV2: jest.fn(),
storeRejectedEventCountV2: jest.fn(),
}));
jest.unstable_mockModule('../v3/event-counts.js', () => ({
storeEventCountInfluxDBV3: jest.fn(),
storeRejectedEventCountInfluxDBV3: jest.fn(),
}));
describe('InfluxDB Factory', () => {
let factory;
let globals;
@@ -54,6 +129,12 @@ describe('InfluxDB Factory', () => {
let v3Impl;
let v2Impl;
let v1Impl;
let v3Health, v2Health, v1Health;
let v3Sessions, v2Sessions, v1Sessions;
let v3Memory, v2Memory, v1Memory;
let v3User, v2User, v1User;
let v3Log, v2Log, v1Log;
let v3EventCounts, v2EventCounts, v1EventCounts;
beforeEach(async () => {
jest.clearAllMocks();
@@ -63,6 +144,31 @@ describe('InfluxDB Factory', () => {
v3Impl = await import('../v3/queue-metrics.js');
v2Impl = await import('../v2/queue-metrics.js');
v1Impl = await import('../v1/queue-metrics.js');
v3Health = await import('../v3/health-metrics.js');
v2Health = await import('../v2/health-metrics.js');
v1Health = await import('../v1/health-metrics.js');
v3Sessions = await import('../v3/sessions.js');
v2Sessions = await import('../v2/sessions.js');
v1Sessions = await import('../v1/sessions.js');
v3Memory = await import('../v3/butler-memory.js');
v2Memory = await import('../v2/butler-memory.js');
v1Memory = await import('../v1/butler-memory.js');
v3User = await import('../v3/user-events.js');
v2User = await import('../v2/user-events.js');
v1User = await import('../v1/user-events.js');
v3Log = await import('../v3/log-events.js');
v2Log = await import('../v2/log-events.js');
v1Log = await import('../v1/log-events.js');
v3EventCounts = await import('../v3/event-counts.js');
v2EventCounts = await import('../v2/event-counts.js');
v1EventCounts = await import('../v1/event-counts.js');
factory = await import('../factory.js');
// Setup default mocks
@@ -72,6 +178,33 @@ describe('InfluxDB Factory', () => {
v2Impl.storeLogEventQueueMetricsV2.mockResolvedValue();
v1Impl.storeUserEventQueueMetricsV1.mockResolvedValue();
v1Impl.storeLogEventQueueMetricsV1.mockResolvedValue();
v3Health.postHealthMetricsToInfluxdbV3.mockResolvedValue();
v2Health.storeHealthMetricsV2.mockResolvedValue();
v1Health.storeHealthMetricsV1.mockResolvedValue();
v3Sessions.postProxySessionsToInfluxdbV3.mockResolvedValue();
v2Sessions.storeSessionsV2.mockResolvedValue();
v1Sessions.storeSessionsV1.mockResolvedValue();
v3Memory.postButlerSOSMemoryUsageToInfluxdbV3.mockResolvedValue();
v2Memory.storeButlerMemoryV2.mockResolvedValue();
v1Memory.storeButlerMemoryV1.mockResolvedValue();
v3User.postUserEventToInfluxdbV3.mockResolvedValue();
v2User.storeUserEventV2.mockResolvedValue();
v1User.storeUserEventV1.mockResolvedValue();
v3Log.postLogEventToInfluxdbV3.mockResolvedValue();
v2Log.storeLogEventV2.mockResolvedValue();
v1Log.storeLogEventV1.mockResolvedValue();
v3EventCounts.storeEventCountInfluxDBV3.mockResolvedValue();
v3EventCounts.storeRejectedEventCountInfluxDBV3.mockResolvedValue();
v2EventCounts.storeEventCountV2.mockResolvedValue();
v2EventCounts.storeRejectedEventCountV2.mockResolvedValue();
v1EventCounts.storeEventCountV1.mockResolvedValue();
v1EventCounts.storeRejectedEventCountV1.mockResolvedValue();
});
describe('postUserEventQueueMetricsToInfluxdb', () => {
@@ -157,4 +290,279 @@ describe('InfluxDB Factory', () => {
);
});
});
describe('postHealthMetricsToInfluxdb', () => {
const serverName = 'test-server';
const host = 'test-host';
const body = { version: '1.0' };
const serverTags = [{ name: 'env', value: 'prod' }];
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.postHealthMetricsToInfluxdb(serverName, host, body, serverTags);
expect(v3Health.postHealthMetricsToInfluxdbV3).toHaveBeenCalledWith(
serverName,
host,
body,
serverTags
);
expect(v2Health.storeHealthMetricsV2).not.toHaveBeenCalled();
expect(v1Health.storeHealthMetricsV1).not.toHaveBeenCalled();
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.postHealthMetricsToInfluxdb(serverName, host, body, serverTags);
expect(v2Health.storeHealthMetricsV2).toHaveBeenCalledWith(
serverName,
host,
body,
serverTags
);
expect(v3Health.postHealthMetricsToInfluxdbV3).not.toHaveBeenCalled();
expect(v1Health.storeHealthMetricsV1).not.toHaveBeenCalled();
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.postHealthMetricsToInfluxdb(serverName, host, body, serverTags);
expect(v1Health.storeHealthMetricsV1).toHaveBeenCalledWith(serverTags, body);
expect(v3Health.postHealthMetricsToInfluxdbV3).not.toHaveBeenCalled();
expect(v2Health.storeHealthMetricsV2).not.toHaveBeenCalled();
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(4);
await expect(
factory.postHealthMetricsToInfluxdb(serverName, host, body, serverTags)
).rejects.toThrow('InfluxDB v4 not supported');
});
});
describe('postProxySessionsToInfluxdb', () => {
const userSessions = { serverName: 'test', host: 'test-host' };
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.postProxySessionsToInfluxdb(userSessions);
expect(v3Sessions.postProxySessionsToInfluxdbV3).toHaveBeenCalledWith(userSessions);
expect(v2Sessions.storeSessionsV2).not.toHaveBeenCalled();
expect(v1Sessions.storeSessionsV1).not.toHaveBeenCalled();
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.postProxySessionsToInfluxdb(userSessions);
expect(v2Sessions.storeSessionsV2).toHaveBeenCalledWith(userSessions);
expect(v3Sessions.postProxySessionsToInfluxdbV3).not.toHaveBeenCalled();
expect(v1Sessions.storeSessionsV1).not.toHaveBeenCalled();
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.postProxySessionsToInfluxdb(userSessions);
expect(v1Sessions.storeSessionsV1).toHaveBeenCalledWith(userSessions);
expect(v3Sessions.postProxySessionsToInfluxdbV3).not.toHaveBeenCalled();
expect(v2Sessions.storeSessionsV2).not.toHaveBeenCalled();
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(10);
await expect(factory.postProxySessionsToInfluxdb(userSessions)).rejects.toThrow(
'InfluxDB v10 not supported'
);
});
});
describe('postButlerSOSMemoryUsageToInfluxdb', () => {
const memory = { heap_used: 100, heap_total: 200 };
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.postButlerSOSMemoryUsageToInfluxdb(memory);
expect(v3Memory.postButlerSOSMemoryUsageToInfluxdbV3).toHaveBeenCalledWith(memory);
expect(v2Memory.storeButlerMemoryV2).not.toHaveBeenCalled();
expect(v1Memory.storeButlerMemoryV1).not.toHaveBeenCalled();
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.postButlerSOSMemoryUsageToInfluxdb(memory);
expect(v2Memory.storeButlerMemoryV2).toHaveBeenCalledWith(memory);
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.postButlerSOSMemoryUsageToInfluxdb(memory);
expect(v1Memory.storeButlerMemoryV1).toHaveBeenCalledWith(memory);
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(7);
await expect(factory.postButlerSOSMemoryUsageToInfluxdb(memory)).rejects.toThrow(
'InfluxDB v7 not supported'
);
});
});
describe('postUserEventToInfluxdb', () => {
const msg = { host: 'test-host', command: 'OpenApp' };
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.postUserEventToInfluxdb(msg);
expect(v3User.postUserEventToInfluxdbV3).toHaveBeenCalledWith(msg);
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.postUserEventToInfluxdb(msg);
expect(v2User.storeUserEventV2).toHaveBeenCalledWith(msg);
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.postUserEventToInfluxdb(msg);
expect(v1User.storeUserEventV1).toHaveBeenCalledWith(msg);
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(0);
await expect(factory.postUserEventToInfluxdb(msg)).rejects.toThrow(
'InfluxDB v0 not supported'
);
});
});
describe('postLogEventToInfluxdb', () => {
const msg = { host: 'test-host', source: 'qseow-engine' };
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.postLogEventToInfluxdb(msg);
expect(v3Log.postLogEventToInfluxdbV3).toHaveBeenCalledWith(msg);
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.postLogEventToInfluxdb(msg);
expect(v2Log.storeLogEventV2).toHaveBeenCalledWith(msg);
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.postLogEventToInfluxdb(msg);
expect(v1Log.storeLogEventV1).toHaveBeenCalledWith(msg);
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(-1);
await expect(factory.postLogEventToInfluxdb(msg)).rejects.toThrow(
'InfluxDB v-1 not supported'
);
});
});
describe('storeEventCountInfluxDB', () => {
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.storeEventCountInfluxDB();
expect(v3EventCounts.storeEventCountInfluxDBV3).toHaveBeenCalled();
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.storeEventCountInfluxDB();
expect(v2EventCounts.storeEventCountV2).toHaveBeenCalled();
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.storeEventCountInfluxDB();
expect(v1EventCounts.storeEventCountV1).toHaveBeenCalled();
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(100);
await expect(factory.storeEventCountInfluxDB()).rejects.toThrow(
'InfluxDB v100 not supported'
);
});
});
describe('storeRejectedEventCountInfluxDB', () => {
test('should route to v3 implementation when version is 3', async () => {
utils.getInfluxDbVersion.mockReturnValue(3);
await factory.storeRejectedEventCountInfluxDB();
expect(v3EventCounts.storeRejectedEventCountInfluxDBV3).toHaveBeenCalled();
});
test('should route to v2 implementation when version is 2', async () => {
utils.getInfluxDbVersion.mockReturnValue(2);
await factory.storeRejectedEventCountInfluxDB();
expect(v2EventCounts.storeRejectedEventCountV2).toHaveBeenCalled();
});
test('should route to v1 implementation when version is 1', async () => {
utils.getInfluxDbVersion.mockReturnValue(1);
await factory.storeRejectedEventCountInfluxDB();
expect(v1EventCounts.storeRejectedEventCountV1).toHaveBeenCalled();
});
test('should throw error for unsupported version', async () => {
utils.getInfluxDbVersion.mockReturnValue(99);
await expect(factory.storeRejectedEventCountInfluxDB()).rejects.toThrow(
'InfluxDB v99 not supported'
);
});
});
});

View File

@@ -0,0 +1,301 @@
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
// Mock globals
const mockGlobals = {
logger: {
info: jest.fn(),
verbose: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
},
config: {
get: jest.fn(),
has: jest.fn(),
},
};
jest.unstable_mockModule('../../../globals.js', () => ({
default: mockGlobals,
}));
// Mock factory
const mockFactory = {
postHealthMetricsToInfluxdb: jest.fn(),
postProxySessionsToInfluxdb: jest.fn(),
postButlerSOSMemoryUsageToInfluxdb: jest.fn(),
postUserEventToInfluxdb: jest.fn(),
postLogEventToInfluxdb: jest.fn(),
storeEventCountInfluxDB: jest.fn(),
storeRejectedEventCountInfluxDB: jest.fn(),
postUserEventQueueMetricsToInfluxdb: jest.fn(),
postLogEventQueueMetricsToInfluxdb: jest.fn(),
};
jest.unstable_mockModule('../factory.js', () => mockFactory);
// Mock shared utils
jest.unstable_mockModule('../shared/utils.js', () => ({
getFormattedTime: jest.fn((time) => `formatted-${time}`),
}));
describe('InfluxDB Index (Facade)', () => {
let indexModule;
let globals;
beforeEach(async () => {
jest.clearAllMocks();
globals = (await import('../../../globals.js')).default;
indexModule = await import('../index.js');
// Setup default mock implementations
mockFactory.postHealthMetricsToInfluxdb.mockResolvedValue();
mockFactory.postProxySessionsToInfluxdb.mockResolvedValue();
mockFactory.postButlerSOSMemoryUsageToInfluxdb.mockResolvedValue();
mockFactory.postUserEventToInfluxdb.mockResolvedValue();
mockFactory.postLogEventToInfluxdb.mockResolvedValue();
mockFactory.storeEventCountInfluxDB.mockResolvedValue();
mockFactory.storeRejectedEventCountInfluxDB.mockResolvedValue();
mockFactory.postUserEventQueueMetricsToInfluxdb.mockResolvedValue();
mockFactory.postLogEventQueueMetricsToInfluxdb.mockResolvedValue();
globals.config.get.mockReturnValue(true);
});
describe('getFormattedTime', () => {
test('should be exported and callable', () => {
expect(indexModule.getFormattedTime).toBeDefined();
expect(typeof indexModule.getFormattedTime).toBe('function');
});
test('should format time correctly', () => {
const result = indexModule.getFormattedTime('20240101T120000');
expect(result).toBe('formatted-20240101T120000');
});
});
describe('postHealthMetricsToInfluxdb', () => {
test('should delegate to factory', async () => {
const serverName = 'server1';
const host = 'host1';
const body = { version: '1.0' };
const serverTags = [{ name: 'env', value: 'prod' }];
await indexModule.postHealthMetricsToInfluxdb(serverName, host, body, serverTags);
expect(mockFactory.postHealthMetricsToInfluxdb).toHaveBeenCalledWith(
serverName,
host,
body,
serverTags
);
});
});
describe('postProxySessionsToInfluxdb', () => {
test('should delegate to factory', async () => {
const userSessions = { serverName: 'test', host: 'test-host' };
await indexModule.postProxySessionsToInfluxdb(userSessions);
expect(mockFactory.postProxySessionsToInfluxdb).toHaveBeenCalledWith(userSessions);
});
});
describe('postButlerSOSMemoryUsageToInfluxdb', () => {
test('should delegate to factory', async () => {
const memory = { heap_used: 100, heap_total: 200 };
await indexModule.postButlerSOSMemoryUsageToInfluxdb(memory);
expect(mockFactory.postButlerSOSMemoryUsageToInfluxdb).toHaveBeenCalledWith(memory);
});
});
describe('postUserEventToInfluxdb', () => {
test('should delegate to factory', async () => {
const msg = { host: 'test-host', command: 'OpenApp' };
await indexModule.postUserEventToInfluxdb(msg);
expect(mockFactory.postUserEventToInfluxdb).toHaveBeenCalledWith(msg);
});
});
describe('postLogEventToInfluxdb', () => {
test('should delegate to factory', async () => {
const msg = { host: 'test-host', source: 'qseow-engine' };
await indexModule.postLogEventToInfluxdb(msg);
expect(mockFactory.postLogEventToInfluxdb).toHaveBeenCalledWith(msg);
});
});
describe('storeEventCountInfluxDB', () => {
test('should delegate to factory', async () => {
await indexModule.storeEventCountInfluxDB('midnight', 'hour');
expect(mockFactory.storeEventCountInfluxDB).toHaveBeenCalled();
});
test('should ignore deprecated parameters', async () => {
await indexModule.storeEventCountInfluxDB('deprecated1', 'deprecated2');
expect(mockFactory.storeEventCountInfluxDB).toHaveBeenCalledWith();
});
});
describe('storeRejectedEventCountInfluxDB', () => {
test('should delegate to factory', async () => {
await indexModule.storeRejectedEventCountInfluxDB('midnight', 'hour');
expect(mockFactory.storeRejectedEventCountInfluxDB).toHaveBeenCalled();
});
test('should ignore deprecated parameters', async () => {
await indexModule.storeRejectedEventCountInfluxDB({ data: 'old' }, { data: 'old2' });
expect(mockFactory.storeRejectedEventCountInfluxDB).toHaveBeenCalledWith();
});
});
describe('postUserEventQueueMetricsToInfluxdb', () => {
test('should delegate to factory', async () => {
await indexModule.postUserEventQueueMetricsToInfluxdb({ some: 'data' });
expect(mockFactory.postUserEventQueueMetricsToInfluxdb).toHaveBeenCalled();
});
test('should ignore deprecated parameter', async () => {
await indexModule.postUserEventQueueMetricsToInfluxdb({ old: 'metrics' });
expect(mockFactory.postUserEventQueueMetricsToInfluxdb).toHaveBeenCalledWith();
});
});
describe('postLogEventQueueMetricsToInfluxdb', () => {
test('should delegate to factory', async () => {
await indexModule.postLogEventQueueMetricsToInfluxdb({ some: 'data' });
expect(mockFactory.postLogEventQueueMetricsToInfluxdb).toHaveBeenCalled();
});
test('should ignore deprecated parameter', async () => {
await indexModule.postLogEventQueueMetricsToInfluxdb({ old: 'metrics' });
expect(mockFactory.postLogEventQueueMetricsToInfluxdb).toHaveBeenCalledWith();
});
});
describe('setupUdpQueueMetricsStorage', () => {
let intervalSpy;
beforeEach(() => {
intervalSpy = jest.spyOn(global, 'setInterval');
});
afterEach(() => {
intervalSpy.mockRestore();
});
test('should return empty interval IDs when InfluxDB is disabled', () => {
globals.config.get.mockImplementation((path) => {
if (path.includes('influxdbConfig.enable')) return false;
return undefined;
});
const result = indexModule.setupUdpQueueMetricsStorage();
expect(result).toEqual({
userEvents: null,
logEvents: null,
});
expect(globals.logger.info).toHaveBeenCalledWith(
expect.stringContaining('InfluxDB is disabled')
);
});
test('should setup user event queue metrics when enabled', () => {
globals.config.get.mockImplementation((path) => {
if (path.includes('influxdbConfig.enable')) return true;
if (path.includes('userEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return true;
if (
path.includes('userEvents.udpServerConfig.queueMetrics.influxdb.writeFrequency')
)
return 60000;
if (path.includes('logEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return false;
return undefined;
});
const result = indexModule.setupUdpQueueMetricsStorage();
expect(result.userEvents).not.toBeNull();
expect(intervalSpy).toHaveBeenCalledWith(expect.any(Function), 60000);
expect(globals.logger.info).toHaveBeenCalledWith(
expect.stringContaining('user event queue metrics')
);
});
test('should setup log event queue metrics when enabled', () => {
globals.config.get.mockImplementation((path) => {
if (path.includes('influxdbConfig.enable')) return true;
if (path.includes('userEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return false;
if (path.includes('logEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return true;
if (path.includes('logEvents.udpServerConfig.queueMetrics.influxdb.writeFrequency'))
return 30000;
return undefined;
});
const result = indexModule.setupUdpQueueMetricsStorage();
expect(result.logEvents).not.toBeNull();
expect(intervalSpy).toHaveBeenCalledWith(expect.any(Function), 30000);
});
test('should setup both metrics when both enabled', () => {
globals.config.get.mockImplementation((path) => {
if (path.includes('influxdbConfig.enable')) return true;
if (path.includes('userEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return true;
if (
path.includes('userEvents.udpServerConfig.queueMetrics.influxdb.writeFrequency')
)
return 45000;
if (path.includes('logEvents.udpServerConfig.queueMetrics.influxdb.enable'))
return true;
if (path.includes('logEvents.udpServerConfig.queueMetrics.influxdb.writeFrequency'))
return 55000;
return undefined;
});
const result = indexModule.setupUdpQueueMetricsStorage();
expect(result.userEvents).not.toBeNull();
expect(result.logEvents).not.toBeNull();
expect(intervalSpy).toHaveBeenCalledTimes(2);
});
test('should log when metrics are disabled', () => {
globals.config.get.mockImplementation((path) => {
if (path.includes('influxdbConfig.enable')) return true;
if (path.includes('queueMetrics.influxdb.enable')) return false;
return undefined;
});
indexModule.setupUdpQueueMetricsStorage();
expect(globals.logger.info).toHaveBeenCalledWith(
expect.stringContaining('User event queue metrics storage to InfluxDB is disabled')
);
expect(globals.logger.info).toHaveBeenCalledWith(
expect.stringContaining('Log event queue metrics storage to InfluxDB is disabled')
);
});
});
});

View File

@@ -0,0 +1,358 @@
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
// Mock globals
const mockGlobals = {
logger: {
info: jest.fn(),
verbose: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
},
config: {
get: jest.fn(),
has: jest.fn(),
},
influx: null,
appNames: [],
};
jest.unstable_mockModule('../../../globals.js', () => ({
default: mockGlobals,
}));
describe('Shared Utils - getFormattedTime', () => {
let utils;
let globals;
beforeEach(async () => {
jest.clearAllMocks();
globals = (await import('../../../globals.js')).default;
utils = await import('../shared/utils.js');
});
test('should return empty string for null input', () => {
const result = utils.getFormattedTime(null);
expect(result).toBe('');
});
test('should return empty string for undefined input', () => {
const result = utils.getFormattedTime(undefined);
expect(result).toBe('');
});
test('should return empty string for empty string input', () => {
const result = utils.getFormattedTime('');
expect(result).toBe('');
});
test('should return empty string for non-string input', () => {
const result = utils.getFormattedTime(12345);
expect(result).toBe('');
});
test('should return empty string for string shorter than minimum length', () => {
const result = utils.getFormattedTime('20240101T12');
expect(result).toBe('');
});
test('should return empty string for invalid date components', () => {
const result = utils.getFormattedTime('abcdXXXXTxxxxxx');
expect(result).toBe('');
});
test('should handle invalid date gracefully', () => {
// JavaScript Date constructor is lenient and converts Month 13 to January of next year
// So this doesn't actually fail - it's a valid date to JS
const result = utils.getFormattedTime('20241301T250000');
// The function doesn't validate date ranges, so this will return a formatted time
expect(typeof result).toBe('string');
});
test('should format valid timestamp correctly', () => {
// Mock Date.now to return a known value
const mockNow = new Date('2024-01-01T13:00:00').getTime();
jest.spyOn(Date, 'now').mockReturnValue(mockNow);
const result = utils.getFormattedTime('20240101T120000');
// Should show approximately 1 hour difference
expect(result).toMatch(/\d+ days, \d+h \d+m \d+s/);
Date.now.mockRestore();
});
test('should handle timestamps with exact minimum length', () => {
const mockNow = new Date('2024-01-01T13:00:00').getTime();
jest.spyOn(Date, 'now').mockReturnValue(mockNow);
const result = utils.getFormattedTime('20240101T120000');
expect(result).not.toBe('');
expect(result).toMatch(/\d+ days/);
Date.now.mockRestore();
});
test('should handle future timestamps', () => {
const mockNow = new Date('2024-01-01T12:00:00').getTime();
jest.spyOn(Date, 'now').mockReturnValue(mockNow);
// Server started in the future (edge case)
const result = utils.getFormattedTime('20250101T120000');
// Result might be negative or weird, but shouldn't crash
expect(typeof result).toBe('string');
Date.now.mockRestore();
});
});
describe('Shared Utils - processAppDocuments', () => {
let utils;
let globals;
beforeEach(async () => {
jest.clearAllMocks();
globals = (await import('../../../globals.js')).default;
utils = await import('../shared/utils.js');
globals.appNames = [
{ id: 'app-123', name: 'Sales Dashboard' },
{ id: 'app-456', name: 'HR Analytics' },
{ id: 'app-789', name: 'Finance Report' },
];
});
test('should process empty array', async () => {
const result = await utils.processAppDocuments([], 'TEST', 'active');
expect(result).toEqual({
appNames: [],
sessionAppNames: [],
});
});
test('should identify session apps correctly', async () => {
const docIDs = ['SessionApp_12345', 'SessionApp_67890'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.sessionAppNames).toEqual(['SessionApp_12345', 'SessionApp_67890']);
expect(result.appNames).toEqual([]);
expect(globals.logger.debug).toHaveBeenCalledWith(
expect.stringContaining('Session app is active')
);
});
test('should resolve app IDs to names', async () => {
const docIDs = ['app-123', 'app-456'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'loaded');
expect(result.appNames).toEqual(['HR Analytics', 'Sales Dashboard']);
expect(result.sessionAppNames).toEqual([]);
expect(globals.logger.debug).toHaveBeenCalledWith(
expect.stringContaining('App is loaded: Sales Dashboard')
);
});
test('should use doc ID when app name not found', async () => {
const docIDs = ['app-unknown', 'app-123'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'in memory');
expect(result.appNames).toEqual(['Sales Dashboard', 'app-unknown']);
expect(result.sessionAppNames).toEqual([]);
});
test('should mix session apps and regular apps', async () => {
const docIDs = ['app-123', 'SessionApp_abc', 'app-456', 'SessionApp_def', 'app-unknown'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.appNames).toEqual(['HR Analytics', 'Sales Dashboard', 'app-unknown']);
expect(result.sessionAppNames).toEqual(['SessionApp_abc', 'SessionApp_def']);
});
test('should sort both arrays alphabetically', async () => {
const docIDs = ['app-789', 'app-123', 'app-456', 'SessionApp_z', 'SessionApp_a'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.appNames).toEqual(['Finance Report', 'HR Analytics', 'Sales Dashboard']);
expect(result.sessionAppNames).toEqual(['SessionApp_a', 'SessionApp_z']);
});
test('should handle session app prefix at start only', async () => {
const docIDs = ['SessionApp_test', 'NotSessionApp_test', 'app-123'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.sessionAppNames).toEqual(['SessionApp_test']);
expect(result.appNames).toEqual(['NotSessionApp_test', 'Sales Dashboard']);
});
test('should handle single document', async () => {
const docIDs = ['app-456'];
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.appNames).toEqual(['HR Analytics']);
expect(result.sessionAppNames).toEqual([]);
});
test('should handle many documents efficiently', async () => {
const docIDs = Array.from({ length: 100 }, (_, i) =>
i % 2 === 0 ? `SessionApp_${i}` : `app-${i}`
);
const result = await utils.processAppDocuments(docIDs, 'TEST', 'active');
expect(result.sessionAppNames.length).toBe(50);
expect(result.appNames.length).toBe(50);
// Arrays are sorted alphabetically
expect(result.sessionAppNames).toEqual(expect.arrayContaining(['SessionApp_0']));
expect(result.appNames).toEqual(expect.arrayContaining(['app-1']));
});
});
describe('Shared Utils - applyTagsToPoint3', () => {
let utils;
let mockPoint;
beforeEach(async () => {
jest.clearAllMocks();
utils = await import('../shared/utils.js');
mockPoint = {
setTag: jest.fn().mockReturnThis(),
};
});
test('should return point unchanged for null tags', () => {
const result = utils.applyTagsToPoint3(mockPoint, null);
expect(result).toBe(mockPoint);
expect(mockPoint.setTag).not.toHaveBeenCalled();
});
test('should return point unchanged for undefined tags', () => {
const result = utils.applyTagsToPoint3(mockPoint, undefined);
expect(result).toBe(mockPoint);
expect(mockPoint.setTag).not.toHaveBeenCalled();
});
test('should return point unchanged for non-object tags', () => {
const result = utils.applyTagsToPoint3(mockPoint, 'not-an-object');
expect(result).toBe(mockPoint);
expect(mockPoint.setTag).not.toHaveBeenCalled();
});
test('should apply single tag', () => {
const tags = { env: 'production' };
const result = utils.applyTagsToPoint3(mockPoint, tags);
expect(result).toBe(mockPoint);
expect(mockPoint.setTag).toHaveBeenCalledWith('env', 'production');
expect(mockPoint.setTag).toHaveBeenCalledTimes(1);
});
test('should apply multiple tags', () => {
const tags = {
env: 'production',
region: 'us-east-1',
service: 'qlik-sense',
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledTimes(3);
expect(mockPoint.setTag).toHaveBeenCalledWith('env', 'production');
expect(mockPoint.setTag).toHaveBeenCalledWith('region', 'us-east-1');
expect(mockPoint.setTag).toHaveBeenCalledWith('service', 'qlik-sense');
});
test('should convert non-string values to strings', () => {
const tags = {
count: 42,
enabled: true,
version: 3.14,
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledWith('count', '42');
expect(mockPoint.setTag).toHaveBeenCalledWith('enabled', 'true');
expect(mockPoint.setTag).toHaveBeenCalledWith('version', '3.14');
});
test('should skip null values', () => {
const tags = {
env: 'production',
region: null,
service: 'qlik-sense',
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledTimes(2);
expect(mockPoint.setTag).toHaveBeenCalledWith('env', 'production');
expect(mockPoint.setTag).toHaveBeenCalledWith('service', 'qlik-sense');
expect(mockPoint.setTag).not.toHaveBeenCalledWith('region', expect.anything());
});
test('should skip undefined values', () => {
const tags = {
env: 'production',
region: undefined,
service: 'qlik-sense',
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledTimes(2);
expect(mockPoint.setTag).toHaveBeenCalledWith('env', 'production');
expect(mockPoint.setTag).toHaveBeenCalledWith('service', 'qlik-sense');
});
test('should handle empty object', () => {
const tags = {};
const result = utils.applyTagsToPoint3(mockPoint, tags);
expect(result).toBe(mockPoint);
expect(mockPoint.setTag).not.toHaveBeenCalled();
});
test('should handle tags with special characters', () => {
const tags = {
'tag-with-dash': 'value',
tag_with_underscore: 'value2',
'tag.with.dot': 'value3',
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledTimes(3);
expect(mockPoint.setTag).toHaveBeenCalledWith('tag-with-dash', 'value');
expect(mockPoint.setTag).toHaveBeenCalledWith('tag_with_underscore', 'value2');
expect(mockPoint.setTag).toHaveBeenCalledWith('tag.with.dot', 'value3');
});
test('should handle empty string values', () => {
const tags = {
env: '',
region: 'us-east-1',
};
utils.applyTagsToPoint3(mockPoint, tags);
expect(mockPoint.setTag).toHaveBeenCalledWith('env', '');
expect(mockPoint.setTag).toHaveBeenCalledWith('region', 'us-east-1');
});
});