Skip to main content
Glama
shared-client.ts4.07 kB
/** * Shared Test Client Singleton * * Provides a singleton pattern for test clients to avoid rate limiting * and improve test performance by reusing authentication tokens. * * Thread-safe implementation ensures only one client instance is created * even when tests run in parallel. */ import type { NavidromeClient } from '../../src/client/navidrome-client.js'; import { logger } from '../../src/utils/logger.js'; /** * Singleton instance holder with lazy initialization */ class SharedTestClient { private static instance: SharedTestClient | null = null; private client: NavidromeClient | null = null; private initializationPromise: Promise<NavidromeClient> | null = null; private initializationError: Error | null = null; private lastInitAttempt: number = 0; private readonly RETRY_DELAY = 5000; // 5 seconds between retry attempts private constructor() { // Private constructor to enforce singleton pattern } /** * Get the singleton instance */ public static getInstance(): SharedTestClient { if (!SharedTestClient.instance) { SharedTestClient.instance = new SharedTestClient(); } return SharedTestClient.instance; } /** * Get or create the shared client instance * Thread-safe implementation ensures only one initialization happens */ public async getClient(): Promise<NavidromeClient> { // If we have a working client, return it if (this.client) { return this.client; } // Check if we should retry after a previous error if (this.initializationError) { const timeSinceLastAttempt = Date.now() - this.lastInitAttempt; if (timeSinceLastAttempt < this.RETRY_DELAY) { throw new Error( `Previous initialization failed. Waiting ${Math.ceil((this.RETRY_DELAY - timeSinceLastAttempt) / 1000)}s before retry. Error: ${this.initializationError.message}` ); } // Clear error state for retry this.initializationError = null; this.initializationPromise = null; } // If initialization is already in progress, wait for it if (this.initializationPromise) { return this.initializationPromise; } // Start new initialization this.initializationPromise = this.initializeClient(); try { this.client = await this.initializationPromise; return this.client; } catch (error) { // Store error state for retry logic this.initializationError = error as Error; this.lastInitAttempt = Date.now(); throw error; } finally { // Clear the promise so next call can retry if needed if (this.initializationError) { this.initializationPromise = null; } } } /** * Initialize the Navidrome client */ private async initializeClient(): Promise<NavidromeClient> { logger.info('Creating shared test client for all tests...'); const { NavidromeClient } = await import('../../src/client/navidrome-client.js'); const { loadConfig } = await import('../../src/config.js'); // Load test configuration const config = await loadConfig(); // Create and initialize client const client = new NavidromeClient(config); await client.initialize(); logger.info('Shared test client initialized successfully'); return client; } /** * Reset the shared client (useful for test cleanup) * Should only be called between test suites if needed */ public reset(): void { logger.info('Resetting shared test client'); this.client = null; this.initializationPromise = null; this.initializationError = null; this.lastInitAttempt = 0; } /** * Check if client is currently initialized */ public isInitialized(): boolean { return this.client !== null; } } /** * Get the shared live client for testing * This replaces createLiveClient() in individual tests */ export async function getSharedLiveClient(): Promise<NavidromeClient> { const sharedClient = SharedTestClient.getInstance(); return sharedClient.getClient(); }

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/Blakeem/Navidrome-MCP'

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