Files
gnome-jarvis/test/daemon-infra.test.js
T
snxraven a4073b9020
Rolling release / release (push) Successful in 6m36s
Check Point
2026-09-12 09:07:09 -04:00

80 lines
4.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import net from 'node:net';
import { PrivacyLog } from '../daemon/privacy-log.js';
import { createEventSocket, sendEvent } from '../daemon/ipc.js';
import { StateRecovery } from '../daemon/recovery.js';
import { runIsolatedJob } from '../daemon/job-runner.js';
import { QvacScheduler } from '../daemon/qvac-scheduler.js';
import { ComputerAudit } from '../computer-use/audit.js';
import { writeFile } from 'node:fs/promises';
test('privacy log allowlists metadata and excludes prompt contents', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-log-'));
const log = new PrivacyLog({ file: path.join(dir, 'events.jsonl') });
await log.record('reply', { state: 'SPEAKING', prompt: 'secret text', success: true });
const line = JSON.parse(await readFile(path.join(dir, 'events.jsonl'), 'utf8'));
assert.equal(line.state, 'SPEAKING');
assert.equal(line.success, true);
assert.equal('prompt' in line, false);
});
test('event socket rejects bulk messages and transports small JSON events', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-ipc-'));
const socketPath = path.join(dir, 'jarvis.sock');
const received = [];
assert.throws(() => sendEvent({ write() {} }, 'Frame', { data: 'x'.repeat(70 * 1024) }), /too large/);
let ipc;
try { ipc = await createEventSocket({ socketPath, onMessage: (message) => received.push(message) }); }
catch (error) {
if (error.code === 'EPERM') { t.skip('sandbox forbids binding Unix sockets'); return; }
throw error;
}
await new Promise((resolve, reject) => {
const socket = net.createConnection(socketPath, () => { sendEvent(socket, 'Token', { text: 'hello' }); socket.end(); resolve(); });
socket.on('error', reject);
});
await new Promise((resolve) => setTimeout(resolve, 10));
assert.deepEqual(received, [{ event: 'Token', text: 'hello' }]);
await ipc.close();
});
test('failed background jobs stay isolated and scheduler remains usable', async () => {
const scheduler = new QvacScheduler(); const errors = [];
const failed = await runIsolatedJob(scheduler, async () => { throw new Error('OOM'); }, { onError: (e) => errors.push(e.message) });
assert.deepEqual(failed, { ok: false, unavailable: 'OOM', isolated: true });
assert.deepEqual(await scheduler.run(async () => 'voice', { lane: 'voice' }), 'voice');
assert.deepEqual(errors, ['OOM']); assert.equal(scheduler.metrics().failed, 1);
});
test('state recovery is atomic and safe after an interrupted turn', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-recovery-')); const recovery = new StateRecovery({ file: path.join(dir, 'state.json') });
recovery.save({ state: 'THINKING', mode: 'chat' }); assert.deepEqual(recovery.load(), { state: 'ARMED', mode: 'chat', recovered: true });
});
test('computer audit stores hashes and never stores screenshot bytes', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-audit-')); const frame = path.join(dir, 'frame.png'); await writeFile(frame, 'private pixels'); const audit = new ComputerAudit({ dir });
const row = await audit.record('observe', { name: 'Window', role: 'window' }, { ok: true, screenshotPath: frame }); const text = await readFile(path.join(dir, `cu-${new Date().toISOString().slice(0, 10).replaceAll('-', '')}.jsonl`), 'utf8');
assert.equal(row.screenshot_hash.length, 64); assert.doesNotMatch(text, /private pixels/); assert.doesNotMatch(text, /frame\.png/);
});
test('event socket bounds UTF-8 input, handles async rejection, and closes active clients', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'jarvis-ipc-errors-'));
let ipc;
try { ipc = await createEventSocket({ socketPath: path.join(dir, 'events.sock'), onMessage: async () => { throw new Error('handler failed'); } }); }
catch (error) { if (error.code === 'EPERM') { t.skip('sandbox forbids binding Unix sockets'); return; } throw error; }
const connect = () => new Promise((resolve, reject) => { const client = net.createConnection(ipc.socketPath, () => resolve(client)); client.on('error', reject); });
try {
const client = await connect();
const response = new Promise(resolve => client.once('data', resolve));
client.write('{}\n'); assert.match(String(await response), /invalid IPC message/);
const closed = new Promise(resolve => client.once('close', resolve));
client.write('é'.repeat(40 * 1024)); await closed;
const idle = await connect(); const idleClosed = new Promise(resolve => idle.once('close', resolve));
await ipc.close(); await idleClosed;
} finally { if (ipc.server.listening) await ipc.close(); }
});