Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
MigrationManager.test.tsโ€ข8.26 kB
/** * Tests for MigrationManager */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import * as fs from 'fs/promises'; import * as path from 'path'; import { tmpdir } from 'os'; import { PortfolioManager, ElementType } from '../../../../src/portfolio/PortfolioManager.js'; import { MigrationManager } from '../../../../src/portfolio/MigrationManager.js'; describe('MigrationManager', () => { let testDir: string; let legacyDir: string; let portfolioDir: string; let portfolioManager: PortfolioManager; let migrationManager: MigrationManager; const originalEnv = process.env.DOLLHOUSE_PORTFOLIO_DIR; beforeEach(async () => { // Create test directories testDir = path.join(tmpdir(), `migration-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); await fs.mkdir(testDir, { recursive: true }); // Setup legacy directory structure const dollhouseDir = path.join(testDir, '.dollhouse'); legacyDir = path.join(dollhouseDir, 'personas'); portfolioDir = path.join(dollhouseDir, 'portfolio'); // Clear environment and reset singleton delete process.env.DOLLHOUSE_PORTFOLIO_DIR; (PortfolioManager as any).instance = undefined; // Create portfolio manager with test directory portfolioManager = PortfolioManager.getInstance({ baseDir: portfolioDir }); // Override getLegacyPersonasDir to use test directory jest.spyOn(portfolioManager, 'getLegacyPersonasDir').mockReturnValue(legacyDir); migrationManager = new MigrationManager(portfolioManager); }); afterEach(async () => { // Restore environment if (originalEnv) { process.env.DOLLHOUSE_PORTFOLIO_DIR = originalEnv; } else { delete process.env.DOLLHOUSE_PORTFOLIO_DIR; } // Clean up try { await fs.rm(testDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } jest.restoreAllMocks(); }); describe('needsMigration', () => { it('should return false when no legacy personas exist', async () => { expect(await migrationManager.needsMigration()).toBe(false); }); it('should return false when portfolio already exists', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'sample.md'), 'content'); // Create portfolio await portfolioManager.initialize(); expect(await migrationManager.needsMigration()).toBe(false); }); it('should return true when legacy exists but portfolio does not', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'sample.md'), 'content'); expect(await migrationManager.needsMigration()).toBe(true); }); }); describe('migrate', () => { it('should return early when no migration needed', async () => { const result = await migrationManager.migrate(); expect(result.success).toBe(true); expect(result.migratedCount).toBe(0); expect(result.errors).toEqual([]); expect(result.backedUp).toBe(false); }); it('should migrate all persona files', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'persona1.md'), '# Persona 1'); await fs.writeFile(path.join(legacyDir, 'persona2.md'), '# Persona 2'); await fs.writeFile(path.join(legacyDir, 'not-md.txt'), 'ignored'); const result = await migrationManager.migrate(); expect(result.success).toBe(true); expect(result.migratedCount).toBe(2); expect(result.errors).toEqual([]); // Verify files were copied const newPersonasDir = portfolioManager.getElementDir(ElementType.PERSONA); const persona1 = await fs.readFile(path.join(newPersonasDir, 'persona1.md'), 'utf-8'); const persona2 = await fs.readFile(path.join(newPersonasDir, 'persona2.md'), 'utf-8'); expect(persona1).toBe('# Persona 1'); expect(persona2).toBe('# Persona 2'); // Verify non-md file was not copied await expect(fs.access(path.join(newPersonasDir, 'not-md.txt'))) .rejects.toThrow(); }); it('should create backup when requested', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'sample.md'), '# Test'); const result = await migrationManager.migrate({ backup: true }); expect(result.success).toBe(true); expect(result.backedUp).toBe(true); expect(result.backupPath).toBeDefined(); // Verify backup exists if (result.backupPath) { await expect(fs.access(result.backupPath)).resolves.toBeUndefined(); const backupFile = await fs.readFile(path.join(result.backupPath, 'sample.md'), 'utf-8'); expect(backupFile).toBe('# Test'); } }); it.skip('should handle migration errors gracefully', async () => { // Skip this test - file permission testing is platform-specific // The error handling is tested in integration tests }); it('should preserve original files after migration', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'sample.md'), '# Test'); await migrationManager.migrate(); // Original file should still exist await expect(fs.access(path.join(legacyDir, 'sample.md'))) .resolves.toBeUndefined(); }); }); describe('getMigrationStatus', () => { it('should report no legacy personas initially', async () => { const status = await migrationManager.getMigrationStatus(); expect(status.hasLegacyPersonas).toBe(false); expect(status.legacyPersonaCount).toBe(0); expect(status.portfolioExists).toBe(false); expect(status.portfolioStats[ElementType.PERSONA]).toBe(0); }); it('should report legacy personas count', async () => { // Create legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'p1.md'), 'content'); await fs.writeFile(path.join(legacyDir, 'p2.md'), 'content'); await fs.writeFile(path.join(legacyDir, 'other.txt'), 'ignored'); const status = await migrationManager.getMigrationStatus(); expect(status.hasLegacyPersonas).toBe(true); expect(status.legacyPersonaCount).toBe(2); }); it('should report portfolio statistics after migration', async () => { // Create and migrate legacy personas await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'p1.md'), 'content'); await fs.writeFile(path.join(legacyDir, 'p2.md'), 'content'); await migrationManager.migrate(); const status = await migrationManager.getMigrationStatus(); expect(status.portfolioExists).toBe(true); expect(status.portfolioStats[ElementType.PERSONA]).toBe(2); expect(status.portfolioStats[ElementType.SKILL]).toBe(0); }); }); describe('backup functionality', () => { it('should create timestamped backup directory', async () => { // Create legacy content await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile(path.join(legacyDir, 'sample.md'), 'content'); await fs.mkdir(path.join(legacyDir, 'subdir'), { recursive: true }); await fs.writeFile(path.join(legacyDir, 'subdir', 'nested.md'), 'nested'); const result = await migrationManager.migrate({ backup: true }); expect(result.backupPath).toMatch(/_backup_\d{4}-\d{2}-\d{2}T/); // Verify only files were backed up (not subdirectories) if (result.backupPath) { const backupContents = await fs.readdir(result.backupPath); expect(backupContents).toContain('sample.md'); expect(backupContents).not.toContain('subdir'); } }); }); });

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