This commit is contained in:
133
packages/server/tests/routes/approval-flow.test.ts
Normal file
133
packages/server/tests/routes/approval-flow.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
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 } from '@waggle/core';
|
||||
import { buildLocalServer } from '../../src/local/index.js';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { injectWithAuth } from '../test-utils.js';
|
||||
|
||||
/**
|
||||
* Tests the server-side approval flow:
|
||||
* pending approval → POST approve → promise resolves
|
||||
*
|
||||
* This proves the HTTP POST path that the UI's approveAction() calls.
|
||||
*/
|
||||
describe('Approval Flow — Server Side', () => {
|
||||
let server: FastifyInstance;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-approval-test-'));
|
||||
const personalPath = path.join(tmpDir, 'personal.mind');
|
||||
const mind = new MindDB(personalPath);
|
||||
mind.close();
|
||||
server = await buildLocalServer({ dataDir: tmpDir });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.close();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('POST /api/approval/:id resolves a pending approval promise', async () => {
|
||||
const requestId = 'test-approval-123';
|
||||
let resolved = false;
|
||||
let approvedValue: boolean | undefined;
|
||||
|
||||
// Simulate what the chat route does: create a pending approval
|
||||
const approvalPromise = new Promise<boolean>((resolve) => {
|
||||
server.agentState.pendingApprovals.set(requestId, {
|
||||
resolve,
|
||||
toolName: 'install_capability',
|
||||
input: { name: 'risk-assessment', source: 'starter-pack' },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
approvalPromise.then((v) => { resolved = true; approvedValue = v; });
|
||||
|
||||
// Verify it's pending
|
||||
expect(server.agentState.pendingApprovals.has(requestId)).toBe(true);
|
||||
|
||||
// Simulate what the UI does: POST approval
|
||||
const res = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `/api/approval/${requestId}`,
|
||||
payload: { approved: true },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.payload);
|
||||
expect(body.ok).toBe(true);
|
||||
expect(body.approved).toBe(true);
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const result = await approvalPromise;
|
||||
expect(result).toBe(true);
|
||||
expect(resolved).toBe(true);
|
||||
expect(approvedValue).toBe(true);
|
||||
|
||||
// Pending approval should be cleaned up
|
||||
expect(server.agentState.pendingApprovals.has(requestId)).toBe(false);
|
||||
});
|
||||
|
||||
it('POST /api/approval/:id with approved=false denies', async () => {
|
||||
const requestId = 'test-deny-456';
|
||||
|
||||
const approvalPromise = new Promise<boolean>((resolve) => {
|
||||
server.agentState.pendingApprovals.set(requestId, {
|
||||
resolve,
|
||||
toolName: 'install_capability',
|
||||
input: { name: 'risk-assessment', source: 'starter-pack' },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
const res = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `/api/approval/${requestId}`,
|
||||
payload: { approved: false },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = await approvalPromise;
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('POST /api/approval/:id returns 404 for unknown requestId', async () => {
|
||||
const res = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: '/api/approval/nonexistent-id',
|
||||
payload: { approved: true },
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('GET /api/approval/pending lists pending approvals', async () => {
|
||||
const requestId = 'test-pending-789';
|
||||
|
||||
server.agentState.pendingApprovals.set(requestId, {
|
||||
resolve: () => {},
|
||||
toolName: 'install_capability',
|
||||
input: { name: 'daily-plan', source: 'starter-pack' },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
const res = await injectWithAuth(server, {
|
||||
method: 'GET',
|
||||
url: '/api/approval/pending',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.payload);
|
||||
expect(body.count).toBeGreaterThanOrEqual(1);
|
||||
const found = body.pending.find((p: { requestId: string }) => p.requestId === requestId);
|
||||
expect(found).toBeDefined();
|
||||
expect(found.toolName).toBe('install_capability');
|
||||
|
||||
// Cleanup
|
||||
server.agentState.pendingApprovals.delete(requestId);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user