Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
PersonaManager.test.tsโ€ข14.2 kB
import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import * as fs from 'fs/promises'; import * as path from 'path'; import { PersonaManager } from '../../../src/persona/PersonaManager.js'; import { PersonaLoader } from '../../../src/persona/PersonaLoader.js'; import { PersonaValidator } from '../../../src/persona/PersonaValidator.js'; import { Persona } from '../../../src/types/persona.js'; import { DEFAULT_INDICATOR_CONFIG } from '../../../src/config/indicator-config.js'; // Mock dependencies jest.mock('fs/promises'); jest.mock('../../../src/persona/PersonaLoader'); jest.mock('../../../src/persona/PersonaValidator'); describe('PersonaManager', () => { let personaManager: PersonaManager; let mockLoader: jest.Mocked<PersonaLoader>; let mockValidator: jest.Mocked<PersonaValidator>; const mockPersonasDir = '/test/personas'; beforeEach(() => { jest.clearAllMocks(); // Create mocks for the dependencies mockLoader = { loadAll: jest.fn(), savePersona: jest.fn(), deletePersona: jest.fn() } as unknown as jest.Mocked<PersonaLoader>; mockValidator = { validatePersona: jest.fn(), validateMetadata: jest.fn(), isValidPersonaName: jest.fn(), suggestImprovements: jest.fn() } as unknown as jest.Mocked<PersonaValidator>; // Mocks are already set up at the top of the file personaManager = new PersonaManager(mockPersonasDir, DEFAULT_INDICATOR_CONFIG); // Replace the internal instances with our mocks (personaManager as any).loader = mockLoader; (personaManager as any).validator = mockValidator; }); describe('loadPersonas', () => { it('should load personas successfully', async () => { const mockPersonas = new Map<string, Persona>([ ['sample.md', { metadata: { name: 'Test Persona', description: 'A test persona', unique_id: 'test-persona_20250101-120000_tester' }, content: 'Test content', filename: 'sample.md', unique_id: 'test-persona_20250101-120000_tester' }] ]); // Mock the loader to return personas mockLoader.loadAll.mockResolvedValue(mockPersonas); await personaManager.initialize(); // Verify loadAll was called expect(mockLoader.loadAll).toHaveBeenCalled(); }); it('should handle load errors gracefully', async () => { // Mock loader to fail mockLoader.loadAll.mockRejectedValue(new Error('Failed to read directory')); // PersonaManager.initialize might throw the error from loader await expect(personaManager.initialize()).rejects.toThrow('Failed to read directory'); }); }); describe('activatePersona', () => { const testPersona: Persona = { metadata: { name: 'Test Persona', description: 'A test persona', unique_id: 'test-persona_20250101-120000_tester' }, content: 'You are a test assistant', filename: 'sample.md', unique_id: 'test-persona_20250101-120000_tester' }; beforeEach(async () => { // Set up personas map (personaManager as any).personas = new Map([['Test Persona', testPersona]]); }); it('should activate a persona by name', () => { const result = personaManager.activatePersona('Test Persona'); expect(result).toBeDefined(); expect(result.message).toContain('Test Persona'); expect((personaManager as any).activePersona).toBe('sample.md'); }); it('should activate a persona by unique_id', () => { const result = personaManager.activatePersona('test-persona_20250101-120000_tester'); expect(result).toBeDefined(); expect(result.message).toContain('Activated'); expect((personaManager as any).activePersona).toBe('sample.md'); }); it('should throw error for non-existent persona', () => { const result = personaManager.activatePersona('Non-existent'); expect(result.success).toBe(false); expect(result.message).toBe('Persona not found: "Non-existent"'); }); }); describe('deactivatePersona', () => { it('should deactivate the active persona', () => { const testPersona: Persona = { metadata: { name: 'Test Persona', description: 'A test persona', unique_id: 'test-persona_20250101-120000_tester' }, content: 'Test content', filename: 'sample.md', unique_id: 'test-persona_20250101-120000_tester' }; (personaManager as any).personas = new Map([['Test Persona', testPersona]]); personaManager.activatePersona('Test Persona'); const result = personaManager.deactivatePersona(); expect(result).toBeDefined(); expect(result.message).toContain('Deactivated persona:'); expect((personaManager as any).activePersona).toBeNull(); }); it('should handle deactivation when no persona is active', () => { const result = personaManager.deactivatePersona(); expect(result).toBeDefined(); expect(result.message).toBe('No persona is currently active'); }); }); describe('createPersona', () => { it('should create a new persona successfully', async () => { const newPersona = { name: 'New Persona', description: 'A new test persona', category: 'creative', instructions: 'You are a creative assistant', triggers: ['creative', 'writing'] }; // Mock validator to return valid mockValidator.validatePersona.mockReturnValue({ valid: true, issues: [], warnings: [], report: 'Validation successful' }); mockLoader.savePersona.mockResolvedValue(); const result = await personaManager.createPersona( newPersona.name, newPersona.description, newPersona.category, newPersona.instructions ); // The createPersona method handles validation internally // Check that the persona was saved expect(mockLoader.savePersona).toHaveBeenCalled(); // PersonaManager uses loader.savePersona, not fs.writeFile directly expect(result.success).toBe(true); expect(result.message).toContain('successfully'); }); it('should reject invalid persona data', async () => { mockValidator.validatePersona.mockReturnValue({ valid: false, issues: ['Name is required', 'Invalid category'], warnings: [], report: 'Validation failed' }); const result = await personaManager.createPersona( '', 'Description', 'invalid-category', 'Instructions' ); expect(result.success).toBe(false); expect(result.message).toContain('Persona name cannot be empty'); }); }); describe('editPersona', () => { const testPersona: Persona = { metadata: { name: 'Test Persona', description: 'Original description', version: '1.0', unique_id: 'test-persona_20250101-120000_tester' }, content: '---\nname: Test Persona\ndescription: Original description\n---\nOriginal content', filename: 'sample.md', unique_id: 'test-persona_20250101-120000_tester' }; beforeEach(async () => { (personaManager as any).personas = new Map([['Test Persona', testPersona]]); (personaManager as any).personasDir = mockPersonasDir; }); it('should edit persona description', async () => { // Mock loader to save the updated persona mockLoader.savePersona.mockResolvedValue(); // Mock validator to return valid mockValidator.validatePersona.mockReturnValue({ valid: true, issues: [], warnings: [], report: 'Validation successful' }); const result = await personaManager.editPersona( 'Test Persona', 'description', 'Updated description' ); expect(mockLoader.savePersona).toHaveBeenCalled(); expect(result.success).toBe(true); }); it('should increment version when editing', async () => { mockLoader.savePersona.mockResolvedValue(); // Mock validator to return valid mockValidator.validatePersona.mockReturnValue({ valid: true, issues: [], warnings: [], report: 'Validation successful' }); const result = await personaManager.editPersona( 'Test Persona', 'description', 'Updated description' ); // Verify savePersona was called expect(mockLoader.savePersona).toHaveBeenCalled(); expect(result.success).toBe(true); // Check that the version was incremented in the response (from 1.0 to something higher) expect(result.message).toMatch(/v1\.\d+/); }); it('should reject edits to non-existent personas', async () => { const result = await personaManager.editPersona( 'Non-existent', 'description', 'New value' ); expect(result.success).toBe(false); expect(result.message).toBe('Persona not found: "Non-existent"'); }); }); describe('User Management', () => { it('should set user identity', () => { const username = 'testuser'; const email = 'test@example.com'; personaManager.setUserIdentity(username, email); const identity = personaManager.getUserIdentity(); expect(identity.username).toBe(username); expect(identity.email).toBe(email); }); it('should clear user identity', () => { personaManager.setUserIdentity('testuser', 'test@example.com'); personaManager.clearUserIdentity(); const identity = personaManager.getUserIdentity(); // After clearing, both should be null expect(identity.username).toBeNull(); expect(identity.email).toBeNull(); }); it('should generate anonymous ID when no user is set', () => { const identity = personaManager.getUserIdentity(); // When no user is set, both should be null expect(identity.username).toBeNull(); expect(identity.email).toBeNull(); }); }); describe('Performance', () => { it('should handle large number of personas efficiently', async () => { const largePersonaSet = new Map<string, Persona>(); const initialMemory = process.memoryUsage().heapUsed; for (let i = 0; i < 1000; i++) { largePersonaSet.set(`Persona ${i}`, { metadata: { name: `Persona ${i}`, description: `Description ${i}`, unique_id: `persona-${i}_20250101-120000_tester` }, content: `Content ${i}`, filename: `persona-${i}.md`, unique_id: `persona-${i}_20250101-120000_tester` }); } (personaManager as any).personas = largePersonaSet; const startTime = Date.now(); const personas = personaManager.getAllPersonas(); const loadTime = Date.now() - startTime; const memoryAfter = process.memoryUsage().heapUsed; const memoryUsed = (memoryAfter - initialMemory) / 1024 / 1024; // MB expect(loadTime).toBeLessThan(100); // Should be instant expect(personas.size).toBe(1000); expect(memoryUsed).toBeLessThan(50); // Should use less than 50MB for 1000 personas }); }); describe('Error Handling', () => { it('should handle file system errors gracefully', async () => { const fsError = new Error('EACCES: permission denied'); mockLoader.savePersona.mockRejectedValue(fsError); const result = await personaManager.createPersona( 'Test', 'Description', 'creative', 'Instructions' ); expect(result.success).toBe(false); expect(result.message).toContain('Failed to create persona'); }); it('should handle corrupted persona files', async () => { // Mock a corrupted file read // Mock loader to return empty map on error mockLoader.loadAll.mockResolvedValue(new Map()); // Initialize should handle the error gracefully await personaManager.initialize(); // Verify loader was called expect(mockLoader.loadAll).toHaveBeenCalled(); }); it('should handle concurrent persona operations', async () => { // Set up initial personas const mockPersonas = new Map([ ['test1.md', { metadata: { name: 'Test 1', description: 'Test', unique_id: 'test1' }, content: 'Content 1', filename: 'test1.md', unique_id: 'test1' }], ['test2.md', { metadata: { name: 'Test 2', description: 'Test', unique_id: 'test2' }, content: 'Content 2', filename: 'test2.md', unique_id: 'test2' }] ]); mockLoader.loadAll.mockResolvedValue(mockPersonas); await personaManager.initialize(); // Simulate concurrent activations (these are synchronous) const results = [ personaManager.activatePersona('Test 1'), personaManager.activatePersona('Test 2'), personaManager.activatePersona('Test 1') ]; // All should succeed expect(results.every(r => r.success)).toBe(true); // Last activation should win const activePersona = personaManager.getActivePersona(); expect(activePersona?.metadata.name).toBe('Test 1'); }); it('should handle file system race conditions', async () => { // Mock validator to succeed mockValidator.validatePersona.mockReturnValue({ valid: true, issues: [], warnings: [], report: 'Valid' }); // Simulate race condition where save fails due to file already existing mockLoader.savePersona .mockRejectedValueOnce(new Error('EEXIST: file already exists')) .mockResolvedValueOnce(undefined); const result = await personaManager.createPersona( 'Race Test', 'Description', 'creative', 'Instructions' ); expect(result.success).toBe(false); expect(result.message).toContain('Failed to create persona'); }); }); });

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/DollhouseMCP/DollhouseMCP'

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