Files
evomap__evolver/test/extensions.test.js
2026-04-16 09:01:19 +00:00

289 lines
8.7 KiB
JavaScript

'use strict';
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { MailboxStore } = require('../src/proxy/mailbox/store');
const { SkillUpdater } = require('../src/proxy/extensions/skillUpdater');
const { DmHandler } = require('../src/proxy/extensions/dmHandler');
const { SessionHandler } = require('../src/proxy/extensions/sessionHandler');
function tmpDataDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'extensions-test-'));
}
describe('SkillUpdater', () => {
let store, dataDir, skillDir;
before(() => {
dataDir = tmpDataDir();
skillDir = tmpDataDir();
store = new MailboxStore(dataDir);
});
after(() => {
store.close();
try { fs.rmSync(dataDir, { recursive: true }); } catch {}
try { fs.rmSync(skillDir, { recursive: true }); } catch {}
});
it('updates skill.md from inbound message', () => {
const skillPath = path.join(skillDir, 'SKILL.md');
const updater = new SkillUpdater({
store,
skillPath,
logger: { log: () => {}, warn: () => {}, error: () => {} },
});
const result = updater.processSkillUpdate({
payload: { content: '# Updated Skill\nNew content here.', version: '1.1.0' },
});
assert.equal(result, true);
assert.equal(fs.readFileSync(skillPath, 'utf8'), '# Updated Skill\nNew content here.');
assert.equal(store.getState('skill_version'), '1.1.0');
});
it('creates backup before overwriting', () => {
const skillPath = path.join(skillDir, 'SKILL2.md');
fs.writeFileSync(skillPath, 'original content', 'utf8');
const updater = new SkillUpdater({
store,
skillPath,
logger: { log: () => {}, warn: () => {}, error: () => {} },
});
updater.processSkillUpdate({
payload: { content: 'updated content', version: '2.0' },
});
assert.equal(fs.readFileSync(skillPath, 'utf8'), 'updated content');
assert.equal(fs.readFileSync(skillPath + '.bak', 'utf8'), 'original content');
});
it('returns false without skill path', () => {
const updater = new SkillUpdater({
store,
logger: { log: () => {}, warn: () => {}, error: () => {} },
});
assert.equal(updater.processSkillUpdate({ payload: { content: 'x' } }), false);
});
it('returns false without content', () => {
const updater = new SkillUpdater({
store,
skillPath: path.join(skillDir, 'noop.md'),
logger: { log: () => {}, warn: () => {}, error: () => {} },
});
assert.equal(updater.processSkillUpdate({ payload: {} }), false);
});
it('pollAndApply processes pending skill_update messages', () => {
const dir2 = tmpDataDir();
const s2 = new MailboxStore(dir2);
const sp = path.join(skillDir, 'polled.md');
s2.writeInbound({
type: 'skill_update',
payload: { content: '# Polled skill', version: '3.0' },
});
const updater = new SkillUpdater({
store: s2,
skillPath: sp,
logger: { log: () => {}, warn: () => {}, error: () => {} },
});
const applied = updater.pollAndApply();
assert.equal(applied, 1);
assert.equal(fs.readFileSync(sp, 'utf8'), '# Polled skill');
s2.close();
try { fs.rmSync(dir2, { recursive: true }); } catch {}
});
});
describe('DmHandler', () => {
let store, handler, dataDir;
before(() => {
dataDir = tmpDataDir();
store = new MailboxStore(dataDir);
handler = new DmHandler({ store });
});
after(() => {
store.close();
try { fs.rmSync(dataDir, { recursive: true }); } catch {}
});
it('sends a DM and creates outbound message', () => {
const result = handler.send({
recipientNodeId: 'node_abc',
content: 'Hello there',
});
assert.ok(result.message_id);
assert.equal(result.status, 'pending');
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'dm');
assert.equal(msg.direction, 'outbound');
assert.equal(msg.payload.recipient_node_id, 'node_abc');
assert.equal(msg.payload.content, 'Hello there');
});
it('throws on missing recipientNodeId', () => {
assert.throws(() => handler.send({ content: 'x' }), /recipientNodeId/);
});
it('throws on missing content', () => {
assert.throws(() => handler.send({ recipientNodeId: 'n' }), /content/);
});
it('polls inbound DMs', () => {
store.writeInbound({ type: 'dm', payload: { content: 'incoming dm' } });
const msgs = handler.poll();
assert.ok(msgs.length >= 1);
assert.equal(msgs[0].type, 'dm');
});
it('acks DM messages', () => {
const id = store.writeInbound({ type: 'dm', payload: { content: 'to ack' } });
const count = handler.ack(id);
assert.equal(count, 1);
const msg = store.getById(id);
assert.equal(msg.status, 'delivered');
});
it('lists DM history', () => {
const msgs = handler.list();
assert.ok(Array.isArray(msgs));
});
});
describe('SessionHandler', () => {
let store, handler, dataDir;
before(() => {
dataDir = tmpDataDir();
store = new MailboxStore(dataDir);
handler = new SessionHandler({ store });
});
after(() => {
store.close();
try { fs.rmSync(dataDir, { recursive: true }); } catch {}
});
it('creates a session and stores outbound message', () => {
const result = handler.createSession({
title: 'Test Session',
description: 'A test collaboration session',
inviteNodeIds: ['node_a', 'node_b'],
});
assert.ok(result.message_id);
assert.equal(result.status, 'pending');
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_create');
assert.equal(msg.direction, 'outbound');
assert.equal(msg.payload.title, 'Test Session');
assert.deepEqual(msg.payload.invite_node_ids, ['node_a', 'node_b']);
});
it('throws on missing title', () => {
assert.throws(() => handler.createSession({}), /title/);
});
it('joins a session', () => {
const result = handler.joinSession({ sessionId: 'sess_123' });
assert.ok(result.message_id);
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_join');
assert.equal(msg.payload.session_id, 'sess_123');
});
it('throws on join without sessionId', () => {
assert.throws(() => handler.joinSession({}), /sessionId/);
});
it('leaves a session', () => {
const result = handler.leaveSession({ sessionId: 'sess_456' });
assert.ok(result.message_id);
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_leave');
});
it('sends a message to a session', () => {
const result = handler.sendMessage({
sessionId: 'sess_789',
toNodeId: 'node_c',
msgType: 'context_update',
payload: { key: 'value' },
});
assert.ok(result.message_id);
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_message');
assert.equal(msg.payload.session_id, 'sess_789');
assert.equal(msg.payload.to_node_id, 'node_c');
});
it('throws on send message with oversized payload', () => {
const bigPayload = { data: 'x'.repeat(17000) };
assert.throws(() => handler.sendMessage({
sessionId: 'sess_big',
payload: bigPayload,
}), /too large/);
});
it('delegates a subtask', () => {
const result = handler.delegateSubtask({
sessionId: 'sess_del',
toNodeId: 'node_worker',
title: 'Implement feature X',
role: 'builder',
});
assert.ok(result.message_id);
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_delegate');
assert.equal(msg.payload.role, 'builder');
assert.equal(msg.payload.title, 'Implement feature X');
});
it('normalizes invalid role to builder', () => {
const result = handler.delegateSubtask({
sessionId: 'sess_role',
title: 'Fix bug',
role: 'invalid_role',
});
const msg = store.getById(result.message_id);
assert.equal(msg.payload.role, 'builder');
});
it('submits a result', () => {
const result = handler.submitResult({
sessionId: 'sess_sub',
taskId: 'task_1',
resultAssetId: 'asset_1',
summary: 'Completed the implementation',
});
assert.ok(result.message_id);
const msg = store.getById(result.message_id);
assert.equal(msg.type, 'session_submit');
assert.equal(msg.payload.task_id, 'task_1');
});
it('polls session invites', () => {
store.writeInbound({ type: 'collaboration_invite', payload: { session_id: 's1' } });
const msgs = handler.pollInvites();
assert.ok(msgs.length >= 1);
});
it('lists active sessions', () => {
const sessions = handler.listActiveSessions();
assert.ok(Array.isArray(sessions));
assert.ok(sessions.length > 0);
});
});