mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-04 20:17:24 +00:00
Merge pull request #1324 from kaitranntt/kai/fix/dashboard-cliproxy-restart-button
fix: ensure dashboard cliproxy restart waits for recovery
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import {
|
||||
restartDashboardCliproxy,
|
||||
type CliproxyDashboardRestartDeps,
|
||||
} from '../../../src/web-server/services/cliproxy-dashboard-restart-service';
|
||||
|
||||
function createDeps(overrides: Partial<CliproxyDashboardRestartDeps> = {}) {
|
||||
const calls: string[] = [];
|
||||
const deps: CliproxyDashboardRestartDeps = {
|
||||
resolveLifecyclePortFn: () => 8317,
|
||||
isRunningUnderSupervisordFn: () => false,
|
||||
restartCliproxyViaSupervisordFn: () => {
|
||||
calls.push('supervisor-restart');
|
||||
return { success: true, port: 8317 };
|
||||
},
|
||||
waitForProxyHealthyFn: async () => {
|
||||
calls.push('wait-healthy');
|
||||
return true;
|
||||
},
|
||||
stopProxyFn: async () => {
|
||||
calls.push('stop');
|
||||
return { stopped: true, port: 8317, pid: 1234, sessionCount: 1 };
|
||||
},
|
||||
ensureCliproxyServiceFn: async () => {
|
||||
calls.push('start');
|
||||
return { started: true, alreadyRunning: false, port: 8317 };
|
||||
},
|
||||
delayFn: async () => {
|
||||
calls.push('delay');
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return { calls, deps };
|
||||
}
|
||||
|
||||
describe('cliproxy dashboard restart service', () => {
|
||||
it('performs a local restart as stop then start, not stop only', async () => {
|
||||
const { calls, deps } = createDeps();
|
||||
|
||||
const result = await restartDashboardCliproxy(deps);
|
||||
|
||||
expect(result).toEqual({ success: true, port: 8317 });
|
||||
expect(calls).toEqual(['stop', 'delay', 'start']);
|
||||
});
|
||||
|
||||
it('starts a local instance even when stop finds no active session', async () => {
|
||||
const { calls, deps } = createDeps({
|
||||
stopProxyFn: async () => {
|
||||
calls.push('stop');
|
||||
return { stopped: false, port: 8317, error: 'No active CLIProxy session found' };
|
||||
},
|
||||
});
|
||||
|
||||
const result = await restartDashboardCliproxy(deps);
|
||||
|
||||
expect(result).toEqual({ success: true, port: 8317 });
|
||||
expect(calls).toEqual(['stop', 'delay', 'start']);
|
||||
});
|
||||
|
||||
it('returns failure when local restart cannot start after stopping', async () => {
|
||||
const { calls, deps } = createDeps({
|
||||
ensureCliproxyServiceFn: async () => {
|
||||
calls.push('start');
|
||||
return { started: false, alreadyRunning: false, port: 8317, error: 'port blocked' };
|
||||
},
|
||||
});
|
||||
|
||||
const result = await restartDashboardCliproxy(deps);
|
||||
|
||||
expect(result).toEqual({ success: false, error: 'port blocked' });
|
||||
expect(calls).toEqual(['stop', 'delay', 'start']);
|
||||
});
|
||||
|
||||
it('waits for Docker supervisor restart to become healthy before reporting success', async () => {
|
||||
const { calls, deps } = createDeps({
|
||||
isRunningUnderSupervisordFn: () => true,
|
||||
});
|
||||
|
||||
const result = await restartDashboardCliproxy(deps);
|
||||
|
||||
expect(result).toEqual({ success: true, port: 8317 });
|
||||
expect(calls).toEqual(['supervisor-restart', 'wait-healthy']);
|
||||
});
|
||||
|
||||
it('reports Docker supervisor restart as failed when the proxy never becomes healthy', async () => {
|
||||
const { calls, deps } = createDeps({
|
||||
isRunningUnderSupervisordFn: () => true,
|
||||
waitForProxyHealthyFn: async () => {
|
||||
calls.push('wait-healthy');
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
const result = await restartDashboardCliproxy(deps);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'CLIProxy did not become healthy after supervisor restart',
|
||||
});
|
||||
expect(calls).toEqual(['supervisor-restart', 'wait-healthy']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||
import express from 'express';
|
||||
import type { Server } from 'http';
|
||||
import { registerCliproxyRestartRoute } from '../../../src/web-server/routes/cliproxy-stats-routes';
|
||||
|
||||
describe('cliproxy stats routes restart endpoint', () => {
|
||||
let server: Server;
|
||||
let baseUrl = '';
|
||||
let restartMock: ReturnType<typeof mock>;
|
||||
|
||||
beforeEach(async () => {
|
||||
restartMock = mock(async () => ({ success: true, port: 8317 }));
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
const restartRouter = express.Router();
|
||||
registerCliproxyRestartRoute(restartRouter, restartMock);
|
||||
app.use('/api/cliproxy', restartRouter);
|
||||
|
||||
server = await new Promise<Server>((resolve, reject) => {
|
||||
const instance = app.listen(0, '127.0.0.1');
|
||||
instance.once('error', reject);
|
||||
instance.once('listening', () => resolve(instance));
|
||||
});
|
||||
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
throw new Error('Unable to resolve test server port');
|
||||
}
|
||||
baseUrl = `http://127.0.0.1:${address.port}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (server) {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
it('routes POST /api/cliproxy/restart through restart semantics', async () => {
|
||||
const response = await fetch(`${baseUrl}/api/cliproxy/restart`, { method: 'POST' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toEqual({ success: true, port: 8317 });
|
||||
expect(restartMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user