154 lines
5.3 KiB
JavaScript
154 lines
5.3 KiB
JavaScript
import {
|
|
AGENTS,
|
|
LOG_ERROR_LINES,
|
|
LOG_TOTAL_LINES,
|
|
RUN_LOG,
|
|
TRIGGER,
|
|
agentById,
|
|
eventDrivenAgents,
|
|
networkStats,
|
|
taskDrivenAgents,
|
|
} from '@/lib/agentNetwork';
|
|
|
|
/**
|
|
* The agent table behind the Agents page.
|
|
*
|
|
* It is a hand-maintained snapshot, not a feed — AI_engine exposes no endpoint
|
|
* this console can reach. That makes these tests load-bearing in an unusual
|
|
* way: they are the only thing standing between a typo in the table and a
|
|
* status board that states something untrue with total confidence.
|
|
*/
|
|
|
|
describe('the swarm as recorded', () => {
|
|
it('is nine agents — JARVIS and eight specialised', () => {
|
|
expect(AGENTS).toHaveLength(9);
|
|
expect(AGENTS.map((a) => a.id)).toContain('JARVIS');
|
|
});
|
|
|
|
it('gives every agent a unique id', () => {
|
|
const ids = AGENTS.map((a) => a.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
|
|
it('gives every agent a trigger the page can label', () => {
|
|
// A trigger with no entry in TRIGGER renders as `undefined` on the card —
|
|
// silently, because React prints nothing for it.
|
|
AGENTS.forEach((a) => {
|
|
expect(Object.keys(TRIGGER)).toContain(a.trigger);
|
|
});
|
|
});
|
|
|
|
it('gives every agent the fields the detail panel reads', () => {
|
|
AGENTS.forEach((a) => {
|
|
expect(typeof a.detail).toBe('string');
|
|
expect(a.detail.length).toBeGreaterThan(20);
|
|
expect(typeof a.publishes).toBe('string');
|
|
expect(typeof a.api).toBe('string');
|
|
expect(typeof a.autonomy).toBe('string');
|
|
expect(typeof a.lastSeen).toBe('string');
|
|
expect(Array.isArray(a.subscribes)).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('uses only the four states the page can colour', () => {
|
|
// An unknown status falls through TONE to `undefined`, and the card then
|
|
// renders with no dot, no badge and no bar — invisible breakage.
|
|
AGENTS.forEach((a) => {
|
|
expect(['active', 'running', 'degraded', 'idle']).toContain(a.status);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('the split that the page is really about', () => {
|
|
it('three agents subscribe to the bus themselves', () => {
|
|
expect(eventDrivenAgents().map((a) => a.id)).toEqual([
|
|
'DISPATCH_AGENT',
|
|
'EXCEPTION_AGENT',
|
|
'EXPRESS_DISPATCH_AGENT',
|
|
]);
|
|
});
|
|
|
|
it('five only run when JARVIS hands them work', () => {
|
|
expect(taskDrivenAgents()).toHaveLength(5);
|
|
});
|
|
|
|
it('every event-driven agent names at least one subject', () => {
|
|
// The pipeline chip prints `subscribes[0]`. An empty array there renders a
|
|
// blank line where a NATS subject should be.
|
|
eventDrivenAgents().forEach((a) => {
|
|
expect(a.subscribes.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
it('no task-driven agent claims to subscribe to anything', () => {
|
|
// The whole point of the second group: they cannot be woken by an event.
|
|
taskDrivenAgents().forEach((a) => {
|
|
expect(a.subscribes).toEqual([]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('the headline numbers are derived, never written down', () => {
|
|
it('counts what the cards underneath actually show', () => {
|
|
// A dashboard that says "3 active" over three grey dots is the failure
|
|
// this guards. Every stat comes off the same table the grid renders.
|
|
const s = networkStats();
|
|
expect(s.total).toBe(AGENTS.length);
|
|
expect(s.working).toBe(AGENTS.filter((a) => a.status === 'active').length);
|
|
expect(s.degraded).toBe(AGENTS.filter((a) => a.status === 'degraded').length);
|
|
expect(s.working + s.degraded + s.idle + 1).toBe(s.total); // +1 for JARVIS, which is `running`
|
|
});
|
|
|
|
it('reports exactly one agent doing real work', () => {
|
|
expect(networkStats().working).toBe(1);
|
|
});
|
|
|
|
it('reports no autonomy gate switched on', () => {
|
|
// Stated on the page as a promise: the swarm observes and alerts, and does
|
|
// not write to the system. If a gate is ever turned on, this fails and the
|
|
// page's own subtitle has to change with it.
|
|
const s = networkStats();
|
|
expect(s.autonomyOn).toBe(0);
|
|
expect(s.gated).toBe(3);
|
|
});
|
|
|
|
it('recomputes for a different set rather than returning constants', () => {
|
|
const two = networkStats([
|
|
{ status: 'active', events: 5, gated: true },
|
|
{ status: 'idle', events: 0, gated: false },
|
|
]);
|
|
expect(two.total).toBe(2);
|
|
expect(two.working).toBe(1);
|
|
expect(two.events).toBe(5);
|
|
expect(two.gated).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('agentById', () => {
|
|
it('finds an agent', () => {
|
|
expect(agentById('EXCEPTION_AGENT').name).toBe('Exception');
|
|
});
|
|
|
|
it('falls back to the first rather than returning undefined', () => {
|
|
// The page reads `.status` off the result straight away; undefined here is
|
|
// a blank panel at best and a crash at worst.
|
|
expect(agentById('NOPE')).toBe(AGENTS[0]);
|
|
});
|
|
});
|
|
|
|
describe('the run log', () => {
|
|
it('uses only tones the page can colour', () => {
|
|
RUN_LOG.forEach((l) => {
|
|
expect(['info', 'warn', 'error']).toContain(l.tone);
|
|
});
|
|
});
|
|
|
|
it("the two repeated errors account for the log's stated error total", () => {
|
|
// 2 x 8,594 = 17,188, which is the number in the panel header. If the
|
|
// table and the header ever disagree, one of them is lying.
|
|
const repeated = RUN_LOG.filter((l) => l.tone === 'error').reduce((n, l) => n + (l.count || 0), 0);
|
|
expect(repeated).toBe(LOG_ERROR_LINES);
|
|
expect(LOG_ERROR_LINES).toBeLessThan(LOG_TOTAL_LINES);
|
|
});
|
|
});
|