Files
memind/release-portal-targets.test.mjs
T

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 }); }
});