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,174 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { MindDB, FrameStore, SessionStore } from '@waggle/core';
import { buildLocalServer } from '../../src/local/index.js';
import type { FastifyInstance } from 'fastify';
import { injectWithAuth } from '../test-utils.js';
describe('Permission settings API', () => {
let server: FastifyInstance;
let tmpDir: string;
beforeAll(async () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-perms-test-'));
// Create personal.mind (required by server startup)
const personalPath = path.join(tmpDir, 'personal.mind');
const mind = new MindDB(personalPath);
const sessions = new SessionStore(mind);
const frames = new FrameStore(mind);
const s1 = sessions.create('test-project');
frames.createIFrame(s1.gop_id, 'test content', 'normal');
mind.close();
server = await buildLocalServer({ dataDir: tmpDir });
});
afterAll(async () => {
await server.close();
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('GET /api/settings/permissions returns defaults', async () => {
const res = await injectWithAuth(server, {
method: 'GET',
url: '/api/settings/permissions',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.defaultAutonomy).toBe('normal');
expect(body.externalGates).toEqual([]);
expect(body.workspaceOverrides).toEqual({});
// P4: dead yoloMode field is gone from responses
expect(body.yoloMode).toBeUndefined();
});
it('PUT /api/settings/permissions persists defaultAutonomy change', async () => {
const putRes = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: {
defaultAutonomy: 'trusted',
externalGates: ['git push', 'rm -rf'],
workspaceOverrides: { 'ws-1': ['deploy'] },
},
});
expect(putRes.statusCode).toBe(200);
const putBody = JSON.parse(putRes.body);
expect(putBody.defaultAutonomy).toBe('trusted');
expect(putBody.externalGates).toEqual(['git push', 'rm -rf']);
expect(putBody.workspaceOverrides).toEqual({ 'ws-1': ['deploy'] });
// Read back to verify persistence
const getRes = await injectWithAuth(server, {
method: 'GET',
url: '/api/settings/permissions',
});
expect(getRes.statusCode).toBe(200);
const getBody = JSON.parse(getRes.body);
expect(getBody.defaultAutonomy).toBe('trusted');
});
it('PUT /api/settings/permissions merges partial updates', async () => {
// First, set known state
await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: {
defaultAutonomy: 'yolo',
externalGates: ['git push'],
workspaceOverrides: {},
},
});
// Now update only externalGates
const putRes = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: {
externalGates: ['curl POST', 'deploy'],
},
});
expect(putRes.statusCode).toBe(200);
const body = JSON.parse(putRes.body);
// defaultAutonomy should be preserved from previous write
expect(body.defaultAutonomy).toBe('yolo');
expect(body.externalGates).toEqual(['curl POST', 'deploy']);
});
it('permissions.json file is created on disk with new schema', async () => {
const permPath = path.join(tmpDir, 'permissions.json');
expect(fs.existsSync(permPath)).toBe(true);
const data = JSON.parse(fs.readFileSync(permPath, 'utf-8'));
expect(data).toHaveProperty('defaultAutonomy');
expect(data).toHaveProperty('externalGates');
expect(data).toHaveProperty('workspaceOverrides');
expect(data.yoloMode).toBeUndefined();
});
// ── P4: legacy migration & validation ───────────────────────────────
it('PUT accepts legacy yoloMode=true body and migrates to defaultAutonomy="yolo"', async () => {
const res = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: { yoloMode: true },
});
expect(res.statusCode).toBe(200);
expect(JSON.parse(res.body).defaultAutonomy).toBe('yolo');
});
it('PUT accepts legacy yoloMode=false and migrates to defaultAutonomy="normal"', async () => {
const res = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: { yoloMode: false },
});
expect(res.statusCode).toBe(200);
expect(JSON.parse(res.body).defaultAutonomy).toBe('normal');
});
it('PUT with both fields prefers the explicit defaultAutonomy over legacy yoloMode', async () => {
const res = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: { defaultAutonomy: 'trusted', yoloMode: true },
});
expect(res.statusCode).toBe(200);
expect(JSON.parse(res.body).defaultAutonomy).toBe('trusted');
});
it('PUT rejects invalid defaultAutonomy values with 400', async () => {
const res = await injectWithAuth(server, {
method: 'PUT',
url: '/api/settings/permissions',
payload: { defaultAutonomy: 'wild' as unknown as 'normal' },
});
expect(res.statusCode).toBe(400);
const body = JSON.parse(res.body);
expect(body.error).toContain('defaultAutonomy must be one of');
});
it('GET migrates legacy permissions.json with yoloMode=true to defaultAutonomy="yolo"', async () => {
// Simulate a file that predates P4 by writing raw yoloMode=true
const permPath = path.join(tmpDir, 'permissions.json');
fs.writeFileSync(
permPath,
JSON.stringify({ yoloMode: true, externalGates: ['x'], workspaceOverrides: {} }, null, 2),
'utf-8',
);
const res = await injectWithAuth(server, {
method: 'GET',
url: '/api/settings/permissions',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.defaultAutonomy).toBe('yolo');
expect(body.externalGates).toEqual(['x']);
// Response should NOT carry the stale field forward
expect(body.yoloMode).toBeUndefined();
});
});