Skip to main content
Glama
environment-value-provider.test.ts8.34 kB
/** * Test suite for EnvironmentValueProvider * Tests for AC 6.4, 6.15 - Environment preparation and performance optimization * * TDD methodology: Failing tests first for environment value collection and caching */ import * as os from 'os'; import * as fs from 'fs'; import { EnvironmentValueProvider } from './environment-value-provider'; describe('EnvironmentValueProvider', () => { let provider: EnvironmentValueProvider; beforeEach(() => { provider = new EnvironmentValueProvider(); }); describe('Basic Environment Value Collection', () => { it('should collect all standard environment values', async () => { const values = await provider.getValues(); expect(values).toHaveProperty('USER'); expect(values).toHaveProperty('PWD'); expect(values).toHaveProperty('HOSTNAME'); expect(values).toHaveProperty('HOME'); expect(values).toHaveProperty('LS_OUTPUT'); expect(values).toHaveProperty('TIMESTAMP'); expect(typeof values.USER).toBe('string'); expect(typeof values.PWD).toBe('string'); expect(typeof values.HOSTNAME).toBe('string'); expect(typeof values.HOME).toBe('string'); expect(Array.isArray(values.LS_OUTPUT)).toBe(true); expect(typeof values.TIMESTAMP).toBe('string'); }); it('should resolve USER correctly from environment or os.userInfo', async () => { const values = await provider.getValues(); const expected = process.env.USER || os.userInfo().username; expect(values.USER).toBe(expected); }); it('should resolve PWD as current working directory', async () => { const values = await provider.getValues(); expect(values.PWD).toBe(process.cwd()); }); it('should resolve HOSTNAME from os.hostname()', async () => { const values = await provider.getValues(); expect(values.HOSTNAME).toBe(os.hostname()); }); it('should resolve HOME from os.homedir()', async () => { const values = await provider.getValues(); expect(values.HOME).toBe(os.homedir()); }); it('should provide directory listing in LS_OUTPUT', async () => { const values = await provider.getValues(); const actualFiles = fs.readdirSync(process.cwd()); expect(values.LS_OUTPUT).toEqual(actualFiles); }); it('should provide valid timestamp', async () => { const values = await provider.getValues(); expect(Number.isInteger(parseInt(values.TIMESTAMP))).toBe(true); expect(parseInt(values.TIMESTAMP)).toBeGreaterThan(0); }); }); describe('Performance Optimization and Caching (AC 6.15)', () => { it('should cache values for performance', async () => { const values1 = await provider.getValues(); const values2 = await provider.getValues(); // Should return same object (cached) expect(values1).toBe(values2); }); it('should allow cache invalidation', async () => { const values1 = await provider.getValues(); provider.invalidateCache(); const values2 = await provider.getValues(); // Should be different objects after cache invalidation expect(values1).not.toBe(values2); // But values should be equal expect(values1.USER).toBe(values2.USER); expect(values1.PWD).toBe(values2.PWD); }); it('should measure and optimize expensive operations', async () => { // First call (should populate cache) const start1 = process.hrtime(); await provider.getValues(); const [s1, ns1] = process.hrtime(start1); const firstCallTime = s1 * 1000 + ns1 / 1000000; // Second call (should use cache) const start2 = process.hrtime(); await provider.getValues(); const [s2, ns2] = process.hrtime(start2); const secondCallTime = s2 * 1000 + ns2 / 1000000; // Cached call should be significantly faster (or at least not slower) expect(secondCallTime).toBeLessThanOrEqual(firstCallTime + 1); // Allow 1ms tolerance }); it('should handle cache expiration if configured', async () => { const provider = new EnvironmentValueProvider({ cacheTimeoutMs: 100 }); const values1 = await provider.getValues(); // Wait for cache to expire await new Promise(resolve => setTimeout(resolve, 150)); const values2 = await provider.getValues(); // Should be different objects after cache expiration expect(values1).not.toBe(values2); }); }); describe('Custom Variable Support', () => { it('should support custom variable definitions', async () => { const provider = new EnvironmentValueProvider({ customVariables: { 'CUSTOM_VAR': 'custom_value' } }); const values = await provider.getValues(); expect(values.CUSTOM_VAR).toBe('custom_value'); }); it('should execute command-based custom variables', async () => { const provider = new EnvironmentValueProvider({ customVariables: { 'NODE_VERSION': 'exec:node --version' } }); const values = await provider.getValues(); expect(values.NODE_VERSION).toMatch(/^v\d+\.\d+\.\d+/); }); it('should handle git branch custom variable', async () => { if (!fs.existsSync('.git')) { return; // Skip if not in git repository } const provider = new EnvironmentValueProvider({ customVariables: { 'GIT_BRANCH': 'exec:git branch --show-current' } }); const values = await provider.getValues(); expect(typeof values.GIT_BRANCH).toBe('string'); expect(values.GIT_BRANCH.length).toBeGreaterThan(0); }); it('should handle command execution failures gracefully', async () => { const provider = new EnvironmentValueProvider({ customVariables: { 'FAILING_COMMAND': 'exec:nonexistent-command-xyz' }, failOnCommandError: false }); const values = await provider.getValues(); expect(values.FAILING_COMMAND).toBe(''); // Should default to empty string on failure }); }); describe('Cross-Platform Compatibility', () => { it('should handle path separators correctly across platforms', async () => { const values = await provider.getValues(); // PWD should use platform-appropriate separators expect(values.PWD).toBe(process.cwd()); }); it('should handle user account naming conventions', async () => { const values = await provider.getValues(); // Should work on Windows, Linux, and macOS expect(values.USER).toBeDefined(); expect(values.USER.length).toBeGreaterThan(0); }); it('should provide consistent results across different environments', async () => { const values = await provider.getValues(); // All required values should be present regardless of platform const requiredKeys = ['USER', 'PWD', 'HOSTNAME', 'HOME', 'LS_OUTPUT', 'TIMESTAMP']; requiredKeys.forEach(key => { expect(values).toHaveProperty(key); }); }); }); describe('Error Handling and Edge Cases', () => { it('should handle missing environment variables gracefully', async () => { // Temporarily remove USER from environment const originalUser = process.env.USER; delete process.env.USER; try { const values = await provider.getValues(); // Should fall back to os.userInfo().username expect(values.USER).toBe(os.userInfo().username); } finally { if (originalUser) { process.env.USER = originalUser; } } }); it('should handle directory listing errors', async () => { const provider = new EnvironmentValueProvider({ targetDirectory: '/nonexistent/directory' }); await expect(provider.getValues()).rejects.toThrow(); }); it('should validate required environment values', async () => { const values = await provider.getValues(); expect(values.USER).toBeTruthy(); expect(values.PWD).toBeTruthy(); expect(values.HOSTNAME).toBeTruthy(); expect(values.HOME).toBeTruthy(); }); }); });

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/LightspeedDMS/ssh-mcp'

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