delete-passage.test.js•16.9 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
handleDeletePassage,
deletePassageDefinition,
} from '../../../tools/passages/delete-passage.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('Delete Passage', () => {
let mockServer;
beforeEach(() => {
mockServer = createMockLettaServer();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Tool Definition', () => {
it('should have correct tool definition', () => {
expect(deletePassageDefinition.name).toBe('delete_passage');
expect(deletePassageDefinition.description).toContain(
"Delete a memory from an agent's archival memory",
);
expect(deletePassageDefinition.description).toContain(
'WARNING: This action is permanent',
);
expect(deletePassageDefinition.inputSchema.required).toEqual(['agent_id', 'memory_id']);
expect(deletePassageDefinition.inputSchema.properties).toHaveProperty('agent_id');
expect(deletePassageDefinition.inputSchema.properties).toHaveProperty('memory_id');
});
});
describe('Functionality Tests', () => {
it('should delete passage successfully', async () => {
const agentId = 'agent-123';
const memoryId = 'passage-456';
// Mock successful deletion (returns empty response)
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
// Verify API call
expect(mockServer.api.delete).toHaveBeenCalledWith(
`/agents/${agentId}/archival-memory/${memoryId}`,
expect.objectContaining({
headers: expect.any(Object),
}),
);
// Verify response contains IDs
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryId);
expect(data.agent_id).toBe(agentId);
});
it('should handle special characters in agent ID', async () => {
const agentId = 'agent@special#id';
const encodedAgentId = encodeURIComponent(agentId);
const memoryId = 'passage-123';
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
expect(mockServer.api.delete).toHaveBeenCalledWith(
`/agents/${encodedAgentId}/archival-memory/${memoryId}`,
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe(agentId);
});
it('should handle special characters in memory ID', async () => {
const agentId = 'agent-123';
const memoryId = 'passage@special#id';
const encodedMemoryId = encodeURIComponent(memoryId);
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
expect(mockServer.api.delete).toHaveBeenCalledWith(
`/agents/${agentId}/archival-memory/${encodedMemoryId}`,
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryId);
});
it('should handle UUID format IDs', async () => {
const agentId = '550e8400-e29b-41d4-a716-446655440000';
const memoryId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
expect(mockServer.api.delete).toHaveBeenCalledWith(
`/agents/${agentId}/archival-memory/${memoryId}`,
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe(agentId);
expect(data.memory_id).toBe(memoryId);
});
it('should handle 204 No Content response', async () => {
const agentId = 'agent-204';
const memoryId = 'passage-204';
// Mock 204 response (no content)
mockServer.api.delete.mockResolvedValueOnce({
data: null,
status: 204,
statusText: 'No Content',
});
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryId);
expect(data.agent_id).toBe(agentId);
});
it('should delete multiple passages in sequence', async () => {
const agentId = 'agent-multi';
const memoryIds = ['passage-1', 'passage-2', 'passage-3'];
for (const memoryId of memoryIds) {
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryId);
}
expect(mockServer.api.delete).toHaveBeenCalledTimes(3);
});
it('should handle deletion with complex IDs', async () => {
const agentId = 'agent_123-456.test';
const memoryId = 'passage_789-012.data';
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe(agentId);
expect(data.memory_id).toBe(memoryId);
});
});
describe('Error Handling', () => {
it('should handle missing agent_id', async () => {
await expect(
handleDeletePassage(mockServer, {
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
'Missing required argument: agent_id',
);
});
it('should handle missing memory_id', async () => {
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
'Missing required argument: memory_id',
);
});
it('should handle null args', async () => {
await expect(handleDeletePassage(mockServer, null)).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
'Missing required argument: agent_id',
);
});
it('should handle 404 not found error', async () => {
const agentId = 'agent-123';
const memoryId = 'non-existent-passage';
const error = new Error('Not found');
error.response = {
status: 404,
data: { error: 'Passage not found' },
};
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
`Agent or Passage not found: agent_id=${agentId}, memory_id=${memoryId}`,
);
});
it('should handle 401 unauthorized error', async () => {
const error = new Error('Unauthorized');
error.response = {
status: 401,
data: { error: 'Invalid authentication' },
};
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
it('should handle 403 forbidden error', async () => {
const error = new Error('Forbidden');
error.response = {
status: 403,
data: { error: 'Cannot delete this passage' },
};
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
it('should handle 409 conflict error', async () => {
const error = new Error('Conflict');
error.response = {
status: 409,
data: { error: 'Passage is in use and cannot be deleted' },
};
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
it('should handle server errors', async () => {
const error = new Error('Internal server error');
error.response = {
status: 500,
data: { error: 'Database error during deletion' },
};
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
it('should handle network errors without response', async () => {
const error = new Error('Network error: Connection refused');
// No response property
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
it('should handle timeout errors', async () => {
const error = new Error('Request timeout');
error.code = 'ECONNABORTED';
mockServer.api.delete.mockRejectedValueOnce(error);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(error);
});
});
describe('Edge Cases', () => {
it('should handle empty string IDs gracefully', async () => {
await expect(
handleDeletePassage(mockServer, {
agent_id: '',
memory_id: 'passage-123',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
'Missing required argument: agent_id',
);
await expect(
handleDeletePassage(mockServer, {
agent_id: 'agent-123',
memory_id: '',
}),
).rejects.toThrow();
expect(mockServer.createErrorResponse).toHaveBeenCalledWith(
'Missing required argument: memory_id',
);
});
it('should handle very long IDs', async () => {
const longAgentId = 'agent-' + 'x'.repeat(1000);
const longMemoryId = 'passage-' + 'y'.repeat(1000);
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: longAgentId,
memory_id: longMemoryId,
});
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe(longAgentId);
expect(data.memory_id).toBe(longMemoryId);
});
it('should handle IDs with unicode characters', async () => {
const agentId = 'agent-你好-🚀';
const memoryId = 'passage-世界-💬';
const encodedAgentId = encodeURIComponent(agentId);
const encodedMemoryId = encodeURIComponent(memoryId);
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
expect(mockServer.api.delete).toHaveBeenCalledWith(
`/agents/${encodedAgentId}/archival-memory/${encodedMemoryId}`,
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe(agentId);
expect(data.memory_id).toBe(memoryId);
});
it('should handle deletion with unexpected response data', async () => {
const agentId = 'agent-unexpected';
const memoryId = 'passage-unexpected';
// API returns unexpected data structure
mockServer.api.delete.mockResolvedValueOnce({
data: {
success: true,
deleted_count: 1,
message: 'Passage deleted successfully',
},
});
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
// Should still return standard response
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryId);
expect(data.agent_id).toBe(agentId);
});
it('should handle rapid successive deletions', async () => {
const agentId = 'agent-rapid';
const memoryIds = Array.from({ length: 10 }, (_, i) => `passage-${i}`);
// Mock all deletions
memoryIds.forEach(() => {
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
});
// Delete all passages rapidly
const promises = memoryIds.map((memoryId) =>
handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
}),
);
const results = await Promise.all(promises);
// Verify all deletions succeeded
results.forEach((result, index) => {
const data = expectValidToolResponse(result);
expect(data.memory_id).toBe(memoryIds[index]);
});
expect(mockServer.api.delete).toHaveBeenCalledTimes(10);
});
it('should handle case-sensitive IDs correctly', async () => {
const agentId = 'Agent-ABC';
const memoryId = 'Passage-XYZ';
mockServer.api.delete.mockResolvedValueOnce({ data: {} });
const result = await handleDeletePassage(mockServer, {
agent_id: agentId,
memory_id: memoryId,
});
// Should preserve case
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe('Agent-ABC');
expect(data.memory_id).toBe('Passage-XYZ');
});
});
});