Skip to main content
Glama
index.test.ts9.53 kB
/** * Tests for Keychain Credential Management * * Tests credential reference parsing, provider detection, and resolution. * * @package WP_Navigator_MCP * @since 2.7.0 */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { parseKeychainReference, createKeychainReference, isKeychainReference, isKeychainAvailable, getKeychainProviderName, resolvePassword, resetKeychainProvider, KEYCHAIN_PREFIX, KEYCHAIN_SERVICE, } from './index.js'; // ============================================================================= // Test Setup // ============================================================================= beforeEach(() => { // Reset the cached provider before each test resetKeychainProvider(); }); afterEach(() => { vi.restoreAllMocks(); }); // ============================================================================= // Constants // ============================================================================= describe('constants', () => { it('should export KEYCHAIN_PREFIX', () => { expect(KEYCHAIN_PREFIX).toBe('keychain:'); }); it('should export KEYCHAIN_SERVICE', () => { expect(KEYCHAIN_SERVICE).toBe('wpnav'); }); }); // ============================================================================= // parseKeychainReference // ============================================================================= describe('parseKeychainReference', () => { it('should parse valid keychain reference', () => { const result = parseKeychainReference('keychain:wpnav:example.com'); expect(result).toEqual({ account: 'example.com' }); }); it('should parse reference with subdomain', () => { const result = parseKeychainReference('keychain:wpnav:blog.example.com'); expect(result).toEqual({ account: 'blog.example.com' }); }); it('should return null for account with port (current limitation)', () => { // Current implementation doesn't support ports in account names // because it splits by ':' and expects exactly 2 parts (service:account) const result = parseKeychainReference('keychain:wpnav:localhost:8080'); expect(result).toBeNull(); }); it('should return null for empty string', () => { expect(parseKeychainReference('')).toBeNull(); }); it('should return null for non-string', () => { expect(parseKeychainReference(null as unknown as string)).toBeNull(); expect(parseKeychainReference(undefined as unknown as string)).toBeNull(); expect(parseKeychainReference(123 as unknown as string)).toBeNull(); }); it('should return null for non-keychain prefix', () => { expect(parseKeychainReference('plaintext-password')).toBeNull(); expect(parseKeychainReference('env:MY_VAR')).toBeNull(); expect(parseKeychainReference('$MY_PASSWORD')).toBeNull(); }); it('should return null for wrong service name', () => { expect(parseKeychainReference('keychain:other:example.com')).toBeNull(); expect(parseKeychainReference('keychain:wpnav2:example.com')).toBeNull(); }); it('should return null for malformed references', () => { expect(parseKeychainReference('keychain:')).toBeNull(); expect(parseKeychainReference('keychain:wpnav')).toBeNull(); expect(parseKeychainReference('keychain:wpnav:')).toBeNull(); expect(parseKeychainReference('keychain:wpnav:ab')).toBeNull(); // too short }); it('should return null for too many colons', () => { // Note: With current implementation, 'foo:bar' after service would be account // This tests the actual behavior - extra colons in account are allowed const result = parseKeychainReference('keychain:wpnav:foo:bar'); // Current impl splits by ':' and checks parts.length !== 2 // 'foo:bar'.split(':') = ['foo', 'bar'], length = 2, so it parses // But the service check fails because first part is 'foo', not 'wpnav' expect(result).toBeNull(); }); }); // ============================================================================= // createKeychainReference // ============================================================================= describe('createKeychainReference', () => { it('should create valid keychain reference', () => { const ref = createKeychainReference('example.com'); expect(ref).toBe('keychain:wpnav:example.com'); }); it('should handle subdomain', () => { const ref = createKeychainReference('blog.example.com'); expect(ref).toBe('keychain:wpnav:blog.example.com'); }); it('should handle localhost (without port)', () => { // Note: Ports are not supported in account names due to ':' delimiter const ref = createKeychainReference('localhost'); expect(ref).toBe('keychain:wpnav:localhost'); }); it('should roundtrip with parseKeychainReference', () => { const account = 'my-site.example.org'; const ref = createKeychainReference(account); const parsed = parseKeychainReference(ref); expect(parsed).toEqual({ account }); }); }); // ============================================================================= // isKeychainReference // ============================================================================= describe('isKeychainReference', () => { it('should return true for valid keychain reference', () => { expect(isKeychainReference('keychain:wpnav:example.com')).toBe(true); }); it('should return false for plaintext password', () => { expect(isKeychainReference('my-secret-password')).toBe(false); }); it('should return false for env var reference', () => { expect(isKeychainReference('$MY_PASSWORD')).toBe(false); expect(isKeychainReference('${WP_APP_PASS}')).toBe(false); }); it('should return false for empty string', () => { expect(isKeychainReference('')).toBe(false); }); it('should return false for malformed reference', () => { expect(isKeychainReference('keychain:wpnav:')).toBe(false); expect(isKeychainReference('keychain:other:site.com')).toBe(false); }); }); // ============================================================================= // Provider Detection // ============================================================================= describe('provider detection', () => { it('should detect provider availability', () => { // This test runs on the actual platform const available = isKeychainAvailable(); expect(typeof available).toBe('boolean'); }); it('should return provider name', () => { const name = getKeychainProviderName(); expect(typeof name).toBe('string'); expect(name.length).toBeGreaterThan(0); // Should be one of the known providers expect([ 'macOS Keychain', 'Linux Secret Service', 'Windows Credential Manager', 'Fallback (unavailable)', ]).toContain(name); }); it('should cache provider across calls', () => { const name1 = getKeychainProviderName(); const name2 = getKeychainProviderName(); expect(name1).toBe(name2); }); }); // ============================================================================= // resolvePassword // ============================================================================= describe('resolvePassword', () => { it('should return literal password with config source', async () => { const result = await resolvePassword('my-literal-password'); expect(result.source).toBe('config'); expect(result.password).toBe('my-literal-password'); expect(result.error).toBeUndefined(); }); it('should return empty string as literal', async () => { const result = await resolvePassword(''); expect(result.source).toBe('config'); expect(result.password).toBe(''); }); it('should return env var reference as literal', async () => { // resolvePassword doesn't expand env vars - that's wpnav-config's job const result = await resolvePassword('$MY_PASSWORD'); expect(result.source).toBe('config'); expect(result.password).toBe('$MY_PASSWORD'); }); it('should attempt keychain resolution for keychain reference', async () => { // Note: This will actually try to access the keychain // On macOS, it might prompt for permission or return not found const result = await resolvePassword('keychain:wpnav:nonexistent-test-site-12345.example'); // Should either succeed (keychain) or return error (not found / unavailable) if (result.source === 'keychain') { expect(result.password).toBeTruthy(); } else { expect(result.source).toBe('none'); expect(result.password).toBeNull(); expect(result.error).toBeTruthy(); } }); }); // ============================================================================= // Platform-specific behavior // ============================================================================= describe('platform detection', () => { it('should detect current platform', () => { const name = getKeychainProviderName(); if (process.platform === 'darwin') { expect(name).toBe('macOS Keychain'); } else if (process.platform === 'linux') { // May be Secret Service or Fallback depending on secret-tool availability expect(['Linux Secret Service', 'Fallback (unavailable)']).toContain(name); } else if (process.platform === 'win32') { // May be Windows Credential Manager or Fallback depending on cmdkey availability expect(['Windows Credential Manager', 'Fallback (unavailable)']).toContain(name); } else { expect(name).toBe('Fallback (unavailable)'); } }); });

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/littlebearapps/wp-navigator-mcp'

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