Files
smartipc/test/test.reliability.ts

286 lines
7.1 KiB
TypeScript
Raw Normal View History

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartipc from '../ts/index.js';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
const testSocketPath = path.join(os.tmpdir(), `test-ipc-reliability-${Date.now()}.sock`);
tap.test('Server Readiness API', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'test-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true
});
let readyEventFired = false;
server.on('ready', () => {
readyEventFired = true;
});
// Start server with 'accepting' readiness mode
await server.start({ readyWhen: 'accepting' });
// Check that ready event was fired
expect(readyEventFired).toBeTrue();
expect(server.getIsReady()).toBeTrue();
await server.stop();
});
tap.test('Automatic Socket Cleanup', async () => {
// Create a stale socket file
fs.writeFileSync(testSocketPath, '');
expect(fs.existsSync(testSocketPath)).toBeTrue();
const server = smartipc.SmartIpc.createServer({
id: 'test-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true,
socketMode: 0o600
});
// Should clean up stale socket and start successfully
await server.start();
expect(server.getIsReady()).toBeTrue();
await server.stop();
});
tap.test('Client Connection Retry', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'retry-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true
});
// Create client with retry configuration
const client = smartipc.SmartIpc.createClient({
id: 'retry-client',
socketPath: testSocketPath,
connectRetry: {
enabled: true,
initialDelay: 50,
maxDelay: 500,
maxAttempts: 10,
totalTimeout: 5000
},
registerTimeoutMs: 3000
});
// Start server first with accepting readiness mode
await server.start({ readyWhen: 'accepting' });
// Give server a moment to be fully ready
await new Promise(resolve => setTimeout(resolve, 100));
// Client should connect successfully with retry enabled
await client.connect();
expect(client.getIsConnected()).toBeTrue();
await client.disconnect();
await server.stop();
});
tap.test('Graceful Heartbeat Handling', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'heartbeat-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true,
heartbeat: true,
heartbeatInterval: 100,
heartbeatTimeout: 500,
heartbeatInitialGracePeriodMs: 1000,
heartbeatThrowOnTimeout: false
});
// Add error handler to prevent unhandled error
server.on('error', (error) => {
// Ignore heartbeat errors in this test
});
await server.start({ readyWhen: 'accepting' });
// Give server a moment to be fully ready
await new Promise(resolve => setTimeout(resolve, 100));
const client = smartipc.SmartIpc.createClient({
id: 'heartbeat-client',
socketPath: testSocketPath,
heartbeat: true,
heartbeatInterval: 100,
heartbeatTimeout: 500,
heartbeatInitialGracePeriodMs: 1000,
heartbeatThrowOnTimeout: false
});
let heartbeatTimeoutFired = false;
client.on('heartbeatTimeout', () => {
heartbeatTimeoutFired = true;
});
// Add error handler to prevent unhandled error
client.on('error', (error) => {
// Ignore errors in this test
});
await client.connect();
expect(client.getIsConnected()).toBeTrue();
// Wait to ensure heartbeat is working
await new Promise(resolve => setTimeout(resolve, 300));
// Heartbeat should not timeout during normal operation
expect(heartbeatTimeoutFired).toBeFalse();
await client.disconnect();
await server.stop();
});
tap.test('Test Helper - waitForServer', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'wait-test-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true
});
// Start server after a delay
setTimeout(() => {
server.start();
}, 100);
// Wait for server should succeed
await smartipc.SmartIpc.waitForServer({
socketPath: testSocketPath,
timeoutMs: 3000
});
// Server should be ready
const client = smartipc.SmartIpc.createClient({
id: 'wait-test-client',
socketPath: testSocketPath
});
await client.connect();
expect(client.getIsConnected()).toBeTrue();
await client.disconnect();
await server.stop();
});
tap.test('Race Condition - Immediate Connect After Server Start', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'race-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true
});
// Start server and immediately try to connect
const serverPromise = server.start({ readyWhen: 'accepting' });
const client = smartipc.SmartIpc.createClient({
id: 'race-client',
socketPath: testSocketPath,
connectRetry: {
enabled: true,
maxAttempts: 20,
initialDelay: 10,
maxDelay: 100
},
registerTimeoutMs: 5000
});
// Wait for server to be ready
await serverPromise;
// Client should be able to connect without race condition
await client.connect();
expect(client.getIsConnected()).toBeTrue();
// Test request/response to ensure full functionality
server.onMessage('test', async (data) => {
return { echo: data };
});
const response = await client.request('test', { message: 'hello' });
expect(response.echo.message).toEqual('hello');
await client.disconnect();
await server.stop();
});
tap.test('Multiple Clients with Retry', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'multi-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true,
maxClients: 10
});
await server.start({ readyWhen: 'accepting' });
// Create multiple clients with retry
const clients = [];
for (let i = 0; i < 5; i++) {
const client = smartipc.SmartIpc.createClient({
id: `client-${i}`,
socketPath: testSocketPath,
connectRetry: {
enabled: true,
maxAttempts: 5
}
});
clients.push(client);
}
// Connect all clients concurrently
await Promise.all(clients.map(c => c.connect()));
// Verify all connected
for (const client of clients) {
expect(client.getIsConnected()).toBeTrue();
}
// Disconnect all
await Promise.all(clients.map(c => c.disconnect()));
await server.stop();
});
tap.test('Server Restart with Socket Cleanup', async () => {
const server = smartipc.SmartIpc.createServer({
id: 'restart-server',
socketPath: testSocketPath,
autoCleanupSocketFile: true
});
// First start
await server.start();
expect(server.getIsReady()).toBeTrue();
await server.stop();
// Second start - should cleanup and work
await server.start();
expect(server.getIsReady()).toBeTrue();
const client = smartipc.SmartIpc.createClient({
id: 'restart-client',
socketPath: testSocketPath
});
await client.connect();
expect(client.getIsConnected()).toBeTrue();
await client.disconnect();
await server.stop();
});
// Clean up test socket file
tap.test('Cleanup', async () => {
try {
fs.unlinkSync(testSocketPath);
} catch (e) {
// Ignore if doesn't exist
}
});
export default tap.start();