Files
tspm/test/test.integration.ts

275 lines
8.0 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as tspm from '../ts/index.js';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
import { spawn } from 'child_process';
import { tspmIpcClient } from '../ts/classes.ipcclient.js';
// Helper to ensure daemon is stopped before tests
async function ensureDaemonStopped() {
try {
await tspmIpcClient.stopDaemon(false);
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
// Ignore errors if daemon is not running
}
}
// Helper to clean up test files
async function cleanupTestFiles() {
const tspmDir = path.join(os.homedir(), '.tspm');
const pidFile = path.join(tspmDir, 'daemon.pid');
const socketFile = path.join(tspmDir, 'tspm.sock');
await fs.unlink(pidFile).catch(() => {});
await fs.unlink(socketFile).catch(() => {});
}
// Integration tests for daemon-client communication
tap.test('Full daemon lifecycle test', async (tools) => {
const done = tools.defer();
// Ensure clean state
await ensureDaemonStopped();
await cleanupTestFiles();
// Test 1: Check daemon is not running
let status = await tspmIpcClient.getDaemonStatus();
expect(status).toEqual(null);
// Test 2: Start daemon
console.log('Starting daemon...');
await tspmIpcClient.connect();
// Give daemon time to fully initialize
await new Promise((resolve) => setTimeout(resolve, 2000));
// Test 3: Check daemon is running
status = await tspmIpcClient.getDaemonStatus();
expect(status).toBeDefined();
expect(status?.status).toEqual('running');
expect(status?.pid).toBeGreaterThan(0);
expect(status?.processCount).toBeGreaterThanOrEqual(0);
// Test 4: Stop daemon
console.log('Stopping daemon...');
await tspmIpcClient.stopDaemon(true);
// Give daemon time to shutdown
await new Promise((resolve) => setTimeout(resolve, 2000));
// Test 5: Check daemon is stopped
status = await tspmIpcClient.getDaemonStatus();
expect(status).toEqual(null);
done.resolve();
});
tap.test('Process management through daemon', async (tools) => {
const done = tools.defer();
// Ensure daemon is running
await tspmIpcClient.connect();
await new Promise((resolve) => setTimeout(resolve, 1000));
// Test 1: List processes (should be empty initially)
let listResponse = await tspmIpcClient.request('list', {});
expect(listResponse.processes).toBeArray();
expect(listResponse.processes.length).toEqual(0);
// Test 2: Start a test process
const testConfig: tspm.IProcessConfig = {
id: 'test-echo',
name: 'Test Echo Process',
command: 'echo "Test process"',
projectDir: process.cwd(),
memoryLimitBytes: 50 * 1024 * 1024,
autorestart: false,
};
const startResponse = await tspmIpcClient.request('start', {
config: testConfig,
});
expect(startResponse.processId).toEqual('test-echo');
expect(startResponse.status).toBeDefined();
// Test 3: List processes (should have one process)
listResponse = await tspmIpcClient.request('list', {});
expect(listResponse.processes.length).toBeGreaterThanOrEqual(1);
const process = listResponse.processes.find((p) => p.id === 'test-echo');
expect(process).toBeDefined();
expect(process?.id).toEqual('test-echo');
// Test 4: Describe the process
const describeResponse = await tspmIpcClient.request('describe', {
id: 'test-echo',
});
expect(describeResponse.processInfo).toBeDefined();
expect(describeResponse.config).toBeDefined();
expect(describeResponse.config.id).toEqual('test-echo');
// Test 5: Stop the process
const stopResponse = await tspmIpcClient.request('stop', { id: 'test-echo' });
expect(stopResponse.success).toEqual(true);
expect(stopResponse.message).toInclude('stopped successfully');
// Test 6: Delete the process
const deleteResponse = await tspmIpcClient.request('delete', {
id: 'test-echo',
});
expect(deleteResponse.success).toEqual(true);
// Test 7: Verify process is gone
listResponse = await tspmIpcClient.request('list', {});
const deletedProcess = listResponse.processes.find(
(p) => p.id === 'test-echo',
);
expect(deletedProcess).toBeUndefined();
// Cleanup: stop daemon
await tspmIpcClient.stopDaemon(true);
done.resolve();
});
tap.test('Batch operations through daemon', async (tools) => {
const done = tools.defer();
// Ensure daemon is running
await tspmIpcClient.connect();
await new Promise((resolve) => setTimeout(resolve, 1000));
// Add multiple test processes
const testConfigs: tspm.IProcessConfig[] = [
{
id: 'batch-test-1',
name: 'Batch Test 1',
command: 'echo "Process 1"',
projectDir: process.cwd(),
memoryLimitBytes: 50 * 1024 * 1024,
autorestart: false,
},
{
id: 'batch-test-2',
name: 'Batch Test 2',
command: 'echo "Process 2"',
projectDir: process.cwd(),
memoryLimitBytes: 50 * 1024 * 1024,
autorestart: false,
},
];
// Start processes
for (const config of testConfigs) {
await tspmIpcClient.request('start', { config });
}
// Test 1: Stop all processes
const stopAllResponse = await tspmIpcClient.request('stopAll', {});
expect(stopAllResponse.stopped).toBeArray();
expect(stopAllResponse.stopped.length).toBeGreaterThanOrEqual(2);
// Test 2: Start all processes
const startAllResponse = await tspmIpcClient.request('startAll', {});
expect(startAllResponse.started).toBeArray();
// Test 3: Restart all processes
const restartAllResponse = await tspmIpcClient.request('restartAll', {});
expect(restartAllResponse.restarted).toBeArray();
// Cleanup: delete all test processes
for (const config of testConfigs) {
await tspmIpcClient.request('delete', { id: config.id }).catch(() => {});
}
// Stop daemon
await tspmIpcClient.stopDaemon(true);
done.resolve();
});
tap.test('Daemon error handling', async (tools) => {
const done = tools.defer();
// Ensure daemon is running
await tspmIpcClient.connect();
await new Promise((resolve) => setTimeout(resolve, 1000));
// Test 1: Try to stop non-existent process
try {
await tspmIpcClient.request('stop', { id: 'non-existent-process' });
expect(false).toEqual(true); // Should not reach here
} catch (error) {
expect(error.message).toInclude('Failed to stop process');
}
// Test 2: Try to describe non-existent process
try {
await tspmIpcClient.request('describe', { id: 'non-existent-process' });
expect(false).toEqual(true); // Should not reach here
} catch (error) {
expect(error.message).toInclude('not found');
}
// Test 3: Try to restart non-existent process
try {
await tspmIpcClient.request('restart', { id: 'non-existent-process' });
expect(false).toEqual(true); // Should not reach here
} catch (error) {
expect(error.message).toInclude('Failed to restart process');
}
// Stop daemon
await tspmIpcClient.stopDaemon(true);
done.resolve();
});
tap.test('Daemon heartbeat functionality', async (tools) => {
const done = tools.defer();
// Ensure daemon is running
await tspmIpcClient.connect();
await new Promise((resolve) => setTimeout(resolve, 1000));
// Test heartbeat
const heartbeatResponse = await tspmIpcClient.request('heartbeat', {});
expect(heartbeatResponse.timestamp).toBeGreaterThan(0);
expect(heartbeatResponse.status).toEqual('healthy');
// Stop daemon
await tspmIpcClient.stopDaemon(true);
done.resolve();
});
tap.test('Daemon memory and CPU reporting', async (tools) => {
const done = tools.defer();
// Ensure daemon is running
await tspmIpcClient.connect();
await new Promise((resolve) => setTimeout(resolve, 1000));
// Get daemon status
const status = await tspmIpcClient.getDaemonStatus();
expect(status).toBeDefined();
expect(status?.memoryUsage).toBeGreaterThan(0);
expect(status?.cpuUsage).toBeGreaterThanOrEqual(0);
expect(status?.uptime).toBeGreaterThan(0);
// Stop daemon
await tspmIpcClient.stopDaemon(true);
done.resolve();
});
// Cleanup after all tests
tap.test('Final cleanup', async () => {
await ensureDaemonStopped();
await cleanupTestFiles();
});
export default tap.start();