aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
245 lines (199 loc) • 7.42 kB
JavaScript
/**
* Basic tests for Dashboard
*
* Run with: node tools/ralph-external/dashboard.test.mjs
*/
import { Dashboard } from './dashboard.mjs';
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import assert from 'assert';
const TEST_DIR = '.aiwg/ralph/dashboard-test';
function cleanup() {
if (existsSync(TEST_DIR)) {
rmSync(TEST_DIR, { recursive: true, force: true });
}
}
function setup() {
cleanup();
mkdirSync(TEST_DIR, { recursive: true });
}
function test(name, fn) {
try {
fn();
console.log(`✓ ${name}`);
} catch (error) {
console.error(`✗ ${name}`);
console.error(` ${error.message}`);
throw error;
}
}
// Test: Initialization
test('Dashboard initializes correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
assert.strictEqual(dashboard.projectRoot, TEST_DIR);
assert.ok(dashboard.loopsDir.endsWith('loops'));
});
// Test: centerText
test('centerText() centers text correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
const centered = dashboard.centerText('Hello', 20);
assert.ok(centered.startsWith('│'));
assert.ok(centered.endsWith('│'));
assert.strictEqual(centered.length, 20);
assert.ok(centered.includes('Hello'));
});
// Test: formatDuration
test('formatDuration() formats durations correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
assert.strictEqual(dashboard.formatDuration(5000), '5s');
assert.strictEqual(dashboard.formatDuration(65000), '1m');
assert.strictEqual(dashboard.formatDuration(3665000), '1h 1m');
assert.strictEqual(dashboard.formatDuration(90065000), '1d 1h');
});
// Test: truncate
test('truncate() truncates strings correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
const short = dashboard.truncate('hello', 10);
assert.strictEqual(short, 'hello '); // Padded
const long = dashboard.truncate('this-is-a-very-long-string', 10);
assert.strictEqual(long, 'this-is-..');
assert.strictEqual(long.length, 10);
});
// Test: padRight and padLeft
test('padRight() and padLeft() pad correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
assert.strictEqual(dashboard.padRight('test', 10), 'test ');
assert.strictEqual(dashboard.padLeft('test', 10), ' test');
});
// Test: getLoopsSummary with no loops
test('getLoopsSummary() returns empty array when no loops', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
const loops = dashboard.getLoopsSummary();
assert.strictEqual(loops.length, 0);
});
// Test: getLoopsSummary with loops
test('getLoopsSummary() reads and sorts loops', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
// Create mock loop states
const loopData = [
{ loopId: 'loop-001', status: 'paused', iteration: 5, max: 10 },
{ loopId: 'loop-002', status: 'running', iteration: 3, max: 10 },
{ loopId: 'loop-003', status: 'completed', iteration: 10, max: 10 },
];
for (const data of loopData) {
const loopDir = join(TEST_DIR, '.aiwg', 'ralph', 'loops', data.loopId);
mkdirSync(loopDir, { recursive: true });
writeFileSync(
join(loopDir, 'state.json'),
JSON.stringify({
status: data.status,
currentIteration: data.iteration,
maxIterations: data.max,
objective: `Test objective for ${data.loopId}`,
startTime: new Date().toISOString(),
})
);
}
const loops = dashboard.getLoopsSummary();
assert.strictEqual(loops.length, 3);
// Should be sorted by status (running first)
assert.strictEqual(loops[0].status, 'running');
assert.strictEqual(loops[1].status, 'paused');
assert.strictEqual(loops[2].status, 'completed');
});
// Test: getAggregateMetrics
test('getAggregateMetrics() calculates metrics correctly', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
// Create mock loop states
const loopData = [
{ loopId: 'loop-004', status: 'completed', iteration: 10, max: 10 },
{ loopId: 'loop-005', status: 'failed', iteration: 5, max: 10 },
{ loopId: 'loop-006', status: 'running', iteration: 7, max: 10 },
];
for (const data of loopData) {
const loopDir = join(TEST_DIR, '.aiwg', 'ralph', 'loops', data.loopId);
mkdirSync(loopDir, { recursive: true });
writeFileSync(
join(loopDir, 'state.json'),
JSON.stringify({
status: data.status,
currentIteration: data.iteration,
maxIterations: data.max,
startTime: new Date().toISOString(),
})
);
}
const metrics = dashboard.getAggregateMetrics();
assert.strictEqual(metrics.totalIterations, 22); // 10 + 5 + 7
assert.strictEqual(metrics.successRate, 50); // 1 completed, 1 failed = 50%
assert.ok(metrics.avgProgress > 0);
});
// Test: countCrashesToday
test('countCrashesToday() counts recent crashes', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
const loopId = 'loop-007';
const loopDir = join(TEST_DIR, '.aiwg', 'ralph', 'loops', loopId);
mkdirSync(loopDir, { recursive: true });
// Create crash log with recent crash
const now = new Date().toISOString();
const yesterday = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
const crashLog = `[${now}] Error: Timeout\n[${yesterday}] Error: OOM\n`;
writeFileSync(join(loopDir, 'crash.log'), crashLog);
const count = dashboard.countCrashesToday();
// Should only count the recent one
assert.strictEqual(count, 1);
});
// Test: render with no loops
test('render() produces valid output with no loops', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
const output = dashboard.render();
assert.ok(output.includes('External Ralph Multi-Loop Dashboard'));
assert.ok(output.includes('Active Loops: 0/0'));
assert.ok(output.includes('No active loops'));
assert.ok(output.includes('╭'));
assert.ok(output.includes('╰'));
});
// Test: render with loops
test('render() produces valid output with loops', () => {
setup();
const dashboard = new Dashboard({ projectRoot: TEST_DIR });
// Create mock loops
const loopData = [
{ loopId: 'ralph-fix-tests-a1b2c3d4', status: 'running', iteration: 3, max: 10 },
{ loopId: 'ralph-migrate-ts-e5f6g7h8', status: 'paused', iteration: 7, max: 20 },
];
for (const data of loopData) {
const loopDir = join(TEST_DIR, '.aiwg', 'ralph', 'loops', data.loopId);
mkdirSync(loopDir, { recursive: true });
writeFileSync(
join(loopDir, 'state.json'),
JSON.stringify({
status: data.status,
currentIteration: data.iteration,
maxIterations: data.max,
startTime: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
})
);
}
const output = dashboard.render();
assert.ok(output.includes('Active Loops: 1/2')); // Only 1 running
assert.ok(output.includes('ralph-fix-tests'));
assert.ok(output.includes('ralph-migrate-ts'));
assert.ok(output.includes('running'));
assert.ok(output.includes('paused'));
assert.ok(output.includes('3/10'));
assert.ok(output.includes('7/20'));
});
// Cleanup after all tests
cleanup();
console.log('\n=== All Dashboard Tests Passed ===\n');