Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
version-persistence.test.tsโ€ข10.1 kB
/** * Unit tests for version persistence and synchronization * Tests the fixes for version handling across element types */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import { v4 as uuidv4 } from 'uuid'; import { SkillManager } from '../../../../src/elements/skills/SkillManager.js'; import { Skill } from '../../../../src/elements/skills/Skill.js'; import { TemplateManager } from '../../../../src/elements/templates/TemplateManager.js'; import { Template } from '../../../../src/elements/templates/Template.js'; import { AgentManager } from '../../../../src/elements/agents/AgentManager.js'; import { Agent } from '../../../../src/elements/agents/Agent.js'; import { ElementType } from '../../../../src/portfolio/types.js'; describe('Version Persistence', () => { let testDir: string; let skillManager: SkillManager; let templateManager: TemplateManager; let agentManager: AgentManager; beforeEach(async () => { // Create unique test directory testDir = path.join(os.tmpdir(), `test-version-${uuidv4()}`); await fs.mkdir(testDir, { recursive: true }); // Create subdirectories const skillsDir = path.join(testDir, 'skills'); const templatesDir = path.join(testDir, 'templates'); const agentsDir = path.join(testDir, 'agents'); await fs.mkdir(skillsDir, { recursive: true }); await fs.mkdir(templatesDir, { recursive: true }); await fs.mkdir(agentsDir, { recursive: true }); // Initialize managers - they now use PortfolioManager.getInstance() internally skillManager = new SkillManager(); templateManager = new TemplateManager(); agentManager = new AgentManager(testDir); }); afterEach(async () => { // Clean up test directory try { await fs.rm(testDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe('SkillManager Version Persistence', () => { it('should persist version when saving a skill', async () => { // Create a skill with a specific version const skill = new Skill( { name: 'Test Skill', description: 'A test skill', version: '2.1.0' }, 'Test instructions' ); // Save the skill await skillManager.save(skill, 'test-skill.md'); // Load the skill back const loadedSkill = await skillManager.load('test-skill.md'); // Verify version persisted expect(loadedSkill.version).toBe('2.1.0'); expect(loadedSkill.metadata.version).toBe('2.1.0'); }); it('should sync version between element.version and metadata.version', async () => { // Create skill with version only in metadata const skill = new Skill( { name: 'Version Sync Test', description: 'Testing version sync', version: '1.5.3' }, 'Instructions' ); // Manually set element.version different from metadata skill.version = '1.5.4'; // Save with element.version taking precedence await skillManager.save(skill, 'version-sync.md'); // Load and verify const loaded = await skillManager.load('version-sync.md'); // Both should match the element.version value expect(loaded.version).toBe('1.5.4'); expect(loaded.metadata.version).toBe('1.5.4'); }); it('should handle missing version gracefully', async () => { // Create skill without version const skill = new Skill( { name: 'No Version Skill', description: 'Skill without version' }, 'Instructions' ); // Remove version to simulate edge case delete (skill as any).version; delete (skill.metadata as any).version; // Save should not throw await expect(skillManager.save(skill, 'no-version.md')).resolves.not.toThrow(); // Load and check default version applied const loaded = await skillManager.load('no-version.md'); // Should have default version expect(loaded.version).toBeDefined(); expect(loaded.metadata.version).toBeDefined(); }); }); describe('TemplateManager Version Persistence', () => { it('should persist version when saving a template', async () => { // Create template with version const template = new Template( { name: 'Test Template', description: 'A test template', version: '3.2.1' }, 'Template content' ); // Save template await templateManager.save(template, 'test-template.md'); // Load and verify const loaded = await templateManager.load('test-template.md'); expect(loaded.version).toBe('3.2.1'); expect(loaded.metadata.version).toBe('3.2.1'); }); }); describe('AgentManager Version Persistence', () => { it('should persist version when saving an agent', async () => { // Create agent with version const agent = new Agent({ name: 'Test Agent', description: 'A test agent', version: '4.0.0' }); // Save agent await agentManager.save(agent, 'test-agent.md'); // Load and verify const loaded = await agentManager.load('test-agent.md'); expect(loaded.version).toBe('4.0.0'); expect(loaded.metadata.version).toBe('4.0.0'); }); }); describe('Version Format Validation', () => { it('should validate semver format', () => { const validVersions = ['1.0.0', '2.3.4', '10.20.30', '0.0.1']; const invalidVersions = ['1', '1.0', 'v1.0.0', '1.0.0-beta', 'abc']; const isValidSemver = (version: string): boolean => { return /^\d+\.\d+\.\d+$/.test(version); }; validVersions.forEach(v => { expect(isValidSemver(v)).toBe(true); }); invalidVersions.forEach(v => { expect(isValidSemver(v)).toBe(false); }); }); it('should handle version increment correctly', () => { const testCases = [ { input: '1.0.0', expected: '1.0.1' }, { input: '1.0.9', expected: '1.0.10' }, { input: '1.0.99', expected: '1.0.100' }, { input: '1.0', expected: '1.0.1' }, { input: '1', expected: '1.0.1' }, { input: 'invalid', expected: '1.0.1' }, // Pre-release version tests { input: '1.0.0-beta', expected: '1.0.0-beta.1' }, { input: '1.0.0-beta.1', expected: '1.0.0-beta.2' }, { input: '1.0.0-alpha.5', expected: '1.0.0-alpha.6' }, { input: '2.0.0-rc.1', expected: '2.0.0-rc.2' } ]; testCases.forEach(({ input, expected }) => { let result: string; // Check for pre-release versions const preReleaseMatch = input.match(/^(\d+\.\d+\.\d+)(-([a-zA-Z0-9.-]+))?$/); if (preReleaseMatch) { const baseVersion = preReleaseMatch[1]; const preReleaseTag = preReleaseMatch[3]; if (preReleaseTag) { const preReleaseNumberMatch = preReleaseTag.match(/^([a-zA-Z]+)\.?(\d+)?$/); if (preReleaseNumberMatch) { const preReleaseType = preReleaseNumberMatch[1]; const preReleaseNumber = Number.parseInt(preReleaseNumberMatch[2] || '0') + 1; result = `${baseVersion}-${preReleaseType}.${preReleaseNumber}`; } else { const [major, minor, patch] = baseVersion.split('.').map(Number); result = `${major}.${minor}.${patch + 1}`; } } else { const [major, minor, patch] = baseVersion.split('.').map(Number); result = `${major}.${minor}.${patch + 1}`; } } else { const versionParts = input.split('.'); if (versionParts.length >= 3) { const patch = Number.parseInt(versionParts[2]) || 0; versionParts[2] = String(patch + 1); result = versionParts.join('.'); } else if (versionParts.length === 2) { result = `${input}.1`; } else if (versionParts.length === 1 && /^\d+$/.test(versionParts[0])) { result = `${input}.0.1`; } else { result = '1.0.1'; } } expect(result).toBe(expected); }); }); }); describe('Version Synchronization Edge Cases', () => { it('should handle concurrent version updates', async () => { const skill = new Skill( { name: 'Concurrent Test', description: 'Testing concurrent updates', version: '1.0.0' }, 'Instructions' ); // Simulate concurrent updates skill.version = '1.0.1'; skill.metadata.version = '1.0.2'; // Save should use element.version as source of truth await skillManager.save(skill, 'concurrent.md'); const loaded = await skillManager.load('concurrent.md'); // Both should be synced to element.version value expect(loaded.version).toBe('1.0.1'); expect(loaded.metadata.version).toBe('1.0.1'); }); it('should handle version updates with metadata undefined', async () => { const skill = new Skill( { name: 'Metadata Test', description: 'Testing undefined metadata' }, 'Instructions' ); // Simulate edge case where metadata might be undefined const originalMetadata = skill.metadata; (skill as any).metadata = undefined; skill.version = '2.0.0'; // Restore metadata for save skill.metadata = originalMetadata; await skillManager.save(skill, 'metadata-sample.md'); const loaded = await skillManager.load('metadata-sample.md'); expect(loaded.version).toBe('2.0.0'); expect(loaded.metadata.version).toBe('2.0.0'); }); }); });

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