78 lines
3.8 KiB
JavaScript
78 lines
3.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync, mkdtempSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const release = readFileSync(new URL('./scripts/release-portal-runtime-prod.sh', import.meta.url), 'utf8');
|
|
const start = release.indexOf('remount_goosed_after_live_swap() {');
|
|
const end = release.indexOf('\n mkdir -p "${APP_DIR}"', release.indexOf('\n return 0\n fi', start));
|
|
const nativeFunction = release.slice(start, end)
|
|
.replace('if [[ ! -x "${docker_bin}" || ! -f "${compose_file}" ]]; then', 'if true; then') + '\n}';
|
|
function run(targets, unavailable = '') {
|
|
const root = mkdtempSync(join(tmpdir(), 'portal-targets-'));
|
|
try {
|
|
writeFileSync(join(root, '.env'), `TKMIND_API_TARGETS=${targets}\n`);
|
|
writeFileSync(join(root, 'mindspace-sandbox-mcp.mjs'), '');
|
|
const result = spawnSync('/bin/bash', ['-c', `
|
|
set -euo pipefail
|
|
APP_DIR="$1"; RELEASE_ID=test; unavailable="$2"
|
|
say() { :; }
|
|
launchctl() { printf 'restart=%s\\n' "$*" >> "$APP_DIR/restarts"; }
|
|
sleep() { :; }
|
|
curl() { case "$*" in *"$unavailable"*) return 1;; esac; printf ok; }
|
|
# An empty unavailable marker must not match every request.
|
|
if [[ -z "$2" ]]; then curl() { printf ok; }; fi
|
|
${nativeFunction}
|
|
remount_goosed_after_live_swap
|
|
`, 'test', root, unavailable], { encoding: 'utf8' });
|
|
try { result.stdout += readFileSync(join(root, 'restarts'), 'utf8'); } catch {}
|
|
return result;
|
|
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
}
|
|
|
|
test('native remount restarts only the two configured targets', () => {
|
|
const result = run('https://127.0.0.1:18006,https://127.0.0.1:18007');
|
|
assert.equal(result.status, 0, result.stderr);
|
|
assert.match(result.stdout, /native-18006/);
|
|
assert.match(result.stdout, /native-18007/);
|
|
assert.doesNotMatch(result.stdout, /native-18008/);
|
|
});
|
|
test('quoted targets and localhost are supported', () => {
|
|
assert.equal(run('"https://127.0.0.1:18006,https://localhost:18007"').status, 0);
|
|
});
|
|
for (const targets of ['', 'https://example.com:18006', 'https://127.0.0.1:99999']) {
|
|
test(`invalid configured targets fail closed: ${targets || '(empty)'}`, () => {
|
|
const result = run(targets);
|
|
assert.notEqual(result.status, 0);
|
|
assert.match(result.stderr, /goosed remount failed/);
|
|
assert.doesNotMatch(result.stdout, /restart=/);
|
|
});
|
|
}
|
|
test('an unavailable configured target still blocks remount', () => {
|
|
const result = run('https://127.0.0.1:18006,https://127.0.0.1:18007', '18007');
|
|
assert.notEqual(result.status, 0);
|
|
assert.match(result.stderr, /did not become healthy/);
|
|
});
|
|
|
|
|
|
test('release tar excludes transient skills locks while keeping user content', () => {
|
|
const root = mkdtempSync(join(tmpdir(), 'portal-backup-'));
|
|
try {
|
|
const owner = join(root, 'Memind', 'MindSpace', 'user');
|
|
mkdirSync(join(owner, '.agents', '.skills-sync.lock'), { recursive: true });
|
|
writeFileSync(join(owner, '.agents', '.skills-sync.lock', 'owner'), 'transient');
|
|
writeFileSync(join(owner, 'page.html'), 'persistent page');
|
|
const archive = join(root, 'backup.tar');
|
|
const exclusion = release.match(/tar --exclude='Memind\/\.tailscale\/\*\.sock' --exclude='([^']+)'/);
|
|
assert.ok(exclusion, 'full backup explicitly excludes transient skills lock');
|
|
const packed = spawnSync('tar', [`--exclude=${exclusion[1]}`, '-cf', archive, '-C', root, 'Memind'], { encoding: 'utf8' });
|
|
assert.equal(packed.status, 0, packed.stderr);
|
|
const listed = spawnSync('tar', ['-tf', archive], { encoding: 'utf8' });
|
|
assert.equal(listed.status, 0, listed.stderr);
|
|
assert.match(listed.stdout, /page\.html/);
|
|
assert.doesNotMatch(listed.stdout, /skills-sync\.lock/);
|
|
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
});
|