Initial commit of massive v2 rewrite
This commit is contained in:
parent
1025f3b523
commit
dc120fe78a
55 changed files with 21733 additions and 0 deletions
623
.tests/index.test.js
Normal file
623
.tests/index.test.js
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
import { jest } from '@jest/globals';
|
||||
|
||||
// Mock all external dependencies before importing the module
|
||||
const mockMkdir = jest.fn();
|
||||
const mockReadFile = jest.fn();
|
||||
const mockWriteFileSync = jest.fn();
|
||||
const mockReadFileSync = jest.fn();
|
||||
const mockUnlinkSync = jest.fn();
|
||||
const mockExistsSync = jest.fn();
|
||||
const mockReaddirSync = jest.fn();
|
||||
|
||||
jest.unstable_mockModule('fs/promises', () => ({
|
||||
mkdir: mockMkdir,
|
||||
readFile: mockReadFile
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('fs', () => ({
|
||||
writeFileSync: mockWriteFileSync,
|
||||
readFileSync: mockReadFileSync,
|
||||
unlinkSync: mockUnlinkSync,
|
||||
existsSync: mockExistsSync,
|
||||
readdirSync: mockReaddirSync
|
||||
}));
|
||||
|
||||
const mockJoin = jest.fn();
|
||||
const mockDirname = jest.fn();
|
||||
const mockBasename = jest.fn();
|
||||
|
||||
jest.unstable_mockModule('path', () => ({
|
||||
join: mockJoin,
|
||||
dirname: mockDirname,
|
||||
basename: mockBasename
|
||||
}));
|
||||
|
||||
const mockFileURLToPath = jest.fn();
|
||||
jest.unstable_mockModule('url', () => ({
|
||||
fileURLToPath: mockFileURLToPath
|
||||
}));
|
||||
|
||||
const mockSecureImportModule = jest.fn();
|
||||
jest.unstable_mockModule('../dist/utils/plugins.js', () => ({
|
||||
secureImportModule: mockSecureImportModule
|
||||
}));
|
||||
|
||||
const mockLogs = {
|
||||
section: jest.fn(),
|
||||
init: jest.fn(),
|
||||
config: jest.fn(),
|
||||
error: jest.fn(),
|
||||
msg: jest.fn(),
|
||||
server: jest.fn()
|
||||
};
|
||||
jest.unstable_mockModule('../dist/utils/logs.js', () => mockLogs);
|
||||
|
||||
const mockApp = {
|
||||
set: jest.fn(),
|
||||
use: jest.fn(),
|
||||
listen: jest.fn()
|
||||
};
|
||||
|
||||
const mockExpress = jest.fn(() => mockApp);
|
||||
mockExpress.json = jest.fn(() => (req, res, next) => next());
|
||||
mockExpress.urlencoded = jest.fn(() => (req, res, next) => next());
|
||||
mockExpress.static = jest.fn(() => (req, res, next) => next());
|
||||
mockExpress.Router = jest.fn(() => ({
|
||||
use: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('express', () => ({
|
||||
default: mockExpress
|
||||
}));
|
||||
|
||||
const mockServer = {
|
||||
on: jest.fn(),
|
||||
close: jest.fn(),
|
||||
listen: jest.fn()
|
||||
};
|
||||
|
||||
const mockCreateServer = jest.fn(() => mockServer);
|
||||
jest.unstable_mockModule('http', () => ({
|
||||
createServer: mockCreateServer
|
||||
}));
|
||||
|
||||
const mockSpawn = jest.fn();
|
||||
jest.unstable_mockModule('child_process', () => ({
|
||||
spawn: mockSpawn
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('dotenv', () => ({
|
||||
config: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock process for command line argument testing
|
||||
const originalArgv = process.argv;
|
||||
const originalEnv = process.env;
|
||||
const originalExit = process.exit;
|
||||
const originalKill = process.kill;
|
||||
const originalOn = process.on;
|
||||
|
||||
// Mock imports properly for ES modules
|
||||
const mockTomlModule = {
|
||||
parse: jest.fn(() => ({}))
|
||||
};
|
||||
|
||||
jest.unstable_mockModule('@iarna/toml', () => ({
|
||||
default: mockTomlModule
|
||||
}));
|
||||
|
||||
// Import the module after all mocking is set up
|
||||
let indexModule;
|
||||
|
||||
describe('Index (Main Application)', () => {
|
||||
let mockProcess;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Import the module once with proper mocking setup
|
||||
indexModule = await import('../dist/index.js');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Setup default mock returns
|
||||
mockFileURLToPath.mockReturnValue('/app/src/index.js');
|
||||
mockDirname.mockReturnValue('/app/src');
|
||||
mockJoin.mockImplementation((...parts) => parts.join('/'));
|
||||
mockBasename.mockImplementation((path, ext) => {
|
||||
const name = path.split('/').pop();
|
||||
return ext ? name.replace(ext, '') : name;
|
||||
});
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
mockReaddirSync.mockReturnValue([]);
|
||||
mockReadFile.mockResolvedValue('');
|
||||
mockTomlModule.parse.mockReturnValue({});
|
||||
|
||||
// Setup process mocking
|
||||
mockProcess = {
|
||||
argv: ['node', 'index.js'],
|
||||
env: { ...originalEnv },
|
||||
exit: jest.fn(),
|
||||
kill: jest.fn(),
|
||||
on: jest.fn()
|
||||
};
|
||||
|
||||
// Note: Plugin registry persists across tests in ES modules
|
||||
// This is expected behavior in the test environment
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv;
|
||||
process.env = originalEnv;
|
||||
process.exit = originalExit;
|
||||
process.kill = originalKill;
|
||||
process.on = originalOn;
|
||||
});
|
||||
|
||||
describe('Plugin Registration System', () => {
|
||||
test('should register plugin successfully', () => {
|
||||
const pluginName = 'test-plugin';
|
||||
const handler = { middleware: jest.fn() };
|
||||
|
||||
indexModule.registerPlugin(pluginName, handler);
|
||||
|
||||
const registeredNames = indexModule.getRegisteredPluginNames();
|
||||
expect(registeredNames).toContain(pluginName);
|
||||
});
|
||||
|
||||
test('should throw error for invalid plugin name', () => {
|
||||
expect(() => {
|
||||
indexModule.registerPlugin('', { middleware: jest.fn() });
|
||||
}).toThrow('Plugin name must be a non-empty string');
|
||||
|
||||
expect(() => {
|
||||
indexModule.registerPlugin(null, { middleware: jest.fn() });
|
||||
}).toThrow('Plugin name must be a non-empty string');
|
||||
});
|
||||
|
||||
test('should throw error for invalid handler', () => {
|
||||
expect(() => {
|
||||
indexModule.registerPlugin('test', null);
|
||||
}).toThrow('Plugin handler must be an object');
|
||||
|
||||
expect(() => {
|
||||
indexModule.registerPlugin('test', 'invalid');
|
||||
}).toThrow('Plugin handler must be an object');
|
||||
});
|
||||
|
||||
test('should prevent duplicate plugin registration', () => {
|
||||
const pluginName = 'duplicate-plugin';
|
||||
const handler = { middleware: jest.fn() };
|
||||
|
||||
indexModule.registerPlugin(pluginName, handler);
|
||||
|
||||
expect(() => {
|
||||
indexModule.registerPlugin(pluginName, handler);
|
||||
}).toThrow(`Plugin 'duplicate-plugin' is already registered`);
|
||||
});
|
||||
|
||||
test('should load plugins in registration order', () => {
|
||||
const handler1 = { middleware: jest.fn() };
|
||||
const handler2 = { middleware: jest.fn() };
|
||||
|
||||
indexModule.registerPlugin('order-test-1', handler1);
|
||||
indexModule.registerPlugin('order-test-2', handler2);
|
||||
|
||||
const handlers = indexModule.loadPlugins();
|
||||
const registeredNames = indexModule.getRegisteredPluginNames();
|
||||
|
||||
// Check that our test plugins are in the registry
|
||||
expect(registeredNames).toContain('order-test-1');
|
||||
expect(registeredNames).toContain('order-test-2');
|
||||
|
||||
// Check that our handlers are in the handlers array
|
||||
expect(handlers).toContain(handler1);
|
||||
expect(handlers).toContain(handler2);
|
||||
|
||||
// Find the indices of our test handlers
|
||||
const handler1Index = handlers.indexOf(handler1);
|
||||
const handler2Index = handlers.indexOf(handler2);
|
||||
|
||||
// Verify order (handler1 should come before handler2)
|
||||
expect(handler1Index).toBeLessThan(handler2Index);
|
||||
});
|
||||
|
||||
test('should freeze plugin registry', () => {
|
||||
const handler = { middleware: jest.fn() };
|
||||
indexModule.registerPlugin('before-freeze', handler);
|
||||
|
||||
indexModule.freezePlugins();
|
||||
|
||||
expect(mockLogs.msg).toHaveBeenCalledWith('Plugin registration frozen');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration Loading', () => {
|
||||
test('should load config successfully', async () => {
|
||||
const configName = 'test-config';
|
||||
const target = {};
|
||||
const configData = { setting1: 'value1' };
|
||||
|
||||
mockReadFile.mockResolvedValue('setting1 = "value1"');
|
||||
mockJoin.mockReturnValue('/app/src/config/test-config.toml');
|
||||
|
||||
// Mock the TOML parser
|
||||
const mockTomlModule = {
|
||||
default: {
|
||||
parse: jest.fn(() => configData)
|
||||
}
|
||||
};
|
||||
jest.unstable_mockModule('@iarna/toml', () => mockTomlModule);
|
||||
|
||||
await indexModule.loadConfig(configName, target);
|
||||
|
||||
expect(mockReadFile).toHaveBeenCalledWith('/app/src/config/test-config.toml', 'utf8');
|
||||
expect(target).toEqual(configData);
|
||||
});
|
||||
|
||||
test('should throw error for invalid config name', async () => {
|
||||
await expect(indexModule.loadConfig('', {})).rejects.toThrow('Config name must be a non-empty string');
|
||||
await expect(indexModule.loadConfig(null, {})).rejects.toThrow('Config name must be a non-empty string');
|
||||
});
|
||||
|
||||
test('should throw error for invalid target', async () => {
|
||||
await expect(indexModule.loadConfig('test', null)).rejects.toThrow('Config target must be an object');
|
||||
await expect(indexModule.loadConfig('test', 'invalid')).rejects.toThrow('Config target must be an object');
|
||||
});
|
||||
|
||||
test('should handle config loading errors', async () => {
|
||||
const configName = 'failing-config';
|
||||
const target = {};
|
||||
|
||||
mockReadFile.mockRejectedValue(new Error('File not found'));
|
||||
|
||||
await expect(indexModule.loadConfig(configName, target)).rejects.toThrow(
|
||||
"Failed to load config 'failing-config': File not found"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Line Argument Handling', () => {
|
||||
test('should validate PID file operations for kill mode', () => {
|
||||
// Test the logic for handling kill mode operations
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
mockReadFileSync.mockReturnValue('1234');
|
||||
mockJoin.mockReturnValue('/app/src/checkpoint.pid');
|
||||
|
||||
const pidContent = mockReadFileSync('checkpoint.pid', 'utf8').trim();
|
||||
const pid = parseInt(pidContent, 10);
|
||||
|
||||
expect(pid).toBe(1234);
|
||||
expect(isNaN(pid)).toBe(false);
|
||||
expect(pid > 0).toBe(true);
|
||||
});
|
||||
|
||||
test('should validate invalid PID handling', () => {
|
||||
mockReadFileSync.mockReturnValue('invalid');
|
||||
|
||||
const pidContent = mockReadFileSync('checkpoint.pid', 'utf8').trim();
|
||||
const pid = parseInt(pidContent, 10);
|
||||
|
||||
expect(isNaN(pid)).toBe(true);
|
||||
});
|
||||
|
||||
test('should validate daemon spawn parameters', () => {
|
||||
const mockChildProcess = {
|
||||
pid: 5678,
|
||||
unref: jest.fn()
|
||||
};
|
||||
mockSpawn.mockReturnValue(mockChildProcess);
|
||||
|
||||
const args = ['index.js'];
|
||||
const nodeExecutable = 'node';
|
||||
|
||||
mockSpawn(nodeExecutable, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
'node',
|
||||
['index.js'],
|
||||
expect.objectContaining({
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Directory Initialization', () => {
|
||||
test('should validate directory creation logic', async () => {
|
||||
mockMkdir.mockResolvedValue(undefined);
|
||||
|
||||
const expectedDirs = [
|
||||
'/app/src/data',
|
||||
'/app/src/db',
|
||||
'/app/src/config'
|
||||
];
|
||||
|
||||
// Test the directory creation logic
|
||||
for (const dirPath of expectedDirs) {
|
||||
mockJoin.mockReturnValueOnce(dirPath);
|
||||
await mockMkdir(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
expect(mockMkdir).toHaveBeenCalledTimes(3);
|
||||
expect(mockMkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Plugin Discovery', () => {
|
||||
test('should discover plugins in correct order', () => {
|
||||
mockExistsSync.mockImplementation(path => {
|
||||
return path.includes('plugins');
|
||||
});
|
||||
|
||||
mockReaddirSync.mockReturnValue([
|
||||
'waf.js',
|
||||
'basic-auth.js',
|
||||
'ipfilter.js',
|
||||
'proxy.js',
|
||||
'rate-limit.js'
|
||||
]);
|
||||
|
||||
mockBasename.mockImplementation((file, ext) =>
|
||||
file.replace(ext || '', '')
|
||||
);
|
||||
|
||||
// Plugin load order should be: ipfilter, waf, proxy, then alphabetical
|
||||
const expectedOrder = ['ipfilter', 'waf', 'proxy', 'basic-auth', 'rate-limit'];
|
||||
|
||||
expect(mockReaddirSync).toHaveBeenCalled;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Express Server Setup', () => {
|
||||
test('should validate Express app creation', () => {
|
||||
// Test Express app creation
|
||||
const app = mockExpress();
|
||||
expect(mockExpress).toHaveBeenCalled();
|
||||
expect(app).toBeDefined();
|
||||
expect(app.set).toBeDefined();
|
||||
expect(app.use).toBeDefined();
|
||||
});
|
||||
|
||||
test('should validate middleware configuration', () => {
|
||||
// Test middleware setup logic
|
||||
const app = mockExpress();
|
||||
|
||||
// Test trust proxy setting
|
||||
app.set('trust proxy', true);
|
||||
expect(app.set).toHaveBeenCalledWith('trust proxy', true);
|
||||
|
||||
// Test body parsing middleware
|
||||
const jsonMiddleware = mockExpress.json({ limit: '10mb' });
|
||||
const urlencodedMiddleware = mockExpress.urlencoded({ extended: true, limit: '10mb' });
|
||||
|
||||
expect(mockExpress.json).toHaveBeenCalledWith({ limit: '10mb' });
|
||||
expect(mockExpress.urlencoded).toHaveBeenCalledWith({ extended: true, limit: '10mb' });
|
||||
});
|
||||
|
||||
test('should handle WebSocket upgrade detection logic', () => {
|
||||
const mockReq = {
|
||||
headers: {
|
||||
upgrade: 'websocket',
|
||||
connection: 'Upgrade'
|
||||
}
|
||||
};
|
||||
const mockRes = {};
|
||||
const mockNext = jest.fn();
|
||||
|
||||
// Test WebSocket detection logic
|
||||
const upgradeHeader = mockReq.headers.upgrade;
|
||||
const connectionHeader = mockReq.headers.connection;
|
||||
|
||||
const isWebSocket = upgradeHeader === 'websocket' ||
|
||||
(connectionHeader && connectionHeader.toLowerCase().includes('upgrade'));
|
||||
|
||||
if (isWebSocket) {
|
||||
mockReq.isWebSocketRequest = true;
|
||||
mockNext();
|
||||
}
|
||||
|
||||
expect(mockReq.isWebSocketRequest).toBe(true);
|
||||
expect(mockNext).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Environment Configuration', () => {
|
||||
test('should handle production environment', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// In production, console.log should be disabled
|
||||
// This is handled at module load time
|
||||
expect(process.env.NODE_ENV).toBe('production');
|
||||
});
|
||||
|
||||
test('should handle custom port configuration', () => {
|
||||
process.env.PORT = '8080';
|
||||
|
||||
// Port validation would happen during server startup
|
||||
expect(process.env.PORT).toBe('8080');
|
||||
});
|
||||
|
||||
test('should use default port when not specified', () => {
|
||||
delete process.env.PORT;
|
||||
|
||||
// Default port should be 3000
|
||||
const expectedPort = 3000;
|
||||
expect(expectedPort).toBe(3000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
test('should validate plugin loading error handling', async () => {
|
||||
const error = new Error('Plugin load failed');
|
||||
mockSecureImportModule.mockRejectedValue(error);
|
||||
|
||||
// Test error handling logic
|
||||
try {
|
||||
await mockSecureImportModule('test-plugin.js');
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Plugin load failed');
|
||||
}
|
||||
|
||||
expect(mockSecureImportModule).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should validate config loading error handling', async () => {
|
||||
const error = new Error('Config read failed');
|
||||
mockReadFile.mockRejectedValue(error);
|
||||
|
||||
// Test config loading error handling
|
||||
try {
|
||||
await indexModule.loadConfig('test-config', {});
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('Failed to load config');
|
||||
expect(e.message).toContain('Config read failed');
|
||||
}
|
||||
});
|
||||
|
||||
test('should validate port number range', () => {
|
||||
const invalidPorts = [-1, 0, 65536, 70000];
|
||||
const validPorts = [80, 443, 3000, 8080, 65535];
|
||||
|
||||
invalidPorts.forEach(port => {
|
||||
expect(isNaN(Number(port)) || Number(port) < 1 || Number(port) > 65535).toBe(true);
|
||||
});
|
||||
|
||||
validPorts.forEach(port => {
|
||||
expect(Number(port) >= 1 && Number(port) <= 65535).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Exclusion Rules Processing', () => {
|
||||
test('should compile exclusion patterns correctly', () => {
|
||||
const exclusionRules = [
|
||||
{
|
||||
Path: '/api/health',
|
||||
Hosts: ['localhost', '127.0.0.1'],
|
||||
UserAgents: ['healthcheck.*']
|
||||
}
|
||||
];
|
||||
|
||||
// Test exclusion rule compilation logic
|
||||
const compiledRule = {
|
||||
...exclusionRules[0],
|
||||
pathStartsWith: exclusionRules[0].Path,
|
||||
hostsSet: new Set(exclusionRules[0].Hosts),
|
||||
userAgentPatterns: exclusionRules[0].UserAgents.map(pattern => new RegExp(pattern, 'i'))
|
||||
};
|
||||
|
||||
expect(compiledRule.hostsSet.has('localhost')).toBe(true);
|
||||
expect(compiledRule.userAgentPatterns[0].test('healthcheck-bot')).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle invalid regex patterns in UserAgents', () => {
|
||||
const invalidPattern = '[invalid regex';
|
||||
|
||||
try {
|
||||
new RegExp(invalidPattern, 'i');
|
||||
} catch (e) {
|
||||
// Should handle invalid regex gracefully
|
||||
const fallbackPattern = /(?!)/; // Never matches
|
||||
expect(fallbackPattern.test('anything')).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static File Middleware', () => {
|
||||
test('should validate static file setup for existing directories', () => {
|
||||
const webfontPath = '/app/src/pages/interstitial/webfont';
|
||||
const jsPath = '/app/src/pages/interstitial/js';
|
||||
|
||||
mockExistsSync.mockImplementation(path => {
|
||||
return path === webfontPath || path === jsPath;
|
||||
});
|
||||
|
||||
const router = mockExpress.Router();
|
||||
|
||||
// Test static directory setup logic
|
||||
if (mockExistsSync(webfontPath)) {
|
||||
router.use('/webfont', mockExpress.static(webfontPath, { maxAge: '7d' }));
|
||||
}
|
||||
|
||||
if (mockExistsSync(jsPath)) {
|
||||
router.use('/js', mockExpress.static(jsPath, { maxAge: '7d' }));
|
||||
}
|
||||
|
||||
expect(mockExistsSync).toHaveBeenCalledWith(webfontPath);
|
||||
expect(mockExistsSync).toHaveBeenCalledWith(jsPath);
|
||||
expect(mockExpress.static).toHaveBeenCalledWith(webfontPath, { maxAge: '7d' });
|
||||
expect(mockExpress.static).toHaveBeenCalledWith(jsPath, { maxAge: '7d' });
|
||||
});
|
||||
|
||||
test('should skip static setup for non-existent directories', () => {
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
const webfontPath = '/app/src/pages/interstitial/webfont';
|
||||
const jsPath = '/app/src/pages/interstitial/js';
|
||||
|
||||
// Test logic when directories don't exist
|
||||
const webfontExists = mockExistsSync(webfontPath);
|
||||
const jsExists = mockExistsSync(jsPath);
|
||||
|
||||
expect(webfontExists).toBe(false);
|
||||
expect(jsExists).toBe(false);
|
||||
expect(mockExistsSync).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Graceful Shutdown', () => {
|
||||
test('should validate shutdown handler logic', () => {
|
||||
const mockShutdownHandler = jest.fn();
|
||||
const activeSockets = new Set();
|
||||
const mockSocketInstance = {
|
||||
destroy: jest.fn(),
|
||||
setTimeout: jest.fn(),
|
||||
setKeepAlive: jest.fn(),
|
||||
on: jest.fn()
|
||||
};
|
||||
|
||||
activeSockets.add(mockSocketInstance);
|
||||
|
||||
// Test shutdown logic
|
||||
const isShuttingDown = false;
|
||||
if (!isShuttingDown) {
|
||||
activeSockets.forEach(sock => sock.destroy());
|
||||
mockShutdownHandler();
|
||||
}
|
||||
|
||||
expect(mockSocketInstance.destroy).toHaveBeenCalled();
|
||||
expect(mockShutdownHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should validate socket management', () => {
|
||||
const activeSockets = new Set();
|
||||
const mockSocket = {
|
||||
destroy: jest.fn(),
|
||||
setTimeout: jest.fn(),
|
||||
setKeepAlive: jest.fn(),
|
||||
on: jest.fn()
|
||||
};
|
||||
|
||||
// Test socket lifecycle
|
||||
activeSockets.add(mockSocket);
|
||||
expect(activeSockets.has(mockSocket)).toBe(true);
|
||||
|
||||
// Test socket configuration
|
||||
mockSocket.setTimeout(120000);
|
||||
mockSocket.setKeepAlive(true, 60000);
|
||||
|
||||
expect(mockSocket.setTimeout).toHaveBeenCalledWith(120000);
|
||||
expect(mockSocket.setKeepAlive).toHaveBeenCalledWith(true, 60000);
|
||||
|
||||
// Test cleanup
|
||||
activeSockets.delete(mockSocket);
|
||||
expect(activeSockets.has(mockSocket)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue