mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
144 lines
6.1 KiB
TypeScript
144 lines
6.1 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync } from 'fs';
|
|
import { spawnSync } from 'child_process';
|
|
import { describe, expect, it } from 'bun:test';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
|
|
const updateScript = readFileSync('docker/host/ccs-cliproxy-safe-update.sh', 'utf8');
|
|
const reconcileScript = readFileSync('docker/host/ccs-cliproxy-reconcile.sh', 'utf8');
|
|
const updateService = readFileSync('docker/host/systemd/ccs-cliproxy-update.service', 'utf8');
|
|
const updateTimer = readFileSync('docker/host/systemd/ccs-cliproxy-update.timer', 'utf8');
|
|
const reconcileService = readFileSync('docker/host/systemd/ccs-cliproxy-reconcile.service', 'utf8');
|
|
const reconcileTimer = readFileSync('docker/host/systemd/ccs-cliproxy-reconcile.timer', 'utf8');
|
|
|
|
describe('CLIProxy Docker host continuity assets', () => {
|
|
it('stages and validates the replacement before stopping the live proxy', () => {
|
|
const stageIndex = updateScript.indexOf('CCS_DIR="$stage_ccs_dir" ccs cliproxy --latest');
|
|
const validateIndex = updateScript.indexOf('"$stage_binary" --version');
|
|
const stopIndex = updateScript.indexOf('supervisorctl_cmd stop cliproxy', validateIndex);
|
|
|
|
expect(stageIndex).toBeGreaterThan(-1);
|
|
expect(validateIndex).toBeGreaterThan(stageIndex);
|
|
expect(stopIndex).toBeGreaterThan(validateIndex);
|
|
});
|
|
|
|
it('serializes updates and reconciliation with the same lock', () => {
|
|
expect(updateScript).toContain('/run/lock/ccs-cliproxy-maintenance.lock');
|
|
expect(reconcileScript).toContain('/run/lock/ccs-cliproxy-maintenance.lock');
|
|
expect(updateScript).toContain('flock -n 9');
|
|
expect(reconcileScript).toContain('flock -n 9');
|
|
});
|
|
|
|
it('shares the in-container install lifecycle lock with CLI and dashboard installs', () => {
|
|
expect(updateScript).toContain(
|
|
"install_lock_target='/root/.ccs/cliproxy/bin/.install-lifecycle-plus'"
|
|
);
|
|
expect(updateScript).toContain('install_lock_dir="$install_lock_target.lock"');
|
|
expect(updateScript).toContain('while ! mkdir "$install_lock_dir"');
|
|
expect(updateScript).toContain('touch "$install_lock_dir"');
|
|
expect(updateScript).toContain('install_lock_stale_seconds=600');
|
|
expect(updateScript).toContain('remove_stale_install_lock');
|
|
expect(updateScript).toContain('rmdir "$install_lock_dir"');
|
|
expect(updateScript.indexOf('acquire_install_lock')).toBeLessThan(
|
|
updateScript.indexOf('supervisorctl_cmd stop cliproxy')
|
|
);
|
|
});
|
|
|
|
it('reclaims an orphaned stale lifecycle lock', () => {
|
|
const testRoot = mkdtempSync(join(tmpdir(), 'ccs-cliproxy-host-lock-'));
|
|
const helpersStart = updateScript.indexOf('remove_stale_install_lock() {');
|
|
const helpersEnd = updateScript.indexOf('\nwait_for_proxy() {');
|
|
const helpers = updateScript.slice(helpersStart, helpersEnd);
|
|
try {
|
|
const result = spawnSync(
|
|
'bash',
|
|
[
|
|
'-c',
|
|
`set -Eeuo pipefail
|
|
install_lock_target="$1/target"
|
|
install_lock_dir="$install_lock_target.lock"
|
|
install_lock_stale_seconds=600
|
|
install_lock_owned=0
|
|
mkdir -p "$install_lock_target" "$install_lock_dir"
|
|
touch -t 200001010000 "$install_lock_dir"
|
|
${helpers}
|
|
acquire_install_lock
|
|
test "$install_lock_owned" -eq 1
|
|
release_install_lock`,
|
|
'test-shell',
|
|
testRoot,
|
|
],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
|
|
expect(result.status).toBe(0);
|
|
} finally {
|
|
rmSync(testRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('rolls back failed swaps and health-checks both services', () => {
|
|
expect(updateScript).toContain('rollback()');
|
|
expect(updateScript).toContain('previous-binary');
|
|
expect(updateScript).toContain('wait_for_proxy');
|
|
expect(reconcileScript).toContain('http://127.0.0.1:3000/');
|
|
expect(reconcileScript).toContain('http://127.0.0.1:8317/');
|
|
});
|
|
|
|
it('recreates missing containers and escalates unhealthy recovery', () => {
|
|
expect(reconcileScript).toContain('CCS_CLIPROXY_COMPOSE_DIR:-/root/.ccs/docker');
|
|
expect(reconcileScript).toContain('$compose_dir/docker-compose.integrated.yml');
|
|
expect(reconcileScript).toContain('CCS_CLIPROXY_COMPOSE_PROJECT:-docker');
|
|
expect(reconcileScript).toContain('--project-name "$compose_project"');
|
|
expect(reconcileScript).toContain('-f "$compose_file" up -d --no-build');
|
|
expect(reconcileScript).toContain("2>/dev/null || true");
|
|
expect(reconcileScript).toContain('if [ -z "$running_state" ]');
|
|
expect(reconcileScript).toContain('restart ccs-dashboard cliproxy');
|
|
expect(reconcileScript).toContain('docker restart "$container_name"');
|
|
expect(reconcileScript).toContain('up -d --force-recreate --no-build');
|
|
expect(reconcileScript.indexOf('docker restart "$container_name"')).toBeGreaterThan(
|
|
reconcileScript.indexOf('restart ccs-dashboard cliproxy')
|
|
);
|
|
});
|
|
|
|
it('installs executable services on bounded timers', () => {
|
|
expect(updateService).toContain('ExecStart=/opt/cliproxy/ccs-cliproxy-safe-update.sh');
|
|
expect(updateService).toContain('TimeoutStartSec=10min');
|
|
expect(updateTimer).toContain('OnUnitActiveSec=15min');
|
|
expect(reconcileService).toContain('ExecStart=/opt/cliproxy/ccs-cliproxy-reconcile.sh');
|
|
expect(reconcileTimer).toContain('OnUnitActiveSec=30s');
|
|
});
|
|
|
|
it('forces nonzero signal exits so the EXIT trap rolls back maintenance', () => {
|
|
expect(updateScript).toContain("trap 'exit 130' INT");
|
|
expect(updateScript).toContain("trap 'exit 143' TERM");
|
|
expect(updateScript).toContain("trap 'exit 129' HUP");
|
|
|
|
const handlerStart = updateScript.indexOf('on_exit() {');
|
|
const handlerEnd = updateScript.indexOf('\ntrap on_exit EXIT');
|
|
const handlers = updateScript.slice(handlerStart, handlerEnd);
|
|
const result = spawnSync(
|
|
'bash',
|
|
[
|
|
'-c',
|
|
`set -Eeuo pipefail
|
|
maintenance_started=1
|
|
stage_root=/tmp/unused
|
|
rollback() { printf 'rollback\\n'; }
|
|
cleanup() { printf 'cleanup\\n'; }
|
|
release_install_lock() { :; }
|
|
${handlers}
|
|
trap on_exit EXIT
|
|
trap 'exit 143' TERM
|
|
true
|
|
kill -TERM $$`,
|
|
],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
|
|
expect(result.status).toBe(143);
|
|
expect(result.stdout).toContain('rollback');
|
|
expect(result.stdout).toContain('cleanup');
|
|
});
|
|
});
|