Skip to main content
Glama

Vibe Check MCP

env-ensure.test.ts8.94 kB
import { promises as fs } from 'node:fs'; import { join } from 'node:path'; import os from 'node:os'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ensureEnv, homeConfigDir, PROVIDER_ENV_KEYS } from '../src/cli/env.js'; const VALID_PROVIDER_VALUES: Record<(typeof PROVIDER_ENV_KEYS)[number], string> = { ANTHROPIC_API_KEY: 'sk-ant-valid', OPENAI_API_KEY: 'sk-valid', GEMINI_API_KEY: 'AI-valid', OPENROUTER_API_KEY: 'sk-or-valid', }; const ORIGINAL_ENV = { ...process.env }; describe('ensureEnv', () => { beforeEach(() => { process.exitCode = undefined; process.env = { ...ORIGINAL_ENV }; for (const key of PROVIDER_ENV_KEYS) { delete process.env[key]; } }); afterEach(() => { vi.restoreAllMocks(); process.env = { ...ORIGINAL_ENV }; }); it.each(PROVIDER_ENV_KEYS)( 'returns without writing when %s is present non-interactively', async (key) => { process.env[key] = VALID_PROVIDER_VALUES[key]; const result = await ensureEnv({ interactive: false }); expect(result.wrote).toBe(false); expect(result.path).toBeUndefined(); expect(result.missing).toBeUndefined(); }, ); it('surfaces invalid optional keys even when another provider is set non-interactively', async () => { process.env.ANTHROPIC_API_KEY = VALID_PROVIDER_VALUES.ANTHROPIC_API_KEY; process.env.OPENAI_API_KEY = 'totally-invalid'; const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: false }); expect(result.wrote).toBe(false); expect(result.missing).toContain('OPENAI_API_KEY'); expect(logSpy).toHaveBeenCalledWith( expect.stringContaining('Invalid OPENAI_API_KEY'), ); logSpy.mockRestore(); }); it('reports missing values when non-interactive', async () => { delete process.env.ANTHROPIC_API_KEY; const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: false }); expect(result.wrote).toBe(false); expect(result.missing).toEqual([...PROVIDER_ENV_KEYS]); expect(logSpy).toHaveBeenCalledWith( expect.stringContaining('No provider API keys detected'), ); logSpy.mockRestore(); }); it('writes missing secrets to the home config when interactive', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); const prompt = vi.fn().mockImplementation(async (key: string) => { if (key === 'ANTHROPIC_API_KEY') { return 'sk-ant-interactive-secret'; } return ''; }); const result = await ensureEnv({ interactive: true, prompt }); expect(prompt).toHaveBeenCalledWith('ANTHROPIC_API_KEY'); expect(prompt.mock.calls.length).toBe(1); expect(result.wrote).toBe(true); expect(result.path).toBe(join(homeConfigDir(), '.env')); const stat = await fs.stat(result.path as string); expect(stat.mode & 0o777).toBe(0o600); const content = await fs.readFile(result.path as string, 'utf8'); expect(content).toContain('ANTHROPIC_API_KEY=sk-ant-interactive-secret'); expect(process.env.ANTHROPIC_API_KEY).toBe('sk-ant-interactive-secret'); }); it('loads missing secrets from existing env files', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); const homeDir = join(tmpHome, '.vibe-check'); await fs.mkdir(homeDir, { recursive: true }); await fs.writeFile(join(homeDir, '.env'), 'OPENAI_API_KEY=sk-from-file\n', 'utf8'); const result = await ensureEnv({ interactive: false }); expect(result.wrote).toBe(false); expect(process.env.OPENAI_API_KEY).toBe('sk-from-file'); }); it('appends new secrets to the local project env file', async () => { const tmpDir = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-local-')); const originalCwd = process.cwd(); await fs.writeFile(join(tmpDir, '.env'), 'EXISTING=value\n', 'utf8'); try { process.chdir(tmpDir); const prompt = vi.fn().mockImplementation(async (key: string) => { if (key === 'GEMINI_API_KEY') { return 'AI value with spaces'; } return ''; }); const result = await ensureEnv({ interactive: true, local: true, prompt }); expect(result.path).toBe(join(tmpDir, '.env')); const content = await fs.readFile(result.path as string, 'utf8'); expect(content).toContain('EXISTING=value'); expect(content).toMatch(/GEMINI_API_KEY="AI value with spaces"/); } finally { process.chdir(originalCwd); } }); it('honors requiredKeys in non-interactive mode', async () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: false, requiredKeys: ['ANTHROPIC_API_KEY'] }); expect(result.wrote).toBe(false); expect(result.missing).toEqual(['ANTHROPIC_API_KEY']); expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Missing required API keys')); logSpy.mockRestore(); }); it('prompts for each required key when interactive', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-required-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); const prompt = vi .fn() .mockImplementation(async (key: string) => key === 'ANTHROPIC_API_KEY' ? 'sk-ant-anthropic-123' : 'sk-openai-456', ); const result = await ensureEnv({ interactive: true, requiredKeys: ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY'], prompt, }); expect(prompt.mock.calls.map((call) => call[0])).toEqual([ 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', ]); expect(result.wrote).toBe(true); expect(result.path).toBe(join(homeConfigDir(), '.env')); expect(process.env.ANTHROPIC_API_KEY).toBe('sk-ant-anthropic-123'); expect(process.env.OPENAI_API_KEY).toBe('sk-openai-456'); const content = await fs.readFile(result.path as string, 'utf8'); expect(content).toContain('ANTHROPIC_API_KEY=sk-ant-anthropic-123'); expect(content).toContain('OPENAI_API_KEY=sk-openai-456'); }); it('prompts to correct invalid optional keys when another provider is configured', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-invalid-optional-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); process.env.ANTHROPIC_API_KEY = VALID_PROVIDER_VALUES.ANTHROPIC_API_KEY; process.env.OPENAI_API_KEY = 'bad-value'; const prompt = vi.fn().mockResolvedValue('sk-openai-corrected'); const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: true, prompt }); expect(prompt).toHaveBeenCalledTimes(1); expect(prompt).toHaveBeenCalledWith('OPENAI_API_KEY'); expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid OPENAI_API_KEY')); expect(result.wrote).toBe(true); expect(process.env.OPENAI_API_KEY).toBe('sk-openai-corrected'); const content = await fs.readFile(join(homeConfigDir(), '.env'), 'utf8'); expect(content).toContain('OPENAI_API_KEY=sk-openai-corrected'); logSpy.mockRestore(); }); it('re-prompts when provided values fail validation', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-retry-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); const prompt = vi .fn() .mockResolvedValueOnce('invalid') .mockResolvedValueOnce('sk-ant-correct'); const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: true, requiredKeys: ['ANTHROPIC_API_KEY'], prompt, }); expect(prompt).toHaveBeenCalledTimes(2); expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid ANTHROPIC_API_KEY')); expect(result.wrote).toBe(true); expect(process.env.ANTHROPIC_API_KEY).toBe('sk-ant-correct'); logSpy.mockRestore(); }); it('fails immediately when non-interactive values are invalid', async () => { const tmpHome = await fs.mkdtemp(join(os.tmpdir(), 'vibe-env-invalid-')); vi.spyOn(os, 'homedir').mockReturnValue(tmpHome); const homeDir = join(tmpHome, '.vibe-check'); await fs.mkdir(homeDir, { recursive: true }); await fs.writeFile(join(homeDir, '.env'), 'OPENAI_API_KEY=not-valid\n', 'utf8'); const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const result = await ensureEnv({ interactive: false, requiredKeys: ['OPENAI_API_KEY'] }); expect(result.wrote).toBe(false); expect(result.missing).toEqual(['OPENAI_API_KEY']); expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid OPENAI_API_KEY')); logSpy.mockRestore(); }); });

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/PV-Bhat/vibe-check-mcp-server'

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