Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
extension-global-cli.test.ts8 kB
/** * Global CLI Setup Test Suite * * Tests for the global CLI symlink setup functionality to ensure: * - NPM installations are detected and not overwritten * - DXT can create symlinks when no npm installation exists * - Error handling works correctly */ import { existsSync, readlinkSync, symlinkSync, unlinkSync, mkdirSync, rmSync, writeFileSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { parseExtensionConfig, initializeExtension } from '../src/extension/extension-init.js'; // Test configuration const TEST_BIN_DIR = join(tmpdir(), 'ncp-test-bin'); const TEST_NCP_LINK = join(TEST_BIN_DIR, 'ncp'); const TEST_CONFIG_DIR = join(tmpdir(), 'ncp-test-global-cli-config'); // Mock __dirname for extension executable path const mockExtensionDir = join(tmpdir(), 'ncp-test-extension'); const mockNcpExecutable = join(mockExtensionDir, 'dist', 'index.js'); describe('Global CLI Setup', () => { beforeAll(() => { // Create test directories mkdirSync(TEST_BIN_DIR, { recursive: true }); mkdirSync(TEST_CONFIG_DIR, { recursive: true }); mkdirSync(join(mockExtensionDir, 'dist'), { recursive: true }); // Create a mock executable writeFileSync(mockNcpExecutable, '#!/usr/bin/env node\nconsole.log("NCP Mock");\n'); }); afterAll(() => { // Clean up test directories if (existsSync(TEST_BIN_DIR)) { rmSync(TEST_BIN_DIR, { recursive: true, force: true }); } if (existsSync(TEST_CONFIG_DIR)) { rmSync(TEST_CONFIG_DIR, { recursive: true, force: true }); } if (existsSync(mockExtensionDir)) { rmSync(mockExtensionDir, { recursive: true, force: true }); } }); beforeEach(() => { // Clean up any existing symlink before each test if (existsSync(TEST_NCP_LINK)) { unlinkSync(TEST_NCP_LINK); } // Reset environment delete process.env.NCP_ENABLE_GLOBAL_CLI; delete process.env.NCP_PROFILE; delete process.env.NCP_CONFIG_PATH; delete process.env.NCP_DEBUG; }); describe('Extension Config Parsing', () => { it('should parse enable_global_cli from environment', () => { process.env.NCP_ENABLE_GLOBAL_CLI = 'true'; const config = parseExtensionConfig(); expect(config.enableGlobalCLI).toBe(true); }); it('should default to false when not set', () => { const config = parseExtensionConfig(); expect(config.enableGlobalCLI).toBe(false); }); it('should parse false correctly', () => { process.env.NCP_ENABLE_GLOBAL_CLI = 'false'; const config = parseExtensionConfig(); expect(config.enableGlobalCLI).toBe(false); }); }); describe('NPM Installation Detection', () => { it('should detect npm installation by path pattern (Unix)', () => { // Create a symlink that looks like npm installed it const npmTarget = '../lib/node_modules/@portel/ncp/dist/index.js'; symlinkSync(mockNcpExecutable, TEST_NCP_LINK); // Update the symlink to point to a mock npm location unlinkSync(TEST_NCP_LINK); const mockNpmDir = join(TEST_BIN_DIR, '..', 'lib', 'node_modules', '@portel', 'ncp', 'dist'); mkdirSync(mockNpmDir, { recursive: true }); const mockNpmExecutable = join(mockNpmDir, 'index.js'); writeFileSync(mockNpmExecutable, '#!/usr/bin/env node\nconsole.log("NPM NCP");\n'); symlinkSync(mockNpmExecutable, TEST_NCP_LINK); // Verify it's detected as npm installation const target = readlinkSync(TEST_NCP_LINK); expect(target.includes('node_modules/@portel/ncp')).toBe(true); }); it('should detect npm installation by path pattern (Windows)', () => { // Create a symlink with Windows-style path const mockNpmDir = join(TEST_BIN_DIR, '..', 'lib', 'node_modules', '@portel', 'ncp', 'dist'); mkdirSync(mockNpmDir, { recursive: true }); const mockNpmExecutable = join(mockNpmDir, 'index.js'); writeFileSync(mockNpmExecutable, '#!/usr/bin/env node\nconsole.log("NPM NCP");\n'); symlinkSync(mockNpmExecutable, TEST_NCP_LINK); // Verify detection works with backslashes (Windows style) const target = readlinkSync(TEST_NCP_LINK); const windowsTarget = target.replace(/\//g, '\\'); expect( windowsTarget.includes('node_modules\\@portel\\ncp') || target.includes('node_modules/@portel/ncp') ).toBe(true); }); it('should not detect DXT installation as npm', () => { // Create a symlink that looks like DXT installed it symlinkSync(mockNcpExecutable, TEST_NCP_LINK); const target = readlinkSync(TEST_NCP_LINK); expect(target.includes('node_modules/@portel/ncp')).toBe(false); }); }); describe('Symlink Behavior', () => { it('should handle missing existing symlink', () => { expect(existsSync(TEST_NCP_LINK)).toBe(false); // Test would proceed to create symlink }); it('should be able to read symlink target', () => { symlinkSync(mockNcpExecutable, TEST_NCP_LINK); const target = readlinkSync(TEST_NCP_LINK); expect(target).toBe(mockNcpExecutable); }); it('should be able to replace non-npm symlink', () => { // Create initial symlink const oldTarget = join(tmpdir(), 'old-ncp'); writeFileSync(oldTarget, '#!/usr/bin/env node\nconsole.log("Old");\n'); symlinkSync(oldTarget, TEST_NCP_LINK); // Verify it exists expect(existsSync(TEST_NCP_LINK)).toBe(true); expect(readlinkSync(TEST_NCP_LINK)).toBe(oldTarget); // Replace with new symlink unlinkSync(TEST_NCP_LINK); symlinkSync(mockNcpExecutable, TEST_NCP_LINK); // Verify it was replaced expect(readlinkSync(TEST_NCP_LINK)).toBe(mockNcpExecutable); }); }); describe('Error Handling', () => { it('should handle readlinkSync errors gracefully', () => { // Create a regular file instead of symlink writeFileSync(TEST_NCP_LINK, 'not a symlink'); // readlinkSync should throw on regular files expect(() => readlinkSync(TEST_NCP_LINK)).toThrow(); // Clean up unlinkSync(TEST_NCP_LINK); }); it('should handle non-existent paths', () => { expect(existsSync(join(TEST_BIN_DIR, 'nonexistent'))).toBe(false); }); }); describe('Integration with Extension Init', () => { it('should skip global CLI setup when disabled', async () => { process.env.NCP_ENABLE_GLOBAL_CLI = 'false'; process.env.NCP_CONFIG_PATH = TEST_CONFIG_DIR; process.env.NCP_PROFILE = 'test'; // initializeExtension should not create symlink // Note: This would require mocking the setupGlobalCLI function // For now, we just verify the config parsing const config = parseExtensionConfig(); expect(config.enableGlobalCLI).toBe(false); }); it('should parse global CLI config when enabled', async () => { process.env.NCP_ENABLE_GLOBAL_CLI = 'true'; process.env.NCP_CONFIG_PATH = TEST_CONFIG_DIR; process.env.NCP_PROFILE = 'test'; const config = parseExtensionConfig(); expect(config.enableGlobalCLI).toBe(true); }); }); describe('Path Expansion', () => { it('should handle tilde expansion in config path', () => { process.env.NCP_CONFIG_PATH = '~/.ncp'; const config = parseExtensionConfig(); // Should expand ~ to home directory expect(config.configPath).not.toContain('~'); expect(config.configPath).toContain('.ncp'); }); it('should handle absolute paths', () => { const absolutePath = '/tmp/test/.ncp'; process.env.NCP_CONFIG_PATH = absolutePath; const config = parseExtensionConfig(); expect(config.configPath).toBe(absolutePath); }); it('should handle relative paths without tilde', () => { const relativePath = '.ncp'; process.env.NCP_CONFIG_PATH = relativePath; const config = parseExtensionConfig(); expect(config.configPath).toBe(relativePath); }); }); });

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/portel-dev/ncp'

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