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-improvements-${Date.now()}.sock`); // Test 1: Server Readiness API tap.test('Server readiness API should emit ready event', async () => { const server = smartipc.SmartIpc.createServer({ id: 'test-server', socketPath: testSocketPath, autoCleanupSocketFile: true, heartbeat: false // Disable heartbeat for this test }); let readyEventFired = false; server.on('ready', () => { readyEventFired = true; }); await server.start({ readyWhen: 'accepting' }); expect(readyEventFired).toBeTrue(); expect(server.getIsReady()).toBeTrue(); await server.stop(); }); // Test 2: Automatic Socket Cleanup tap.test('Should cleanup stale socket file automatically', 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, heartbeat: false // Disable heartbeat for this test }); // Should clean up and start successfully await server.start(); expect(server.getIsReady()).toBeTrue(); await server.stop(); }); // Test 3: Basic Connection with New Options tap.test('Client should connect with basic configuration', async () => { const server = smartipc.SmartIpc.createServer({ id: 'test-server', socketPath: testSocketPath, autoCleanupSocketFile: true, heartbeat: false // Disable heartbeat for this test }); await server.start({ readyWhen: 'accepting' }); // Wait for server to be fully ready await new Promise(resolve => setTimeout(resolve, 200)); const client = smartipc.SmartIpc.createClient({ id: 'test-server', socketPath: testSocketPath, clientId: 'test-client', registerTimeoutMs: 10000 // Longer timeout }); await client.connect(); expect(client.getIsConnected()).toBeTrue(); await client.disconnect(); await server.stop(); }); // Test 4: Heartbeat Configuration Without Throwing tap.test('Heartbeat should use event mode instead of throwing', async () => { const server = smartipc.SmartIpc.createServer({ id: 'test-server', socketPath: testSocketPath, autoCleanupSocketFile: true, heartbeat: false // Disable server heartbeat for this test }); // Add error handler to prevent unhandled errors server.on('error', () => {}); await server.start({ readyWhen: 'accepting' }); await new Promise(resolve => setTimeout(resolve, 200)); const client = smartipc.SmartIpc.createClient({ id: 'test-server', socketPath: testSocketPath, clientId: 'heartbeat-client', heartbeat: true, heartbeatInterval: 100, heartbeatTimeout: 300, heartbeatInitialGracePeriodMs: 1000, heartbeatThrowOnTimeout: false // Don't throw, emit event }); let heartbeatTimeoutFired = false; client.on('heartbeatTimeout', () => { heartbeatTimeoutFired = true; }); client.on('error', () => {}); await client.connect(); expect(client.getIsConnected()).toBeTrue(); // Wait a bit but within grace period await new Promise(resolve => setTimeout(resolve, 500)); // Should still be connected, no timeout during grace period expect(heartbeatTimeoutFired).toBeFalse(); expect(client.getIsConnected()).toBeTrue(); await client.disconnect(); await server.stop(); }); // Test 5: Wait for Server Helper tap.test('waitForServer should detect when server becomes ready', async () => { const server = smartipc.SmartIpc.createServer({ id: 'test-server', socketPath: testSocketPath, autoCleanupSocketFile: true, heartbeat: false // Disable heartbeat for this test }); // Start server after delay setTimeout(async () => { await server.start(); }, 200); // Wait for server should succeed await smartipc.SmartIpc.waitForServer({ socketPath: testSocketPath, timeoutMs: 3000 }); // Server should be ready now const client = smartipc.SmartIpc.createClient({ id: 'test-server', socketPath: testSocketPath, clientId: 'wait-test-client' }); await client.connect(); expect(client.getIsConnected()).toBeTrue(); await client.disconnect(); await server.stop(); }); // Test 6: Connect Retry Configuration tap.test('Client retry should work with delayed server', async () => { const server = smartipc.SmartIpc.createServer({ id: 'test-server', socketPath: testSocketPath, autoCleanupSocketFile: true, heartbeat: false // Disable heartbeat for this test }); const client = smartipc.SmartIpc.createClient({ id: 'test-server', socketPath: testSocketPath, clientId: 'retry-client', connectRetry: { enabled: true, initialDelay: 100, maxDelay: 500, maxAttempts: 10, totalTimeout: 5000 } }); // Start server after a delay setTimeout(async () => { await server.start({ readyWhen: 'accepting' }); }, 300); // Client should retry and eventually connect await client.connect({ waitForReady: true, waitTimeout: 5000 }); expect(client.getIsConnected()).toBeTrue(); await client.disconnect(); await server.stop(); }); // Test 7: clientOnly prevents client from auto-starting a server tap.test('clientOnly should prevent auto-start and fail fast', async () => { const uniqueSocketPath = path.join(os.tmpdir(), `smartipc-clientonly-${Date.now()}.sock`); const client = smartipc.SmartIpc.createClient({ id: 'clientonly-test', socketPath: uniqueSocketPath, clientId: 'co-client-1', clientOnly: true, connectRetry: { enabled: false } }); let failed = false; try { await client.connect(); } catch (err: any) { failed = true; expect(err.message).toContain('clientOnly prevents auto-start'); } expect(failed).toBeTrue(); // Ensure no server-side socket was created expect(fs.existsSync(uniqueSocketPath)).toBeFalse(); }); // Test 8: env SMARTIPC_CLIENT_ONLY enforces clientOnly behavior tap.test('SMARTIPC_CLIENT_ONLY=1 should enforce clientOnly', async () => { const uniqueSocketPath = path.join(os.tmpdir(), `smartipc-clientonly-env-${Date.now()}.sock`); const prev = process.env.SMARTIPC_CLIENT_ONLY; process.env.SMARTIPC_CLIENT_ONLY = '1'; const client = smartipc.SmartIpc.createClient({ id: 'clientonly-test-env', socketPath: uniqueSocketPath, clientId: 'co-client-2', connectRetry: { enabled: false } }); let failed = false; try { await client.connect(); } catch (err: any) { failed = true; expect(err.message).toContain('clientOnly prevents auto-start'); } expect(failed).toBeTrue(); expect(fs.existsSync(uniqueSocketPath)).toBeFalse(); // restore env if (prev === undefined) { delete process.env.SMARTIPC_CLIENT_ONLY; } else { process.env.SMARTIPC_CLIENT_ONLY = prev; } }); // Cleanup tap.test('Cleanup test socket', async () => { try { fs.unlinkSync(testSocketPath); } catch (e) { // Ignore if doesn't exist } }); export default tap.start();