create-memory-block.test.js•15 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
handleCreateMemoryBlock,
createMemoryBlockToolDefinition,
} from '../../../tools/memory/create-memory-block.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { fixtures } from '../../utils/test-fixtures.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('Create Memory Block', () => {
let mockServer;
beforeEach(() => {
mockServer = createMockLettaServer();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Tool Definition', () => {
it('should have correct tool definition', () => {
expect(createMemoryBlockToolDefinition.name).toBe('create_memory_block');
expect(createMemoryBlockToolDefinition.description).toContain(
'Create a new memory block',
);
expect(createMemoryBlockToolDefinition.inputSchema.required).toEqual([
'name',
'label',
'value',
]);
expect(createMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('name');
expect(createMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('label');
expect(createMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty('value');
expect(createMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty(
'agent_id',
);
expect(createMemoryBlockToolDefinition.inputSchema.properties).toHaveProperty(
'metadata',
);
});
});
describe('Functionality Tests', () => {
it('should create memory block successfully with minimal args', async () => {
const createdBlock = {
...fixtures.memory.block,
id: 'new-block-123',
name: 'Test Block',
label: 'persona',
value: 'I am a helpful assistant',
};
// Mock successful block creation
mockServer.api.post.mockResolvedValueOnce({ data: createdBlock });
const result = await handleCreateMemoryBlock(mockServer, {
name: 'Test Block',
label: 'persona',
value: 'I am a helpful assistant',
});
// Verify API call
expect(mockServer.api.post).toHaveBeenCalledWith(
'/blocks',
expect.objectContaining({
name: 'Test Block',
label: 'persona',
value: 'I am a helpful assistant',
metadata: expect.objectContaining({
type: 'persona',
version: '1.0',
last_updated: expect.any(String),
}),
}),
expect.objectContaining({ headers: expect.any(Object) }),
);
// Verify response
const data = expectValidToolResponse(result);
expect(data.block_id).toBe('new-block-123');
expect(data.name).toBe('Test Block');
expect(data.label).toBe('persona');
expect(data.agent_id).toBeUndefined();
});
it('should create memory block with custom metadata', async () => {
const customMetadata = {
custom_field: 'custom_value',
version: '2.0',
tags: ['important', 'user-defined'],
};
const createdBlock = {
id: 'custom-block-123',
name: 'Custom Block',
label: 'human',
value: 'User information',
metadata: customMetadata,
};
mockServer.api.post.mockResolvedValueOnce({ data: createdBlock });
const result = await handleCreateMemoryBlock(mockServer, {
name: 'Custom Block',
label: 'human',
value: 'User information',
metadata: customMetadata,
});
// Verify metadata was passed correctly
expect(mockServer.api.post).toHaveBeenCalledWith(
'/blocks',
expect.objectContaining({
metadata: customMetadata,
}),
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.block_id).toBe('custom-block-123');
});
it('should create and attach memory block to agent', async () => {
const agentId = 'agent-789';
const blockId = 'attached-block-123';
const agentInfo = {
id: agentId,
name: 'Test Agent',
};
// Mock block creation
mockServer.api.post.mockResolvedValueOnce({
data: { id: blockId },
});
// Mock attachment (patch returns empty)
mockServer.api.patch.mockResolvedValueOnce({ data: {} });
// Mock agent info retrieval
mockServer.api.get.mockResolvedValueOnce({
data: agentInfo,
});
const result = await handleCreateMemoryBlock(mockServer, {
name: 'Agent Memory',
label: 'system',
value: 'System configuration',
agent_id: agentId,
});
// Verify user_id header was set
expect(mockServer.api.post).toHaveBeenCalledWith(
'/blocks',
expect.any(Object),
expect.objectContaining({
headers: expect.objectContaining({
user_id: agentId,
}),
}),
);
// Verify attachment call
expect(mockServer.api.patch).toHaveBeenCalledWith(
`/agents/${agentId}/core-memory/blocks/attach/${blockId}`,
{},
expect.objectContaining({
headers: expect.objectContaining({
user_id: agentId,
}),
}),
);
// Verify agent info retrieval
expect(mockServer.api.get).toHaveBeenCalledWith(
`/agents/${agentId}`,
expect.objectContaining({
headers: expect.objectContaining({
user_id: agentId,
}),
}),
);
// Verify response includes agent info
const data = expectValidToolResponse(result);
expect(data.block_id).toBe(blockId);
expect(data.agent_id).toBe(agentId);
expect(data.agent_name).toBe('Test Agent');
});
it('should handle agent without name when attaching', async () => {
const agentId = 'agent-no-name';
const blockId = 'block-no-name';
mockServer.api.post.mockResolvedValueOnce({
data: { id: blockId },
});
mockServer.api.patch.mockResolvedValueOnce({ data: {} });
mockServer.api.get.mockResolvedValueOnce({
data: { id: agentId }, // No name field
});
const result = await handleCreateMemoryBlock(mockServer, {
name: 'No Name Block',
label: 'persona',
value: 'Content',
agent_id: agentId,
});
const data = expectValidToolResponse(result);
expect(data.agent_name).toBe('Unknown');
});
it('should handle very long memory block values', async () => {
const longValue = 'A'.repeat(10000); // 10k characters
const createdBlock = {
id: 'long-block-123',
name: 'Long Block',
label: 'system',
value: longValue,
limit: 10000,
};
mockServer.api.post.mockResolvedValueOnce({ data: createdBlock });
const result = await handleCreateMemoryBlock(mockServer, {
name: 'Long Block',
label: 'system',
value: longValue,
});
expect(mockServer.api.post).toHaveBeenCalledWith(
'/blocks',
expect.objectContaining({
value: longValue,
}),
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.block_id).toBe('long-block-123');
});
});
describe('Error Handling', () => {
it('should throw error for missing name', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
label: 'persona',
value: 'Some value',
}),
).rejects.toThrow('Missing required argument: name');
});
it('should throw error for non-string name', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
name: 123,
label: 'persona',
value: 'Some value',
}),
).rejects.toThrow('Missing required argument: name (must be a string)');
});
it('should throw error for missing label', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Test Block',
value: 'Some value',
}),
).rejects.toThrow('Missing required argument: label');
});
it('should throw error for non-string label', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Test Block',
label: ['invalid'],
value: 'Some value',
}),
).rejects.toThrow('Missing required argument: label (must be a string)');
});
it('should throw error for missing value', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Test Block',
label: 'persona',
}),
).rejects.toThrow('Missing required argument: value');
});
it('should throw error for non-string value', async () => {
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Test Block',
label: 'persona',
value: { invalid: 'object' },
}),
).rejects.toThrow('Missing required argument: value (must be a string)');
});
it('should handle API error during block creation', async () => {
const error = new Error('Server error');
error.response = { status: 500, data: { error: 'Internal server error' } };
mockServer.api.post.mockRejectedValueOnce(error);
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Failed Block',
label: 'persona',
value: 'This will fail',
}),
).rejects.toThrow('Server error');
});
it('should handle API error during attachment', async () => {
const agentId = 'agent-fail';
const blockId = 'block-fail';
// Block creation succeeds
mockServer.api.post.mockResolvedValueOnce({
data: { id: blockId },
});
// Attachment fails
const error = new Error('Failed to attach');
error.response = { status: 404, data: { error: 'Agent not found' } };
mockServer.api.patch.mockRejectedValueOnce(error);
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Attach Fail Block',
label: 'persona',
value: 'Will fail on attach',
agent_id: agentId,
}),
).rejects.toThrow('Failed to attach');
});
it('should handle API error when retrieving agent info', async () => {
const agentId = 'agent-info-fail';
const blockId = 'block-info-fail';
// Block creation succeeds
mockServer.api.post.mockResolvedValueOnce({
data: { id: blockId },
});
// Attachment succeeds
mockServer.api.patch.mockResolvedValueOnce({ data: {} });
// Agent info retrieval fails
const error = new Error('Failed to get agent');
error.response = { status: 404 };
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handleCreateMemoryBlock(mockServer, {
name: 'Info Fail Block',
label: 'persona',
value: 'Will fail on agent info',
agent_id: agentId,
}),
).rejects.toThrow('Failed to get agent');
});
});
describe('Memory Block Limits', () => {
it('should handle blocks at maximum character limit', async () => {
const maxValue = 'X'.repeat(5000); // Assuming 5000 is the default limit
const createdBlock = {
id: 'max-block-123',
name: 'Max Block',
label: 'system',
value: maxValue,
limit: 5000,
};
mockServer.api.post.mockResolvedValueOnce({ data: createdBlock });
const result = await handleCreateMemoryBlock(mockServer, {
name: 'Max Block',
label: 'system',
value: maxValue,
});
const data = expectValidToolResponse(result);
expect(data.block_id).toBe('max-block-123');
});
it('should create blocks with different labels', async () => {
const labels = ['persona', 'human', 'system', 'custom_label'];
for (const label of labels) {
const createdBlock = {
id: `block-${label}`,
name: `${label} Block`,
label: label,
value: `Content for ${label}`,
};
mockServer.api.post.mockResolvedValueOnce({ data: createdBlock });
const result = await handleCreateMemoryBlock(mockServer, {
name: `${label} Block`,
label: label,
value: `Content for ${label}`,
});
const data = expectValidToolResponse(result);
expect(data.label).toBe(label);
}
});
});
});