read-memory-block.test.js•15 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
handleReadMemoryBlock,
readMemoryBlockToolDefinition,
} from '../../../tools/memory/read-memory-block.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('Read Memory Block', () => {
let mockServer;
beforeEach(() => {
mockServer = createMockLettaServer();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Tool Definition', () => {
it('should have correct tool definition', () => {
expect(readMemoryBlockToolDefinition.name).toBe('read_memory_block');
expect(readMemoryBlockToolDefinition.description).toContain(
'Get full details of a specific memory block',
);
expect(readMemoryBlockToolDefinition.inputSchema.required).toEqual(['block_id']);
expect(readMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('block_id');
expect(readMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('agent_id');
});
});
describe('Functionality Tests', () => {
it('should read memory block successfully', async () => {
const mockBlock = {
id: 'block-123',
name: 'Test Block',
label: 'persona',
value: 'I am a helpful AI assistant with extensive knowledge and capabilities',
limit: 5000,
is_template: false,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-02T00:00:00Z',
metadata: {
type: 'persona',
version: '1.0',
tags: ['ai', 'assistant'],
},
};
mockServer.api.get.mockResolvedValueOnce({ data: mockBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'block-123',
});
// Verify API call
expect(mockServer.api.get).toHaveBeenCalledWith(
'/blocks/block-123',
expect.objectContaining({
headers: expect.any(Object),
}),
);
// Verify response contains all block data
const data = expectValidToolResponse(result);
expect(data.id).toBe('block-123');
expect(data.name).toBe('Test Block');
expect(data.label).toBe('persona');
expect(data.value).toBe(
'I am a helpful AI assistant with extensive knowledge and capabilities',
);
expect(data.limit).toBe(5000);
expect(data.is_template).toBe(false);
expect(data.metadata).toEqual({
type: 'persona',
version: '1.0',
tags: ['ai', 'assistant'],
});
});
it('should read memory block with agent_id authorization', async () => {
const agentId = 'agent-456';
const mockBlock = {
id: 'agent-block-123',
name: 'Agent Specific Block',
label: 'human',
value: 'User preferences for this agent',
limit: 2000,
};
mockServer.api.get.mockResolvedValueOnce({ data: mockBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'agent-block-123',
agent_id: agentId,
});
// Verify user_id header was included
expect(mockServer.api.get).toHaveBeenCalledWith(
'/blocks/agent-block-123',
expect.objectContaining({
headers: expect.objectContaining({
user_id: agentId,
}),
}),
);
const data = expectValidToolResponse(result);
expect(data.id).toBe('agent-block-123');
});
it('should handle blocks with minimal data', async () => {
const minimalBlock = {
id: 'minimal-block',
value: 'Minimal content',
// Only required fields
};
mockServer.api.get.mockResolvedValueOnce({ data: minimalBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'minimal-block',
});
const data = expectValidToolResponse(result);
expect(data.id).toBe('minimal-block');
expect(data.value).toBe('Minimal content');
});
it('should handle template blocks', async () => {
const templateBlock = {
id: 'template-123',
name: 'Template Block',
label: 'system',
value: 'This is a template for system prompts',
is_template: true,
template_variables: ['user_name', 'system_role'],
};
mockServer.api.get.mockResolvedValueOnce({ data: templateBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'template-123',
});
const data = expectValidToolResponse(result);
expect(data.is_template).toBe(true);
expect(data.template_variables).toEqual(['user_name', 'system_role']);
});
it('should handle blocks with complex metadata', async () => {
const complexBlock = {
id: 'complex-123',
name: 'Complex Block',
label: 'custom',
value: 'Complex content',
metadata: {
nested: {
level1: {
level2: 'deep value',
},
},
array_data: [1, 2, 3, 'text'],
boolean_flag: true,
null_value: null,
},
};
mockServer.api.get.mockResolvedValueOnce({ data: complexBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'complex-123',
});
const data = expectValidToolResponse(result);
expect(data.metadata.nested.level1.level2).toBe('deep value');
expect(data.metadata.array_data).toEqual([1, 2, 3, 'text']);
expect(data.metadata.boolean_flag).toBe(true);
expect(data.metadata.null_value).toBeNull();
});
it('should handle very large memory blocks', async () => {
const largeValue = 'X'.repeat(10000); // 10k characters
const largeBlock = {
id: 'large-block',
name: 'Large Block',
label: 'data',
value: largeValue,
limit: 10000,
};
mockServer.api.get.mockResolvedValueOnce({ data: largeBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'large-block',
});
const data = expectValidToolResponse(result);
expect(data.value).toBe(largeValue);
expect(data.value.length).toBe(10000);
});
it('should handle blocks with special characters', async () => {
const specialBlock = {
id: 'special-block',
name: 'Special "Characters" Block',
label: 'test',
value: 'Content with special chars: \n\t"quotes" \'apostrophes\' & symbols < > {}',
metadata: {
emoji: '🤖💬🔧',
unicode: 'こんにちは世界',
},
};
mockServer.api.get.mockResolvedValueOnce({ data: specialBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'special-block',
});
const data = expectValidToolResponse(result);
expect(data.value).toContain('\n\t"quotes"');
expect(data.metadata.emoji).toBe('🤖💬🔧');
expect(data.metadata.unicode).toBe('こんにちは世界');
});
it('should handle blocks associated with multiple agents', async () => {
const sharedBlock = {
id: 'shared-block',
name: 'Shared Configuration',
label: 'system',
value: 'Shared system configuration',
agents: [
{ id: 'agent-1', name: 'Agent One' },
{ id: 'agent-2', name: 'Agent Two' },
{ id: 'agent-3', name: 'Agent Three' },
],
shared: true,
};
mockServer.api.get.mockResolvedValueOnce({ data: sharedBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'shared-block',
});
const data = expectValidToolResponse(result);
expect(data.agents).toHaveLength(3);
expect(data.shared).toBe(true);
});
});
describe('Error Handling', () => {
it('should throw error for missing block_id', async () => {
await expect(handleReadMemoryBlock(mockServer, {})).rejects.toThrow(
'Missing required argument: block_id',
);
});
it('should throw error for null block_id', async () => {
await expect(handleReadMemoryBlock(mockServer, { block_id: null })).rejects.toThrow(
'Missing required argument: block_id',
);
});
it('should throw error for undefined args', async () => {
await expect(handleReadMemoryBlock(mockServer, undefined)).rejects.toThrow(
'Missing required argument: block_id',
);
});
it('should handle 404 error when block not found', async () => {
const error = new Error('Not found');
error.response = {
status: 404,
data: { error: 'Memory block not found' },
};
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'non-existent-block',
}),
).rejects.toThrow('Not found');
});
it('should handle 401 unauthorized error', async () => {
const error = new Error('Unauthorized');
error.response = {
status: 401,
data: { error: 'Unauthorized access to memory block' },
};
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'protected-block',
}),
).rejects.toThrow('Unauthorized');
});
it('should handle 403 forbidden error with agent_id', async () => {
const error = new Error('Forbidden');
error.response = {
status: 403,
data: { error: 'Agent does not have access to this block' },
};
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'forbidden-block',
agent_id: 'agent-no-access',
}),
).rejects.toThrow('Forbidden');
});
it('should handle network errors', async () => {
const error = new Error('Network error: Connection refused');
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'block-123',
}),
).rejects.toThrow('Network error');
});
it('should handle server errors', async () => {
const error = new Error('Internal server error');
error.response = {
status: 500,
data: { error: 'Database connection failed' },
};
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'block-123',
}),
).rejects.toThrow('Internal server error');
});
it('should handle malformed response', async () => {
// API returns null data
mockServer.api.get.mockResolvedValueOnce({ data: null });
const result = await handleReadMemoryBlock(mockServer, {
block_id: 'null-block',
});
// Should still return the null as valid response
const content = result.content[0];
expect(content.text).toBe('null');
});
});
describe('Edge Cases', () => {
it('should handle empty string block_id gracefully', async () => {
await expect(
handleReadMemoryBlock(mockServer, {
block_id: '',
}),
).rejects.toThrow('Missing required argument: block_id');
});
it('should handle blocks with circular references in metadata', async () => {
const blockData = {
id: 'circular-block',
name: 'Circular Block',
label: 'test',
value: 'Content',
metadata: {
ref: null,
},
};
// Create circular reference
blockData.metadata.ref = blockData.metadata;
// Mock will throw when trying to serialize circular structure
mockServer.api.get.mockResolvedValueOnce({ data: blockData });
await expect(
handleReadMemoryBlock(mockServer, {
block_id: 'circular-block',
}),
).rejects.toThrow();
});
it('should handle UUID format block IDs', async () => {
const uuidBlockId = '550e8400-e29b-41d4-a716-446655440000';
const mockBlock = {
id: uuidBlockId,
name: 'UUID Block',
label: 'test',
value: 'Content with UUID',
};
mockServer.api.get.mockResolvedValueOnce({ data: mockBlock });
const result = await handleReadMemoryBlock(mockServer, {
block_id: uuidBlockId,
});
expect(mockServer.api.get).toHaveBeenCalledWith(
`/blocks/${uuidBlockId}`,
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.id).toBe(uuidBlockId);
});
});
});