Skip to main content
Glama
getSection.test.ts26.2 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { getSectionTool } from './getSection.js' import { StorageManager } from '../storage/StorageManager.js' import { Memory, MemorySection } from '../types/memory.js' // Mock the StorageManager vi.mock('../storage/StorageManager.js', () => { class StorageManager { readMemory = vi.fn() findSection = vi.fn() parseSections = vi.fn() } return { StorageManager } }) describe('getSection Tool', () => { let storageManager: StorageManager let mockMemory: Memory beforeEach(() => { storageManager = new StorageManager() mockMemory = { metadata: { id: 'test-memory', created: '2025-07-30T12:00:00Z', updated: '2025-07-31T10:00:00Z', status: 'active', tags: ['work', 'project'] }, content: `# Test Memory ## Work Projects - Complete the API integration task - Review code for the authentication module - Update documentation for new features ## Personal Notes The integration work is challenging but rewarding. Need to focus on error handling and edge cases. ## Meeting Notes Discussed project timeline with team. API endpoints need better documentation. Focus on user authentication flow. ## Empty Section ## Another Section This section has some content.`, filePath: '/test/path/test-memory.md' } vi.mocked(storageManager.readMemory).mockResolvedValue(mockMemory) }) afterEach(() => { vi.clearAllMocks() }) describe('Input Validation', () => { it('should throw error when memory_id is missing', async () => { await expect( getSectionTool(storageManager, { section: 'Work Projects' }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when section is missing', async () => { await expect( getSectionTool(storageManager, { memory_id: 'test-memory' }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when both parameters are missing', async () => { await expect( getSectionTool(storageManager, {}) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when memory_id is empty string', async () => { await expect( getSectionTool(storageManager, { memory_id: '', section: 'Work Projects' }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when section is empty string', async () => { await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: '' }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when memory_id is null', async () => { await expect( getSectionTool(storageManager, { memory_id: null, section: 'Work Projects' }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when section is null', async () => { await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: null }) ).rejects.toThrow('Both memory_id and section are required') }) it('should throw error when memory document not found', async () => { vi.mocked(storageManager.readMemory).mockResolvedValue(null) await expect( getSectionTool(storageManager, { memory_id: 'nonexistent', section: 'Work Projects' }) ).rejects.toThrow("Memory document 'nonexistent' not found") }) }) describe('Section Retrieval', () => { it('should successfully retrieve existing section with content', async () => { const mockSection: MemorySection = { name: 'Work Projects', content: '- Complete the API integration task\n- Review code for the authentication module\n- Update documentation for new features', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Work Projects' }) expect(result.content[0].type).toBe('text') expect(result.content[0].text).toContain('## Work Projects') expect(result.content[0].text).toContain('- Complete the API integration task') expect(result.content[0].text).toContain('- Review code for the authentication module') expect(result.content[0].text).toContain('- Update documentation for new features') expect(result.content[0].text).toContain('---') expect(result.content[0].text).toContain('*From memory document: test-memory*') expect(result.content[0].text).toContain('*Last updated: 2025-07-31T10:00:00Z*') }) it('should handle section with empty content', async () => { const mockSection: MemorySection = { name: 'Empty Section', content: '', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Empty Section' }) expect(result.content[0].text).toContain('## Empty Section') expect(result.content[0].text).toContain('(This section is empty)') expect(result.content[0].text).toContain('*From memory document: test-memory*') }) it('should handle section with null content', async () => { const mockSection: MemorySection = { name: 'Null Content Section', content: null as any, level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Null Content Section' }) expect(result.content[0].text).toContain('## Null Content Section') expect(result.content[0].text).toContain('(This section is empty)') }) it('should handle section with undefined content', async () => { const mockSection: MemorySection = { name: 'Undefined Content Section', content: undefined as any, level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Undefined Content Section' }) expect(result.content[0].text).toContain('## Undefined Content Section') expect(result.content[0].text).toContain('(This section is empty)') }) it('should handle section with whitespace-only content', async () => { const mockSection: MemorySection = { name: 'Whitespace Section', content: ' \n\t \n ', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Whitespace Section' }) expect(result.content[0].text).toContain('## Whitespace Section') expect(result.content[0].text).toContain(' \n\t \n ') expect(result.content[0].text).not.toContain('(This section is empty)') }) it('should handle multi-line section content', async () => { const mockSection: MemorySection = { name: 'Personal Notes', content: 'The integration work is challenging but rewarding.\nNeed to focus on error handling and edge cases.\n\nAdditional thoughts on the project.', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Personal Notes' }) expect(result.content[0].text).toContain('## Personal Notes') expect(result.content[0].text).toContain('The integration work is challenging but rewarding.') expect(result.content[0].text).toContain('Need to focus on error handling and edge cases.') expect(result.content[0].text).toContain('Additional thoughts on the project.') }) it('should handle section with special characters in name', async () => { const mockSection: MemorySection = { name: 'Section & Special <Characters> "Quotes"', content: 'Content with special characters & symbols', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Section & Special <Characters> "Quotes"' }) expect(result.content[0].text).toContain('## Section & Special <Characters> "Quotes"') expect(result.content[0].text).toContain('Content with special characters & symbols') }) it('should handle section with markdown formatting in content', async () => { const mockSection: MemorySection = { name: 'Formatted Section', content: '**Bold text** and *italic text*\n\n- Bullet point\n- Another bullet\n\n`Code snippet`\n\n[Link](https://example.com)', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Formatted Section' }) expect(result.content[0].text).toContain('## Formatted Section') expect(result.content[0].text).toContain('**Bold text** and *italic text*') expect(result.content[0].text).toContain('- Bullet point') expect(result.content[0].text).toContain('`Code snippet`') expect(result.content[0].text).toContain('[Link](https://example.com)') }) }) describe('Section Not Found Error Handling', () => { it('should throw error with available sections when section not found', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) const mockSections: MemorySection[] = [ { name: 'Work Projects', content: 'content1', level: 2 }, { name: 'Personal Notes', content: 'content2', level: 2 }, { name: 'Meeting Notes', content: 'content3', level: 2 } ] vi.mocked(storageManager.parseSections).mockReturnValue(mockSections) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'nonexistent-section' }) ).rejects.toThrow( "Section 'nonexistent-section' not found in memory document 'test-memory'. " + "Available sections: Work Projects, Personal Notes, Meeting Notes" ) }) it('should throw error with no available sections when memory has no sections', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) vi.mocked(storageManager.parseSections).mockReturnValue([]) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'any-section' }) ).rejects.toThrow( "Section 'any-section' not found in memory document 'test-memory'. " + "Available sections: " ) }) it('should throw error with single available section', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) const mockSections: MemorySection[] = [ { name: 'Only Section', content: 'content', level: 2 } ] vi.mocked(storageManager.parseSections).mockReturnValue(mockSections) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'wrong-section' }) ).rejects.toThrow( "Section 'wrong-section' not found in memory document 'test-memory'. " + "Available sections: Only Section" ) }) it('should handle sections with special characters in available sections list', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) const mockSections: MemorySection[] = [ { name: 'Section & One', content: 'content1', level: 2 }, { name: 'Section "Two"', content: 'content2', level: 2 }, { name: 'Section <Three>', content: 'content3', level: 2 } ] vi.mocked(storageManager.parseSections).mockReturnValue(mockSections) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'missing' }) ).rejects.toThrow( "Section 'missing' not found in memory document 'test-memory'. " + 'Available sections: Section & One, Section "Two", Section <Three>' ) }) }) describe('Storage Manager Integration', () => { it('should call readMemory with correct memory_id', async () => { const mockSection: MemorySection = { name: 'Test Section', content: 'test content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) await getSectionTool(storageManager, { memory_id: 'specific-memory-id', section: 'Test Section' }) expect(vi.mocked(storageManager.readMemory)).toHaveBeenCalledWith('specific-memory-id') expect(vi.mocked(storageManager.readMemory)).toHaveBeenCalledTimes(1) }) it('should call findSection with memory content and section name', async () => { const mockSection: MemorySection = { name: 'Test Section', content: 'test content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Test Section' }) expect(vi.mocked(storageManager.findSection)).toHaveBeenCalledWith( mockMemory.content, 'Test Section' ) expect(vi.mocked(storageManager.findSection)).toHaveBeenCalledTimes(1) }) it('should call parseSections only when section not found', async () => { const mockSection: MemorySection = { name: 'Found Section', content: 'content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Found Section' }) expect(vi.mocked(storageManager.parseSections)).not.toHaveBeenCalled() }) it('should call parseSections when section not found for error message', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) const mockSections: MemorySection[] = [ { name: 'Available Section', content: 'content', level: 2 } ] vi.mocked(storageManager.parseSections).mockReturnValue(mockSections) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Missing Section' }) ).rejects.toThrow() expect(vi.mocked(storageManager.parseSections)).toHaveBeenCalledWith(mockMemory.content) expect(vi.mocked(storageManager.parseSections)).toHaveBeenCalledTimes(1) }) it('should handle readMemory throwing error', async () => { vi.mocked(storageManager.readMemory).mockRejectedValue(new Error('Read failed')) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Test Section' }) ).rejects.toThrow('Read failed') }) it('should handle findSection throwing error', async () => { vi.mocked(storageManager.findSection).mockImplementation(() => { throw new Error('Find section failed') }) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Test Section' }) ).rejects.toThrow('Find section failed') }) it('should handle parseSections throwing error', async () => { vi.mocked(storageManager.findSection).mockReturnValue(null) vi.mocked(storageManager.parseSections).mockImplementation(() => { throw new Error('Parse sections failed') }) await expect( getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Missing Section' }) ).rejects.toThrow('Parse sections failed') }) }) describe('Return Value Format', () => { it('should return correct structure with content array', async () => { const mockSection: MemorySection = { name: 'Test Section', content: 'test content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Test Section' }) expect(result).toHaveProperty('content') expect(Array.isArray(result.content)).toBe(true) expect(result.content).toHaveLength(1) expect(result.content[0]).toHaveProperty('type', 'text') expect(result.content[0]).toHaveProperty('text') expect(typeof result.content[0].text).toBe('string') }) it('should format header with section name', async () => { const mockSection: MemorySection = { name: 'Formatted Section Name', content: 'some content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Formatted Section Name' }) expect(result.content[0].text).toMatch(/^## Formatted Section Name\n/) }) it('should include content followed by footer with metadata', async () => { const mockSection: MemorySection = { name: 'Content Section', content: 'This is the section content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Content Section' }) const text = result.content[0].text const lines = text.split('\n') expect(lines[0]).toBe('## Content Section') expect(lines[1]).toBe('') expect(lines[2]).toBe('This is the section content') expect(lines[3]).toBe('') expect(lines[4]).toBe('---') expect(lines[5]).toBe('*From memory document: test-memory*') expect(lines[6]).toBe('*Last updated: 2025-07-31T10:00:00Z*') }) it('should handle different memory metadata', async () => { const differentMemory: Memory = { metadata: { id: 'different-id', created: '2025-01-01T00:00:00Z', updated: '2025-07-30T15:30:45Z', status: 'archived', tags: ['personal'] }, content: 'content', filePath: '/different/path.md' } const mockSection: MemorySection = { name: 'Section', content: 'content', level: 2 } vi.mocked(storageManager.readMemory).mockResolvedValue(differentMemory) vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'different-id', section: 'Section' }) expect(result.content[0].text).toContain('*From memory document: different-id*') expect(result.content[0].text).toContain('*Last updated: 2025-07-30T15:30:45Z*') }) }) describe('Edge Cases', () => { it('should handle very long section content', async () => { const longContent = 'A'.repeat(10000) const mockSection: MemorySection = { name: 'Long Section', content: longContent, level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Long Section' }) expect(result.content[0].text).toContain(longContent) expect(result.content[0].text).toContain('## Long Section') }) it('should handle section name with leading/trailing whitespace', async () => { const mockSection: MemorySection = { name: ' Trimmed Section ', content: 'content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: ' Trimmed Section ' }) expect(result.content[0].text).toContain('## Trimmed Section ') }) it('should handle content with only newlines', async () => { const mockSection: MemorySection = { name: 'Newline Section', content: '\n\n\n', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Newline Section' }) expect(result.content[0].text).toContain('## Newline Section') expect(result.content[0].text).toContain('\n\n\n') expect(result.content[0].text).not.toContain('(This section is empty)') }) it('should handle Unicode characters in section name and content', async () => { const mockSection: MemorySection = { name: 'Sección Españ0la 🚀', content: 'Contenido con émojis 💫 y caracteres especiales àáâãäå', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Sección Españ0la 🚀' }) expect(result.content[0].text).toContain('## Sección Españ0la 🚀') expect(result.content[0].text).toContain('Contenido con émojis 💫 y caracteres especiales àáâãäå') }) it('should handle memory with missing metadata fields', async () => { const incompleteMemory: Memory = { metadata: { id: 'incomplete', created: '2025-07-30T12:00:00Z', updated: '2025-07-31T10:00:00Z', tags: [] // status is optional and missing }, content: 'content', filePath: '/path.md' } const mockSection: MemorySection = { name: 'Section', content: 'content', level: 2 } vi.mocked(storageManager.readMemory).mockResolvedValue(incompleteMemory) vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'incomplete', section: 'Section' }) expect(result.content[0].text).toContain('*From memory document: incomplete*') expect(result.content[0].text).toContain('*Last updated: 2025-07-31T10:00:00Z*') }) it('should handle section with different heading levels', async () => { const mockSection: MemorySection = { name: 'Level 3 Section', content: 'content for level 3', level: 3 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'Level 3 Section' }) // The tool always formats as level 2 (##) regardless of original level expect(result.content[0].text).toContain('## Level 3 Section') expect(result.content[0].text).not.toContain('### Level 3 Section') }) it('should handle case-sensitive section matching', async () => { // This tests that the tool uses the exact section name as returned by findSection const mockSection: MemorySection = { name: 'CaseSensitive Section', content: 'content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: 'casesensitive section' // Different case }) // Should use the exact name from the found section expect(result.content[0].text).toContain('## CaseSensitive Section') }) it('should handle sections with numeric names', async () => { const mockSection: MemorySection = { name: '2025 Goals', content: 'Goals for the year 2025', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) const result = await getSectionTool(storageManager, { memory_id: 'test-memory', section: '2025 Goals' }) expect(result.content[0].text).toContain('## 2025 Goals') expect(result.content[0].text).toContain('Goals for the year 2025') }) }) describe('Parameter Types', () => { it('should handle GetSectionParams interface correctly', async () => { const mockSection: MemorySection = { name: 'Valid Section', content: 'valid content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) // Test with explicit GetSectionParams structure const params = { memory_id: 'test-memory', section: 'Valid Section' } const result = await getSectionTool(storageManager, params) expect(result.content[0].text).toContain('## Valid Section') expect(result.content[0].text).toContain('valid content') }) it('should handle extra properties in args object', async () => { const mockSection: MemorySection = { name: 'Test Section', content: 'content', level: 2 } vi.mocked(storageManager.findSection).mockReturnValue(mockSection) // Args with extra properties const argsWithExtra = { memory_id: 'test-memory', section: 'Test Section', extraProperty: 'should be ignored', anotherExtra: 42 } const result = await getSectionTool(storageManager, argsWithExtra) expect(result.content[0].text).toContain('## Test Section') // Should work despite extra properties }) }) })

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/nmeierpolys/mcp-structured-memory'

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