Skip to main content
Glama
template-custom.test.js14 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import path from 'path'; // Mock fs module first const mockFs = { readFile: vi.fn(), writeFile: vi.fn(), mkdir: vi.fn(), readdir: vi.fn(), stat: vi.fn(), unlink: vi.fn(), access: vi.fn() }; vi.mock('fs/promises', () => mockFs); // Mock os module vi.mock('os', () => ({ default: { homedir: () => '/mock/home', tmpdir: () => '/mock/tmp' } })); // Import after mocks are set up const fs = mockFs; describe('Template Custom Feature Tests', () => { const mockTemplatesDir = path.join('/mock/home', '.shrimp-task-viewer-templates'); const mockDefaultTemplatesDir = '/mock/project/src/prompts/templates_en'; beforeEach(() => { vi.clearAllMocks(); // Default mock implementations mockFs.stat.mockImplementation((filePath) => { if (filePath.includes('templates_en') || filePath.includes('.shrimp-task-viewer-templates')) { return Promise.resolve({ isDirectory: () => true }); } const error = new Error('ENOENT'); error.code = 'ENOENT'; return Promise.reject(error); }); mockFs.access.mockResolvedValue(); mockFs.mkdir.mockResolvedValue(); mockFs.writeFile.mockResolvedValue(); }); describe('Custom Template Loading - Passing Tests', () => { it('should successfully load and apply a custom template', async () => { // Setup: Custom template exists const customTemplate = `## Custom Task Analysis Task: {description} Requirements: {requirements} ## Context7 Integration This template includes Context7 documentation lookup. ## Steps: 1. Analyze the task 2. Plan implementation 3. Execute with Context7 support`; mockFs.readFile.mockImplementation((filePath) => { if (filePath === path.join(mockTemplatesDir, 'planTask.txt')) { return Promise.resolve(customTemplate); } return Promise.reject(new Error('File not found')); }); mockFs.readdir.mockImplementation((dirPath) => { if (dirPath === mockTemplatesDir) { return Promise.resolve(['planTask.txt']); } return Promise.resolve([]); }); // Act: Load template const loadTemplate = async (templateName) => { const templatePath = path.join(mockTemplatesDir, `${templateName}.txt`); try { const content = await fs.readFile(templatePath, 'utf8'); return { success: true, content, status: 'custom' }; } catch (error) { return { success: false, error: error.message }; } }; const result = await loadTemplate('planTask'); // Assert expect(result.success).toBe(true); expect(result.content).toContain('Custom Task Analysis'); expect(result.content).toContain('Context7 Integration'); expect(result.status).toBe('custom'); }); it('should successfully save and update a custom template', async () => { // Setup const newTemplate = `## Updated Template {description} {requirements} {summary} Enhanced with new features`; // Act: Save template const saveTemplate = async (templateName, content, mode = 'override') => { const fileName = mode === 'append' ? `${templateName}_append.txt` : `${templateName}.txt`; const templatePath = path.join(mockTemplatesDir, fileName); try { // Ensure directory exists await fs.mkdir(mockTemplatesDir, { recursive: true }); // Write template await fs.writeFile(templatePath, content); return { success: true, path: templatePath, mode }; } catch (error) { return { success: false, error: error.message }; } }; const result = await saveTemplate('executeTask', newTemplate); // Assert expect(result.success).toBe(true); expect(mockFs.mkdir).toHaveBeenCalledWith(mockTemplatesDir, { recursive: true }); expect(mockFs.writeFile).toHaveBeenCalledWith( path.join(mockTemplatesDir, 'executeTask.txt'), newTemplate ); expect(result.mode).toBe('override'); }); it('should handle append mode correctly', async () => { // Setup: Existing template const existingTemplate = '## Original Template\n{description}'; const appendContent = '\n\n## Additional Context\n{summary}'; mockFs.readFile.mockResolvedValue(existingTemplate); // Act: Append to template const appendToTemplate = async (templateName, content) => { const appendPath = path.join(mockTemplatesDir, `${templateName}_append.txt`); try { await fs.writeFile(appendPath, content); // When loading, combine base + append const basePath = path.join(mockTemplatesDir, `${templateName}.txt`); let finalContent = ''; try { finalContent = await fs.readFile(basePath, 'utf8'); } catch { // Use default if no base custom template finalContent = '## Default Template'; } finalContent += content; return { success: true, content: finalContent, mode: 'append' }; } catch (error) { return { success: false, error: error.message }; } }; const result = await appendToTemplate('planTask', appendContent); // Assert expect(result.success).toBe(true); expect(result.content).toContain('Original Template'); expect(result.content).toContain('Additional Context'); expect(result.mode).toBe('append'); }); it('should validate template parameters are properly replaced', async () => { // Setup const template = `Task: {description} Requirements: {requirements} Summary: {summary} Agent: {agentName}`; const parameters = { description: 'Implement user authentication', requirements: 'OAuth2, JWT tokens', summary: 'Add secure login system', agentName: 'SecurityAgent' }; // Act: Replace parameters const applyTemplate = (templateContent, params) => { let result = templateContent; for (const [key, value] of Object.entries(params)) { result = result.replace(new RegExp(`{${key}}`, 'g'), value); } return result; }; const result = applyTemplate(template, parameters); // Assert expect(result).not.toContain('{description}'); expect(result).not.toContain('{requirements}'); expect(result).toContain('Implement user authentication'); expect(result).toContain('OAuth2, JWT tokens'); expect(result).toContain('Add secure login system'); expect(result).toContain('SecurityAgent'); }); }); describe('Custom Template Loading - Failing Tests', () => { it('should fail when template directory does not exist', async () => { // Setup: Directory doesn't exist mockFs.stat.mockRejectedValue(new Error('ENOENT: no such file or directory')); mockFs.readdir.mockRejectedValue(new Error('ENOENT: no such file or directory')); // Act: Try to load templates const loadTemplates = async () => { try { await fs.stat(mockTemplatesDir); const templates = await fs.readdir(mockTemplatesDir); return { success: true, templates }; } catch (error) { return { success: false, error: error.message }; } }; const result = await loadTemplates(); // Assert expect(result.success).toBe(false); expect(result.error).toContain('ENOENT'); }); it('should fail when template file is corrupted or invalid', async () => { // Setup: Corrupted template (invalid JSON-like structure expected) const corruptedTemplate = `{{{{{invalid brackets}}}} <<<malformed>>> undefined content {unclosed parameter`; mockFs.readFile.mockResolvedValue(corruptedTemplate); // Act: Validate template const validateTemplate = async (templateName) => { const templatePath = path.join(mockTemplatesDir, `${templateName}.txt`); try { const content = await fs.readFile(templatePath, 'utf8'); // Validation checks const errors = []; // Check for unclosed parameters const openBraces = (content.match(/{/g) || []).length; const closeBraces = (content.match(/}/g) || []).length; if (openBraces !== closeBraces) { errors.push('Unclosed parameter brackets detected'); } // Check for invalid syntax if (content.includes('{{{') || content.includes('}}}')) { errors.push('Invalid bracket syntax'); } // Check for malformed markers if (content.includes('<<<') || content.includes('>>>')) { errors.push('Invalid template markers'); } if (errors.length > 0) { return { success: false, errors }; } return { success: true, content }; } catch (error) { return { success: false, error: error.message }; } }; const result = await validateTemplate('corruptedTask'); // Assert expect(result.success).toBe(false); expect(result.errors).toContain('Unclosed parameter brackets detected'); expect(result.errors).toContain('Invalid bracket syntax'); expect(result.errors).toContain('Invalid template markers'); }); it('should fail when trying to save template with invalid name', async () => { // Setup const invalidNames = [ '../../../etc/passwd', // Path traversal attempt 'template/with/slashes', // Invalid characters '', // Empty name '.', // Current directory '..', // Parent directory 'con', // Reserved name (Windows) 'nul', // Reserved name (Windows) ]; // Act: Validate and save const saveTemplate = async (templateName, content) => { // Validate template name const isValidName = (name) => { if (!name || name.length === 0) return false; if (name.includes('/') || name.includes('\\')) return false; if (name.includes('..')) return false; if (name === '.' || name === '..') return false; if (['con', 'prn', 'aux', 'nul'].includes(name.toLowerCase())) return false; return true; }; if (!isValidName(templateName)) { return { success: false, error: 'Invalid template name' }; } const templatePath = path.join(mockTemplatesDir, `${templateName}.txt`); await fs.writeFile(templatePath, content); return { success: true }; }; // Assert: All invalid names should fail for (const invalidName of invalidNames) { const result = await saveTemplate(invalidName, 'Template content'); expect(result.success).toBe(false); expect(result.error).toBe('Invalid template name'); } }); it('should fail when template exceeds size limit', async () => { // Setup: Very large template const MAX_TEMPLATE_SIZE = 100000; // 100KB limit const largeTemplate = 'x'.repeat(MAX_TEMPLATE_SIZE + 1); // Act: Try to save large template const saveTemplate = async (templateName, content) => { // Check size limit const sizeInBytes = Buffer.byteLength(content, 'utf8'); if (sizeInBytes > MAX_TEMPLATE_SIZE) { return { success: false, error: `Template size (${sizeInBytes} bytes) exceeds maximum allowed size (${MAX_TEMPLATE_SIZE} bytes)` }; } await fs.writeFile(path.join(mockTemplatesDir, `${templateName}.txt`), content); return { success: true }; }; const result = await saveTemplate('largeTemplate', largeTemplate); // Assert expect(result.success).toBe(false); expect(result.error).toContain('exceeds maximum allowed size'); }); it('should fail when circular reference in template parameters', async () => { // Setup: Template with circular reference const circularTemplate = `## Task Description: {description} Extended: {extendedDescription}`; const parameters = { description: 'Task with {extendedDescription}', extendedDescription: 'Extended with {description}' }; // Act: Try to resolve parameters const resolveTemplate = (template, params, maxDepth = 10) => { let result = template; let depth = 0; let hasReplacements = true; while (hasReplacements && depth < maxDepth) { hasReplacements = false; for (const [key, value] of Object.entries(params)) { const pattern = new RegExp(`{${key}}`, 'g'); if (result.includes(`{${key}}`)) { result = result.replace(pattern, value); hasReplacements = true; } } depth++; } // Check if still has unresolved parameters if (result.match(/{[^}]+}/)) { return { success: false, error: 'Circular reference or unresolved parameters detected', unresolvedParams: result.match(/{[^}]+}/g) }; } return { success: true, content: result }; }; const result = resolveTemplate(circularTemplate, parameters); // Assert expect(result.success).toBe(false); expect(result.error).toContain('Circular reference'); expect(result.unresolvedParams).toBeDefined(); }); }); });

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/cjo4m06/mcp-shrimp-task-manager'

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