Skip to main content
Glama
dynamic-expected-value-constructor.ts6.24 kB
/** * DynamicExpectedValueConstructor * Implementation for Story 06: Dynamic Expected Value Construction * * Main orchestrator class that coordinates EnvironmentValueProvider and TemplateResolver * to provide runtime generation of expected command outputs with parameterized values. * * CRITICAL: No mocks - uses real environment values and template resolution. */ import { EnvironmentValueProvider, EnvironmentValues } from './environment-value-provider'; import { TemplateResolver } from './template-resolver'; /** * Configuration for DynamicExpectedValueConstructor */ export interface DynamicExpectedValueConstructorConfig { customVariables?: Record<string, string>; // Custom variable definitions fallbackValues?: Record<string, string>; // Fallback values for missing variables cacheTimeoutMs?: number; // Cache timeout for environment values templateConfig?: { maxRecursionDepth?: number; commandTimeout?: number; }; } /** * Volatile pattern types for regex generation */ export type VolatilePatternType = 'timestamp' | 'date' | 'pid' | 'memory' | 'time'; /** * DynamicExpectedValueConstructor class */ export class DynamicExpectedValueConstructor { private readonly environmentProvider: EnvironmentValueProvider; private readonly config: DynamicExpectedValueConstructorConfig; private templateResolverCache: Map<string, TemplateResolver> = new Map(); constructor(config: DynamicExpectedValueConstructorConfig = {}) { this.config = config; // Initialize environment provider with custom variables this.environmentProvider = new EnvironmentValueProvider({ customVariables: this.mergeCustomVariables(), cacheTimeoutMs: config.cacheTimeoutMs }); } /** * Resolve template string to actual values * AC 6.5: Dynamic value substitution in expected outputs * AC 6.10: Template variable expansion system */ async resolveTemplate(template: string): Promise<string> { // Validate template syntax first TemplateResolver.validateSyntax(template); // Get environment values const environmentValues = await this.getEnvironmentValues(); // Get or create template resolver for this environment const cacheKey = this.getResolverCacheKey(environmentValues); let resolver = this.templateResolverCache.get(cacheKey); if (!resolver) { resolver = new TemplateResolver( environmentValues, this.config.customVariables || {}, this.config.templateConfig ); this.templateResolverCache.set(cacheKey, resolver); } try { return await resolver.resolve(template); } catch (error) { // Add fallback handling const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('Unknown template variable') && this.config.fallbackValues) { const unknownVar = this.extractUnknownVariable(errorMessage); if (unknownVar && this.config.fallbackValues[unknownVar]) { // Create new resolver with fallback values const fallbackResolver = new TemplateResolver( { ...environmentValues, ...this.config.fallbackValues }, this.config.customVariables || {}, this.config.templateConfig ); return await fallbackResolver.resolve(template); } } throw error; } } /** * Check if actual output matches dynamic pattern * AC 6.13: Dynamic expected value integration with Jest matchers */ async matchesDynamicPattern(actualOutput: string, template: string): Promise<boolean> { try { const resolvedTemplate = await this.resolveTemplate(template); return actualOutput === resolvedTemplate; } catch (error) { return false; } } /** * Get environment values with caching * AC 6.4: Pre-execution environment preparation */ async getEnvironmentValues(): Promise<EnvironmentValues> { return await this.environmentProvider.getValues(); } /** * Invalidate all cached values * AC 6.15: Cache invalidation for environment changes */ invalidateCache(): void { this.environmentProvider.invalidateCache(); this.templateResolverCache.clear(); } /** * Create regex pattern for volatile outputs * AC 6.7: Volatile output handling with regex patterns */ createVolatilePattern(patternType: VolatilePatternType): RegExp { switch (patternType) { case 'timestamp': // Matches Unix timestamps return /\d{10,13}/; case 'date': // Matches date command output like "Mon Dec 25 10:30:45 UTC 2023" return /\w{3}\s+\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\w{3}\s+\d{4}/i; case 'pid': // Matches process IDs return /\d+/; case 'memory': // Matches memory usage patterns like "1.2MB" or "512KB" return /\d+\.?\d*[KMGT]?B/i; case 'time': // Matches time patterns like "10:30:45" return /\d{2}:\d{2}:\d{2}/; default: throw new Error(`Unknown volatile pattern type: ${patternType}`); } } /** * Merge custom variables with built-in variables */ private mergeCustomVariables(): Record<string, string> { const builtInVariables: Record<string, string> = { 'SSH_SESSION_COUNT': 'exec:who | wc -l', 'GIT_BRANCH': 'exec:git branch --show-current 2>/dev/null || echo "not-in-git"', 'NODE_VERSION': 'exec:node --version' }; return { ...builtInVariables, ...(this.config.customVariables || {}) }; } /** * Generate cache key for template resolver */ private getResolverCacheKey(environmentValues: EnvironmentValues): string { const keyData = { user: environmentValues.USER, pwd: environmentValues.PWD, hostname: environmentValues.HOSTNAME, timestamp: environmentValues.TIMESTAMP }; return JSON.stringify(keyData); } /** * Extract unknown variable name from error message */ private extractUnknownVariable(errorMessage: string): string | null { const match = errorMessage.match(/Unknown template variable: (\w+)/); return match ? match[1] : null; } }

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