Skip to main content
Glama
global-config.test.ts15 kB
/** * Unit tests for GlobalConfigManager * Tests vault registry, platform-specific config directories, and vault management */ import { test, describe, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert'; import { GlobalConfigManager } from '../../src/utils/global-config.ts'; import fs from 'fs/promises'; import path from 'path'; import os from 'os'; describe('GlobalConfigManager', () => { let originalConfigDir: string | undefined; beforeEach(async () => { // Mock the config directory to use temp directory originalConfigDir = process.env.XDG_CONFIG_HOME; }); afterEach(async () => { // Restore original config directory if (originalConfigDir !== undefined) { process.env.XDG_CONFIG_HOME = originalConfigDir; } else { delete process.env.XDG_CONFIG_HOME; } }); async function createTestConfig(): Promise<{ globalConfig: GlobalConfigManager; tempDir: string; cleanup: () => void; }> { // Create unique temporary directory for each test const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'flint-note-global-test-')); // Store original env var const originalXDGConfigHome = process.env.XDG_CONFIG_HOME; // Set test-specific env var process.env.XDG_CONFIG_HOME = tempDir; const globalConfig = new GlobalConfigManager(); const cleanup = () => { // Restore original env var if (originalXDGConfigHome !== undefined) { process.env.XDG_CONFIG_HOME = originalXDGConfigHome; } else { delete process.env.XDG_CONFIG_HOME; } }; return { globalConfig, tempDir, cleanup }; } async function cleanupTestConfig(tempDir: string, cleanup: () => void): Promise<void> { cleanup(); try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } } test('should get platform-specific configuration directory', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { const configDir = globalConfig.getPlatformConfigDir(); assert.ok( configDir.includes('flint-note'), 'Config directory should contain flint-note' ); assert.ok(path.isAbsolute(configDir), 'Config directory should be absolute path'); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should create default configuration when none exists', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { const config = await globalConfig.load(); assert.strictEqual(config.version, '1.0.0'); assert.strictEqual(config.current_vault, null); assert.deepStrictEqual(config.vaults, {}); assert.ok(config.settings); assert.strictEqual(typeof config.settings.auto_switch_on_create, 'boolean'); assert.strictEqual(typeof config.settings.max_recent_vaults, 'number'); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should validate vault IDs correctly', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { // Valid IDs assert.ok(globalConfig.isValidVaultId('valid-id')); assert.ok(globalConfig.isValidVaultId('valid_id')); assert.ok(globalConfig.isValidVaultId('valid123')); assert.ok(globalConfig.isValidVaultId('Valid-ID_123')); // Invalid IDs assert.ok(!globalConfig.isValidVaultId('')); assert.ok(!globalConfig.isValidVaultId('invalid id')); // spaces assert.ok(!globalConfig.isValidVaultId('invalid/id')); // slash assert.ok(!globalConfig.isValidVaultId('invalid\\id')); // backslash assert.ok(!globalConfig.isValidVaultId('invalid.id')); // dot assert.ok(!globalConfig.isValidVaultId('config')); // reserved assert.ok(!globalConfig.isValidVaultId('cache')); // reserved assert.ok(!globalConfig.isValidVaultId('a'.repeat(65))); // too long } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should add vault to registry', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); // Create a test vault directory const vaultPath = path.join(tempDir, 'test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault('test', 'Test Vault', vaultPath, 'A test vault'); const vault = globalConfig.getVault('test'); assert.ok(vault); assert.strictEqual(vault.name, 'Test Vault'); assert.strictEqual(vault.path, path.resolve(vaultPath)); assert.strictEqual(vault.description, 'A test vault'); assert.ok(vault.created); assert.ok(vault.last_accessed); // Should be set as current vault since it's the first one assert.strictEqual(globalConfig.getCurrentVaultPath(), path.resolve(vaultPath)); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should not add vault with invalid ID', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await assert.rejects( () => globalConfig.addVault('invalid id', 'Test', vaultPath), /Invalid vault ID/ ); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should not add vault with duplicate ID', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'test-vault-duplicate'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault('duplicate-test', 'Test Vault', vaultPath); await assert.rejects( () => globalConfig.addVault('duplicate-test', 'Another Vault', vaultPath), /already exists/ ); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should not add vault with non-existent path', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const nonExistentPath = path.join(tempDir, 'does-not-exist'); await assert.rejects( () => globalConfig.addVault('nonexistent-test', 'Test', nonExistentPath), /does not exist/ ); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should switch between vaults', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); // Create two test vaults const vault1Path = path.join(tempDir, 'vault1'); const vault2Path = path.join(tempDir, 'vault2'); await fs.mkdir(vault1Path, { recursive: true }); await fs.mkdir(vault2Path, { recursive: true }); await globalConfig.addVault('vault1', 'Vault 1', vault1Path); await globalConfig.addVault('vault2', 'Vault 2', vault2Path); // Switch to vault2 await globalConfig.switchVault('vault2'); assert.strictEqual(globalConfig.getCurrentVaultPath(), path.resolve(vault2Path)); // Switch back to vault1 await globalConfig.switchVault('vault1'); assert.strictEqual(globalConfig.getCurrentVaultPath(), path.resolve(vault1Path)); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should remove vault from registry', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'remove-test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault('remove-test', 'Test Vault', vaultPath); assert.ok(globalConfig.hasVault('remove-test')); await globalConfig.removeVault('remove-test'); assert.ok(!globalConfig.hasVault('remove-test')); assert.strictEqual(globalConfig.getCurrentVaultPath(), null); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should update vault information', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'update-test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault( 'update-test', 'Test Vault', vaultPath, 'Original description' ); await globalConfig.updateVault('update-test', { name: 'Updated Vault', description: 'Updated description' }); const vault = globalConfig.getVault('update-test'); assert.ok(vault); assert.strictEqual(vault.name, 'Updated Vault'); assert.strictEqual(vault.description, 'Updated description'); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should list vaults with current status', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vault1Path = path.join(tempDir, 'list-vault1'); const vault2Path = path.join(tempDir, 'list-vault2'); await fs.mkdir(vault1Path, { recursive: true }); await fs.mkdir(vault2Path, { recursive: true }); await globalConfig.addVault('list-vault1', 'Vault 1', vault1Path); await globalConfig.addVault('list-vault2', 'Vault 2', vault2Path); const vaults = globalConfig.listVaults(); assert.strictEqual(vaults.length, 2); const vault1 = vaults.find(v => v.info.id === 'list-vault1'); const vault2 = vaults.find(v => v.info.id === 'list-vault2'); assert.ok(vault1); assert.ok(vault2); // One should be current (the last one added due to auto_switch_on_create default) const currentVaults = vaults.filter(v => v.is_current); assert.strictEqual(currentVaults.length, 1); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should get recent vaults sorted by last accessed', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); // Create vaults const vault1Path = path.join(tempDir, 'recent-vault1'); const vault2Path = path.join(tempDir, 'recent-vault2'); const vault3Path = path.join(tempDir, 'recent-vault3'); await fs.mkdir(vault1Path, { recursive: true }); await fs.mkdir(vault2Path, { recursive: true }); await fs.mkdir(vault3Path, { recursive: true }); await globalConfig.addVault('recent-vault1', 'Vault 1', vault1Path); await globalConfig.addVault('recent-vault2', 'Vault 2', vault2Path); await globalConfig.addVault('recent-vault3', 'Vault 3', vault3Path); // Access vaults in specific order with delays to ensure timestamp differences await globalConfig.switchVault('recent-vault1'); await new Promise(resolve => setTimeout(resolve, 10)); await globalConfig.switchVault('recent-vault3'); await new Promise(resolve => setTimeout(resolve, 10)); await globalConfig.switchVault('recent-vault2'); const recentVaults = globalConfig.getRecentVaults(); assert.strictEqual(recentVaults.length, 3); assert.strictEqual(recentVaults[0].info.id, 'recent-vault2'); // Most recent assert.strictEqual(recentVaults[1].info.id, 'recent-vault3'); assert.strictEqual(recentVaults[2].info.id, 'recent-vault1'); // Least recent } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should persist and reload configuration', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'persist-test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault( 'persist-test', 'Test Vault', vaultPath, 'Test description' ); // Create new instance and load const newGlobalConfig = new GlobalConfigManager(); await newGlobalConfig.load(); const vault = newGlobalConfig.getVault('persist-test'); assert.ok(vault); assert.strictEqual(vault.name, 'Test Vault'); assert.strictEqual(vault.description, 'Test description'); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should handle configuration validation errors', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); // Manually corrupt the configuration const corruptConfig = { version: '1.0.0', current_vault: 'non-existent', vaults: {}, settings: {} }; // Write corrupt config const configPath = globalConfig.getConfigPath(); await fs.writeFile(configPath, JSON.stringify(corruptConfig)); // Should throw validation error const newGlobalConfig = new GlobalConfigManager(); await assert.rejects( () => newGlobalConfig.load(), /does not exist in vault registry/ ); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should export and import configuration as JSON', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'export-test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault('export-test', 'Test Vault', vaultPath); const jsonConfig = globalConfig.toJSON(); assert.ok(jsonConfig); const newGlobalConfig = new GlobalConfigManager(); await newGlobalConfig.fromJSON(jsonConfig); const vault = newGlobalConfig.getVault('export-test'); assert.ok(vault); assert.strictEqual(vault.name, 'Test Vault'); } finally { await cleanupTestConfig(tempDir, cleanup); } }); test('should reset configuration to defaults', async () => { const { globalConfig, tempDir, cleanup } = await createTestConfig(); try { await globalConfig.load(); const vaultPath = path.join(tempDir, 'reset-test-vault'); await fs.mkdir(vaultPath, { recursive: true }); await globalConfig.addVault('reset-test', 'Test Vault', vaultPath); assert.ok(globalConfig.hasVault('reset-test')); await globalConfig.reset(); assert.ok(!globalConfig.hasVault('reset-test')); assert.strictEqual(globalConfig.getCurrentVaultPath(), null); } finally { await cleanupTestConfig(tempDir, cleanup); } }); });

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/disnet/flint-note'

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