moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { MindDB } from '@waggle/core';
import { buildLocalServer } from '../src/local/index.js';
import { injectWithAuth } from './test-utils.js';
import type { FastifyInstance } from 'fastify';
describe('Evolution Routes', () => {
let server: FastifyInstance;
let tmpDir: string;
beforeAll(async () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-evolution-routes-'));
const personalPath = path.join(tmpDir, 'personal.mind');
const mind = new MindDB(personalPath);
mind.close();
server = await buildLocalServer({ dataDir: tmpDir });
});
afterAll(async () => {
await server.close();
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
});
function seedRun(overrides: Partial<Parameters<typeof server.evolutionStore.create>[0]> = {}) {
return server.evolutionStore.create({
targetKind: 'persona-system-prompt',
targetName: 'coder',
baselineText: 'baseline prompt that is long enough',
winnerText: 'evolved prompt that is long enough to pass the gate',
deltaAccuracy: 0.08,
gateVerdict: 'pass',
gateReasons: [{ gate: 'size', verdict: 'pass', reason: 'within limit' }],
...overrides,
});
}
// ── GET /api/evolution/runs ──
describe('GET /api/evolution/runs', () => {
beforeAll(() => server.evolutionStore.clear());
it('returns an empty list when nothing has run', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/runs',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.runs).toEqual([]);
expect(body.count).toBe(0);
});
it('returns seeded runs in reverse-chronological order', async () => {
seedRun({ targetName: 'a' });
seedRun({ targetName: 'b' });
seedRun({ targetName: 'c' });
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/runs',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.count).toBe(3);
expect(body.runs[0].id).toBeGreaterThan(body.runs[body.runs.length - 1].id);
});
it('filters by status', async () => {
const a = seedRun({ targetName: 'x' });
server.evolutionStore.reject(a.run_uuid, 'test reject');
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/runs?status=rejected',
});
const body = JSON.parse(res.body);
expect(body.runs.length).toBeGreaterThanOrEqual(1);
expect(body.runs.every((r: { status: string }) => r.status === 'rejected')).toBe(true);
});
it('respects the limit query param', async () => {
for (let i = 0; i < 5; i++) seedRun({ targetName: `limit-${i}` });
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/runs?limit=2',
});
const body = JSON.parse(res.body);
expect(body.runs).toHaveLength(2);
});
});
// ── GET /api/evolution/runs/:uuid ──
describe('GET /api/evolution/runs/:uuid', () => {
it('returns a single run with parsed JSON blobs', async () => {
const run = seedRun({
winnerSchema: { name: 'test', fields: [{ name: 'answer', type: 'string' }] },
artifacts: { seed: 42, generations: 2 },
});
const res = await injectWithAuth(server, {
method: 'GET', url: `/api/evolution/runs/${run.run_uuid}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.run_uuid).toBe(run.run_uuid);
expect(body.winnerSchema).toEqual({ name: 'test', fields: [{ name: 'answer', type: 'string' }] });
expect(body.artifacts).toEqual({ seed: 42, generations: 2 });
expect(body.gateReasons).toHaveLength(1);
});
it('returns 404 for unknown uuid', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/runs/does-not-exist',
});
expect(res.statusCode).toBe(404);
});
it('returns run even when artifacts are null/malformed', async () => {
const run = seedRun();
const res = await injectWithAuth(server, {
method: 'GET', url: `/api/evolution/runs/${run.run_uuid}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.winnerSchema).toBeNull();
expect(body.artifacts).toBeNull();
});
});
// ── POST /api/evolution/runs/:uuid/reject ──
describe('POST /api/evolution/runs/:uuid/reject', () => {
it('moves proposed run to rejected with reason', async () => {
const run = seedRun();
const res = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/reject`,
payload: { reason: 'not great' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.status).toBe('rejected');
expect(body.user_note).toBe('not great');
});
it('rejects 404 for unknown uuid', async () => {
const res = await injectWithAuth(server, {
method: 'POST',
url: '/api/evolution/runs/ghost/reject',
payload: {},
});
expect(res.statusCode).toBe(404);
});
it('rejects 409 when run is not proposed', async () => {
const run = seedRun();
server.evolutionStore.reject(run.run_uuid);
const res = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/reject`,
payload: {},
});
expect(res.statusCode).toBe(409);
});
});
// ── POST /api/evolution/runs/:uuid/accept ──
describe('POST /api/evolution/runs/:uuid/accept', () => {
it('writes a persona override to disk and moves to deployed', async () => {
const run = seedRun({
targetKind: 'persona-system-prompt',
targetName: 'coder',
winnerText: 'EVOLVED coder system prompt',
});
const res = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: { note: 'looks good' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.status).toBe('deployed');
const overridePath = path.join(tmpDir, 'personas', 'coder.json');
expect(fs.existsSync(overridePath)).toBe(true);
const loaded = JSON.parse(fs.readFileSync(overridePath, 'utf-8'));
expect(loaded.systemPrompt).toBe('EVOLVED coder system prompt');
});
it('writes a behavioral-spec override to disk', async () => {
const run = seedRun({
targetKind: 'behavioral-spec-section',
targetName: 'coreLoop',
winnerText: 'EVOLVED core loop text',
});
const res = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: {},
});
expect(res.statusCode).toBe(200);
const overridePath = path.join(tmpDir, 'behavioral-overrides', 'coreLoop.json');
expect(fs.existsSync(overridePath)).toBe(true);
const loaded = JSON.parse(fs.readFileSync(overridePath, 'utf-8'));
expect(loaded.text).toBe('EVOLVED core loop text');
expect(loaded.runUuid).toBe(run.run_uuid);
});
it('marks run failed when deploy throws (unsupported target_kind)', async () => {
const run = seedRun({
targetKind: 'tool-description',
targetName: 'some_tool',
winnerText: 'new description',
});
const res = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: {},
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.status).toBe('failed');
expect(body.failure_reason).toMatch(/not yet implemented/);
});
it('emits persona:reloaded event on persona deploy', async () => {
const run = seedRun({
targetKind: 'persona-system-prompt',
targetName: 'writer',
winnerText: 'EVOLVED writer',
});
let emitted: unknown = null;
const listener = (payload: unknown) => { emitted = payload; };
server.eventBus.on('persona:reloaded', listener);
await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: {},
});
server.eventBus.off('persona:reloaded', listener);
expect(emitted).toEqual({ personaId: 'writer' });
});
it('returns 409 for already-accepted run', async () => {
const run = seedRun();
await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: {},
});
const second = await injectWithAuth(server, {
method: 'POST',
url: `/api/evolution/runs/${run.run_uuid}/accept`,
payload: {},
});
expect(second.statusCode).toBe(409);
});
});
// ── GET /api/evolution/status ──
describe('GET /api/evolution/status', () => {
it('returns aggregate counts per status', async () => {
server.evolutionStore.clear();
const a = seedRun();
const b = seedRun();
server.evolutionStore.reject(a.run_uuid);
server.evolutionStore.accept(b.run_uuid);
server.evolutionStore.markDeployed(b.run_uuid);
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/status',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.counts.rejected).toBe(1);
expect(body.counts.deployed).toBe(1);
expect(body.pendingCount).toBe(0);
});
});
// ── GET /api/evolution/targets ──
describe('GET /api/evolution/targets', () => {
it('returns personas + behavioral-spec sections + a default schema', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/targets',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(Array.isArray(body.personas)).toBe(true);
expect(body.personas.length).toBeGreaterThan(0);
expect(body.personas[0]).toHaveProperty('id');
expect(body.personas[0]).toHaveProperty('name');
// systemPrompt is intentionally omitted from /targets to match /api/personas privacy policy
expect(body.personas[0]).not.toHaveProperty('systemPrompt');
expect(Array.isArray(body.sections)).toBe(true);
expect(body.sections).toEqual(expect.arrayContaining([
'coreLoop', 'qualityRules', 'behavioralRules', 'workPatterns', 'intelligenceDefaults',
]));
expect(body.defaultSchema).toMatchObject({
name: expect.any(String),
version: expect.any(Number),
fields: expect.any(Array),
});
expect(body.defaultSchema.fields.length).toBeGreaterThan(0);
});
});
// ── GET /api/evolution/baseline ──
describe('GET /api/evolution/baseline', () => {
it('returns 400 when kind or name is missing', async () => {
const a = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline',
});
expect(a.statusCode).toBe(400);
const b = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline?kind=persona-system-prompt',
});
expect(b.statusCode).toBe(400);
});
it('returns the persona systemPrompt for persona-system-prompt', async () => {
// Pick whatever persona the registry lists first — avoids hardcoding
// an id that might be renamed.
const listRes = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/targets',
});
const { personas } = JSON.parse(listRes.body);
const personaId = personas[0].id;
const res = await injectWithAuth(server, {
method: 'GET', url: `/api/evolution/baseline?kind=persona-system-prompt&name=${encodeURIComponent(personaId)}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(typeof body.baseline).toBe('string');
expect(body.baseline.length).toBeGreaterThan(0);
expect(body.schemaBaseline).toMatchObject({
name: expect.any(String),
fields: expect.any(Array),
});
});
it('returns 404 for unknown persona', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline?kind=persona-system-prompt&name=ghost-persona',
});
expect(res.statusCode).toBe(404);
});
it('returns the active section text for behavioral-spec-section', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline?kind=behavioral-spec-section&name=coreLoop',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(typeof body.baseline).toBe('string');
expect(body.baseline.length).toBeGreaterThan(0);
// coreLoop has identifiable content
expect(body.baseline).toMatch(/HOW YOU THINK|Core Loop|Step 1/i);
});
it('returns 404 for unknown section', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline?kind=behavioral-spec-section&name=nopeSection',
});
expect(res.statusCode).toBe(404);
});
it('returns 400 for unknown kind', async () => {
const res = await injectWithAuth(server, {
method: 'GET', url: '/api/evolution/baseline?kind=tool-description&name=x',
});
expect(res.statusCode).toBe(400);
});
});
});