Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
confirmation-flow.test.ts9.76 kB
// ============================================================================ // Confirmation Flow Tests // Tests for confirmation token utilities and destructive operation workflows // ============================================================================ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { generateConfirmationToken, validateConfirmationToken, consumeConfirmationToken, getConfirmationStats } from '../src/tools/confirmation.utils.js'; import { handleMemoryClear } from '../src/tools/graph.handlers.js'; import { MockGraphManager } from './helpers/mockGraphManager.js'; describe('Confirmation Token Utilities', () => { beforeEach(() => { // Clear any existing tokens between tests const stats = getConfirmationStats(); // Tokens will naturally expire or be consumed during tests }); describe('generateConfirmationToken', () => { it('should generate unique tokens for same action', () => { const token1 = generateConfirmationToken('test_action', { param: 'value' }); const token2 = generateConfirmationToken('test_action', { param: 'value' }); expect(token1).toBeTruthy(); expect(token2).toBeTruthy(); expect(token1).not.toBe(token2); expect(token1).toHaveLength(32); // 16 bytes = 32 hex chars }); it('should generate different tokens for different actions', () => { const token1 = generateConfirmationToken('action_1', { key: 'value' }); const token2 = generateConfirmationToken('action_2', { key: 'value' }); expect(token1).not.toBe(token2); }); it('should generate different tokens for different params', () => { const token1 = generateConfirmationToken('action', { param: 'value1' }); const token2 = generateConfirmationToken('action', { param: 'value2' }); expect(token1).not.toBe(token2); }); }); describe('validateConfirmationToken', () => { it('should validate correct token with matching action and params', () => { const action = 'delete_node'; const params = { id: 'node-123', cascade: true }; const token = generateConfirmationToken(action, params); const isValid = validateConfirmationToken(token, action, params); expect(isValid).toBe(true); }); it('should reject token with wrong action', () => { const token = generateConfirmationToken('action_1', { key: 'value' }); const isValid = validateConfirmationToken(token, 'action_2', { key: 'value' }); expect(isValid).toBe(false); }); it('should reject token with changed params', () => { const token = generateConfirmationToken('action', { param: 'original' }); const isValid = validateConfirmationToken(token, 'action', { param: 'changed' }); expect(isValid).toBe(false); }); it('should reject non-existent token', () => { const isValid = validateConfirmationToken('fake-token-12345', 'action', {}); expect(isValid).toBe(false); }); it('should reject expired token', async () => { // Mock Date.now to simulate token expiry const originalNow = Date.now; const startTime = Date.now(); const token = generateConfirmationToken('action', { key: 'value' }); // Fast-forward time by 6 minutes (past 5-minute expiry) vi.spyOn(Date, 'now').mockReturnValue(startTime + 6 * 60 * 1000); const isValid = validateConfirmationToken(token, 'action', { key: 'value' }); expect(isValid).toBe(false); // Restore Date.now vi.restoreAllMocks(); }); }); describe('consumeConfirmationToken', () => { it('should make token invalid after consumption', () => { const action = 'action'; const params = { key: 'value' }; const token = generateConfirmationToken(action, params); // Token should be valid before consumption expect(validateConfirmationToken(token, action, params)).toBe(true); // Consume token consumeConfirmationToken(token); // Token should be invalid after consumption expect(validateConfirmationToken(token, action, params)).toBe(false); }); it('should handle consuming non-existent token gracefully', () => { expect(() => consumeConfirmationToken('fake-token')).not.toThrow(); }); }); describe('getConfirmationStats', () => { it('should return zero pending when no tokens exist', () => { const stats = getConfirmationStats(); expect(stats.pending).toBeGreaterThanOrEqual(0); }); it('should track pending confirmations', () => { const statsBefore = getConfirmationStats(); generateConfirmationToken('action1', {}); generateConfirmationToken('action2', {}); const statsAfter = getConfirmationStats(); expect(statsAfter.pending).toBeGreaterThanOrEqual(statsBefore.pending + 2); }); it('should decrease pending count after consumption', () => { const token = generateConfirmationToken('action', {}); const statsBefore = getConfirmationStats(); consumeConfirmationToken(token); const statsAfter = getConfirmationStats(); expect(statsAfter.pending).toBe(statsBefore.pending - 1); }); }); }); describe('Confirmation Flow - handleMemoryClear', () => { let mockManager: MockGraphManager; beforeEach(() => { mockManager = new MockGraphManager(); // Add some test nodes mockManager.addNode('todo', { title: 'Test 1' }); mockManager.addNode('todo', { title: 'Test 2' }); mockManager.addNode('memory', { content: 'Memory 1' }); }); it('should return preview without confirm parameter', async () => { const result = await handleMemoryClear({ type: 'ALL' }, mockManager); expect(result.needsConfirmation).toBe(true); expect(result.confirmationId).toBeTruthy(); expect(result.preview).toBeTruthy(); expect(result.preview?.deletedNodes).toBe(3); expect(result.message).toContain('delete ALL'); }); it('should reject execution without confirmationId', async () => { // When confirm=true but confirmationId missing, handler treats it as preview request // (both confirm and confirmationId must be present for execution) const result = await handleMemoryClear({ type: 'ALL', confirm: true }, mockManager); // This should return a preview, not an error expect(result.needsConfirmation).toBe(true); expect(result.confirmationId).toBeTruthy(); }); it('should reject execution with invalid confirmationId', async () => { const result = await handleMemoryClear({ type: 'ALL', confirm: true, confirmationId: 'fake-token-12345' }, mockManager); expect(result.success).toBe(false); expect(result.error).toContain('Invalid or expired'); }); it('should execute clear with valid confirmation', async () => { // Step 1: Get preview const previewResult = await handleMemoryClear({ type: 'ALL' }, mockManager); expect(previewResult.needsConfirmation).toBe(true); const confirmationId = previewResult.confirmationId!; // Step 2: Confirm and execute const executeResult = await handleMemoryClear({ type: 'ALL', confirm: true, confirmationId }, mockManager); expect(executeResult.success).toBe(true); expect((executeResult as any).deletedNodes).toBe(3); expect(executeResult.message).toContain('Cleared'); }); it('should prevent token reuse (one-time use)', async () => { // Get preview const previewResult = await handleMemoryClear({ type: 'ALL' }, mockManager); const confirmationId = previewResult.confirmationId!; // First execution should succeed const firstResult = await handleMemoryClear({ type: 'ALL', confirm: true, confirmationId }, mockManager); expect(firstResult.success).toBe(true); // Recreate nodes for second attempt mockManager.addNode('todo', { title: 'Test' }); // Second execution with same token should fail const secondResult = await handleMemoryClear({ type: 'ALL', confirm: true, confirmationId }, mockManager); expect(secondResult.success).toBe(false); expect(secondResult.error).toContain('Invalid or expired'); }); it('should work for specific node types', async () => { // Preview for clearing todos only const previewResult = await handleMemoryClear({ type: 'todo' }, mockManager); expect(previewResult.preview?.types?.['todo']).toBe(2); // Execute clear const confirmationId = previewResult.confirmationId!; const executeResult = await handleMemoryClear({ type: 'todo', confirm: true, confirmationId }, mockManager); expect(executeResult.success).toBe(true); expect((executeResult as any).deletedNodes).toBe(2); }); it('should handle concurrent confirmation requests', async () => { // Create multiple preview requests const preview1 = await handleMemoryClear({ type: 'ALL' }, mockManager); const preview2 = await handleMemoryClear({ type: 'ALL' }, mockManager); const preview3 = await handleMemoryClear({ type: 'ALL' }, mockManager); // All should have unique tokens expect(preview1.confirmationId).not.toBe(preview2.confirmationId); expect(preview2.confirmationId).not.toBe(preview3.confirmationId); expect(preview1.confirmationId).not.toBe(preview3.confirmationId); // First should work const execute1 = await handleMemoryClear({ type: 'ALL', confirm: true, confirmationId: preview1.confirmationId! }, mockManager); expect(execute1.success).toBe(true); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server