Skip to main content
Glama
index.test.ts14.5 kB
/** * Tests for embedding index/factory functions * Tests client creation, configuration, and initialization patterns */ import { describe, expect, test } from "bun:test"; describe("embeddings index", () => { describe("provider factory", () => { type Provider = "local" | "openai" | "nomic" | "voyage" | "ollama"; interface EmbeddingConfig { provider: Provider; apiKey?: string; endpoint?: string; model?: string; } interface ClientOptions { type: Provider; endpoint?: string; apiKey?: string; model: string; } function createClientOptions(config: EmbeddingConfig): ClientOptions { const defaults: Record<Provider, { endpoint?: string; model: string }> = { local: { endpoint: "http://localhost:8080", model: "" }, openai: { model: "text-embedding-3-small" }, nomic: { model: "nomic-embed-text-v1.5" }, voyage: { model: "voyage-3" }, ollama: { endpoint: "http://localhost:11434", model: "nomic-embed-text", }, }; const providerDefaults = defaults[config.provider]; return { type: config.provider, endpoint: config.endpoint ?? providerDefaults.endpoint, apiKey: config.apiKey, model: config.model ?? providerDefaults.model, }; } test("creates local client options", () => { const options = createClientOptions({ provider: "local" }); expect(options.type).toBe("local"); expect(options.endpoint).toBe("http://localhost:8080"); }); test("creates openai client options", () => { const options = createClientOptions({ provider: "openai", apiKey: "sk-123", }); expect(options.type).toBe("openai"); expect(options.model).toBe("text-embedding-3-small"); expect(options.apiKey).toBe("sk-123"); }); test("creates nomic client options", () => { const options = createClientOptions({ provider: "nomic", apiKey: "nk-123", }); expect(options.type).toBe("nomic"); expect(options.model).toBe("nomic-embed-text-v1.5"); }); test("creates voyage client options", () => { const options = createClientOptions({ provider: "voyage", apiKey: "pa-123", }); expect(options.type).toBe("voyage"); expect(options.model).toBe("voyage-3"); }); test("creates ollama client options", () => { const options = createClientOptions({ provider: "ollama" }); expect(options.type).toBe("ollama"); expect(options.endpoint).toBe("http://localhost:11434"); expect(options.model).toBe("nomic-embed-text"); }); test("overrides default endpoint", () => { const options = createClientOptions({ provider: "local", endpoint: "http://custom:9999", }); expect(options.endpoint).toBe("http://custom:9999"); }); test("overrides default model", () => { const options = createClientOptions({ provider: "openai", model: "text-embedding-ada-002", }); expect(options.model).toBe("text-embedding-ada-002"); }); }); describe("API key validation", () => { type Provider = "local" | "openai" | "nomic" | "voyage" | "ollama"; function requiresApiKey(provider: Provider): boolean { return ["openai", "nomic", "voyage"].includes(provider); } function validateApiKey( provider: Provider, apiKey?: string, ): { valid: boolean; error?: string } { if (!requiresApiKey(provider)) { return { valid: true }; } if (!apiKey || apiKey.length === 0) { return { valid: false, error: `API key required for ${provider}` }; } return { valid: true }; } test("openai requires API key", () => { expect(requiresApiKey("openai")).toBe(true); }); test("nomic requires API key", () => { expect(requiresApiKey("nomic")).toBe(true); }); test("voyage requires API key", () => { expect(requiresApiKey("voyage")).toBe(true); }); test("local does not require API key", () => { expect(requiresApiKey("local")).toBe(false); }); test("ollama does not require API key", () => { expect(requiresApiKey("ollama")).toBe(false); }); test("validates valid openai key", () => { const result = validateApiKey("openai", "sk-abc123"); expect(result.valid).toBe(true); }); test("rejects missing openai key", () => { const result = validateApiKey("openai", undefined); expect(result.valid).toBe(false); expect(result.error).toContain("openai"); }); test("rejects empty openai key", () => { const result = validateApiKey("openai", ""); expect(result.valid).toBe(false); }); test("validates local without key", () => { const result = validateApiKey("local", undefined); expect(result.valid).toBe(true); }); }); describe("endpoint validation", () => { function isValidEndpoint(endpoint: string): boolean { try { const url = new URL(endpoint); return url.protocol === "http:" || url.protocol === "https:"; } catch { return false; } } test("validates http endpoint", () => { expect(isValidEndpoint("http://localhost:8080")).toBe(true); }); test("validates https endpoint", () => { expect(isValidEndpoint("https://api.example.com")).toBe(true); }); test("rejects invalid URL", () => { expect(isValidEndpoint("not-a-url")).toBe(false); }); test("rejects ftp protocol", () => { expect(isValidEndpoint("ftp://example.com")).toBe(false); }); test("validates endpoint with path", () => { expect(isValidEndpoint("http://localhost:8080/v1")).toBe(true); }); }); describe("client initialization", () => { interface ClientState { initialized: boolean; error?: string; } function initializeClient( hasEndpoint: boolean, hasApiKey: boolean, requiresApiKey: boolean, ): ClientState { if (requiresApiKey && !hasApiKey) { return { initialized: false, error: "Missing API key" }; } if (!hasEndpoint) { return { initialized: false, error: "Missing endpoint" }; } return { initialized: true }; } test("initializes with endpoint and no key required", () => { const state = initializeClient(true, false, false); expect(state.initialized).toBe(true); }); test("initializes with endpoint and key when required", () => { const state = initializeClient(true, true, true); expect(state.initialized).toBe(true); }); test("fails when key required but missing", () => { const state = initializeClient(true, false, true); expect(state.initialized).toBe(false); expect(state.error).toBe("Missing API key"); }); test("fails when endpoint missing", () => { const state = initializeClient(false, true, false); expect(state.initialized).toBe(false); expect(state.error).toBe("Missing endpoint"); }); }); describe("singleton pattern", () => { function createSingleton<T>(factory: () => T): () => T { let instance: T | null = null; return () => { if (instance === null) { instance = factory(); } return instance; }; } test("returns same instance", () => { let callCount = 0; const getInstance = createSingleton(() => { callCount++; return { id: Math.random() }; }); const first = getInstance(); const second = getInstance(); expect(first).toBe(second); expect(callCount).toBe(1); }); test("calls factory only once", () => { let callCount = 0; const getInstance = createSingleton(() => { callCount++; return {}; }); getInstance(); getInstance(); getInstance(); expect(callCount).toBe(1); }); }); describe("lazy initialization", () => { interface LazyClient { isInitialized: boolean; initialize: () => void; } function createLazyClient(): LazyClient { let initialized = false; return { get isInitialized() { return initialized; }, initialize() { initialized = true; }, }; } test("not initialized on creation", () => { const client = createLazyClient(); expect(client.isInitialized).toBe(false); }); test("initialized after initialize call", () => { const client = createLazyClient(); client.initialize(); expect(client.isInitialized).toBe(true); }); }); describe("export validation", () => { const expectedExports = [ "createEmbeddingClient", "CachedEmbeddingClient", "EmbeddingConfig", ]; function hasExpectedExports( exports: string[], expected: string[], ): boolean { return expected.every((e) => exports.includes(e)); } test("has all expected exports", () => { const moduleExports = [ "createEmbeddingClient", "CachedEmbeddingClient", "EmbeddingConfig", "embed", "embedBatch", ]; expect(hasExpectedExports(moduleExports, expectedExports)).toBe(true); }); test("detects missing exports", () => { const moduleExports = ["createEmbeddingClient"]; expect(hasExpectedExports(moduleExports, expectedExports)).toBe(false); }); }); describe("environment variable handling", () => { function getEnvKey(provider: string): string | undefined { const envMap: Record<string, string> = { openai: "OPENAI_API_KEY", nomic: "NOMIC_API_KEY", voyage: "VOYAGE_API_KEY", }; const envName = envMap[provider]; if (!envName) return undefined; // Simulate env lookup (in real code would be process.env[envName]) return undefined; // Default to undefined in tests } function resolveApiKey( provider: string, explicitKey?: string, ): string | undefined { if (explicitKey) return explicitKey; return getEnvKey(provider); } test("uses explicit key over env", () => { const key = resolveApiKey("openai", "explicit-key"); expect(key).toBe("explicit-key"); }); test("returns undefined when no key", () => { const key = resolveApiKey("openai", undefined); expect(key).toBeUndefined(); }); test("returns explicit key for local", () => { const key = resolveApiKey("local", "some-key"); expect(key).toBe("some-key"); }); }); describe("dimension detection", () => { function getExpectedDimension(provider: string, model: string): number { const dimensions: Record<string, number> = { "text-embedding-3-small": 1536, "text-embedding-3-large": 3072, "text-embedding-ada-002": 1536, "nomic-embed-text-v1.5": 768, "voyage-3": 1024, "voyage-2": 1024, "nomic-embed-text": 768, }; return dimensions[model] ?? 768; // Default dimension } test("openai small dimension", () => { expect(getExpectedDimension("openai", "text-embedding-3-small")).toBe( 1536, ); }); test("openai large dimension", () => { expect(getExpectedDimension("openai", "text-embedding-3-large")).toBe( 3072, ); }); test("nomic dimension", () => { expect(getExpectedDimension("nomic", "nomic-embed-text-v1.5")).toBe(768); }); test("voyage dimension", () => { expect(getExpectedDimension("voyage", "voyage-3")).toBe(1024); }); test("unknown model uses default", () => { expect(getExpectedDimension("custom", "unknown-model")).toBe(768); }); }); describe("retry configuration", () => { interface RetryConfig { maxRetries: number; initialDelayMs: number; maxDelayMs: number; backoffMultiplier: number; } function getDefaultRetryConfig(): RetryConfig { return { maxRetries: 3, initialDelayMs: 100, maxDelayMs: 5000, backoffMultiplier: 2, }; } function calculateDelay(attempt: number, config: RetryConfig): number { const delay = config.initialDelayMs * config.backoffMultiplier ** attempt; return Math.min(delay, config.maxDelayMs); } test("default max retries is 3", () => { const config = getDefaultRetryConfig(); expect(config.maxRetries).toBe(3); }); test("first retry delay", () => { const config = getDefaultRetryConfig(); expect(calculateDelay(0, config)).toBe(100); }); test("second retry delay", () => { const config = getDefaultRetryConfig(); expect(calculateDelay(1, config)).toBe(200); }); test("third retry delay", () => { const config = getDefaultRetryConfig(); expect(calculateDelay(2, config)).toBe(400); }); test("caps at max delay", () => { const config = getDefaultRetryConfig(); expect(calculateDelay(10, config)).toBe(5000); }); }); describe("client interface", () => { interface EmbeddingClient { embed(text: string): Promise<number[]>; embedBatch(texts: string[]): Promise<number[][]>; } function isValidClient(client: unknown): client is EmbeddingClient { if (typeof client !== "object" || client === null) return false; const c = client as Record<string, unknown>; return ( typeof c.embed === "function" && typeof c.embedBatch === "function" ); } test("validates valid client", () => { const client = { embed: async () => [0.1], embedBatch: async () => [[0.1]], }; expect(isValidClient(client)).toBe(true); }); test("rejects missing embed", () => { const client = { embedBatch: async () => [[0.1]], }; expect(isValidClient(client)).toBe(false); }); test("rejects missing embedBatch", () => { const client = { embed: async () => [0.1], }; expect(isValidClient(client)).toBe(false); }); test("rejects non-function embed", () => { const client = { embed: "not a function", embedBatch: async () => [[0.1]], }; expect(isValidClient(client)).toBe(false); }); }); });

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/docleaai/doclea-mcp'

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