This commit is contained in:
174
packages/server/tests/local/settings-permissions.test.ts
Normal file
174
packages/server/tests/local/settings-permissions.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user