Skip to main content
Glama
cli-args.test.tsโ€ข23.3 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { parseArgs } from 'node:util'; // Mock the parseArgs function to test our parsing logic vi.mock('node:util', () => ({ parseArgs: vi.fn() })); const mockParseArgs = parseArgs as vi.MockedFunction<typeof parseArgs>; // Import the types we need import type { LogMode } from '../../lib/logger.js'; interface CLIOptions { url: string; authToken: string | undefined; minConnections: number | undefined; maxConnections: number | undefined; connectionTimeout: number | undefined; queryTimeout: number | undefined; help: boolean | undefined; version: boolean | undefined; dev: boolean | undefined; logMode: LogMode | undefined; } // Replicate the parsing logic from index.ts for testing function parseCliArgs(): CLIOptions { const { values } = parseArgs({ args: process.argv.slice(2), options: { url: { type: 'string' }, 'auth-token': { type: 'string' }, 'min-connections': { type: 'string' }, 'max-connections': { type: 'string' }, 'connection-timeout': { type: 'string' }, 'query-timeout': { type: 'string' }, 'log-mode': { type: 'string' }, dev: { type: 'boolean', short: 'd' }, help: { type: 'boolean', short: 'h' }, version: { type: 'boolean', short: 'v' } }, strict: true }); return { url: values.url || '', authToken: values['auth-token'] || process.env['LIBSQL_AUTH_TOKEN'], minConnections: values['min-connections'] ? parseInt(values['min-connections'], 10) : undefined, maxConnections: values['max-connections'] ? parseInt(values['max-connections'], 10) : undefined, connectionTimeout: values['connection-timeout'] ? parseInt(values['connection-timeout'], 10) : undefined, queryTimeout: values['query-timeout'] ? parseInt(values['query-timeout'], 10) : undefined, logMode: values['log-mode'] as LogMode | undefined, dev: values.dev, help: values.help, version: values.version }; } describe('CLI Arguments Parsing', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('url option', () => { it('should parse url correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('libsql://my-db.turso.io'); }); it('should handle missing url', () => { mockParseArgs.mockReturnValue({ values: {}, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe(''); }); it('should handle different url formats', () => { const urls = [ 'file:local.db', 'http://localhost:8080', 'https://remote.db.com', 'libsql://my-db.turso.io' ]; urls.forEach(url => { mockParseArgs.mockReturnValue({ values: { url }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe(url); }); }); }); describe('connection pool options', () => { it('should parse min-connections correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'min-connections': '5' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBe(5); }); it('should parse max-connections correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'max-connections': '20' }, positionals: [] }); const result = parseCliArgs(); expect(result.maxConnections).toBe(20); }); it('should handle missing connection pool options', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBeUndefined(); expect(result.maxConnections).toBeUndefined(); }); it('should parse both connection pool options together', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'min-connections': '2', 'max-connections': '15' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBe(2); expect(result.maxConnections).toBe(15); }); }); describe('timeout options', () => { it('should parse connection-timeout correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'connection-timeout': '5000' }, positionals: [] }); const result = parseCliArgs(); expect(result.connectionTimeout).toBe(5000); }); it('should parse query-timeout correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'query-timeout': '10000' }, positionals: [] }); const result = parseCliArgs(); expect(result.queryTimeout).toBe(10000); }); it('should handle missing timeout options', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.connectionTimeout).toBeUndefined(); expect(result.queryTimeout).toBeUndefined(); }); it('should parse both timeout options together', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'connection-timeout': '3000', 'query-timeout': '8000' }, positionals: [] }); const result = parseCliArgs(); expect(result.connectionTimeout).toBe(3000); expect(result.queryTimeout).toBe(8000); }); }); describe('boolean flags', () => { it('should parse dev flag correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', dev: true }, positionals: [] }); const result = parseCliArgs(); expect(result.dev).toBe(true); }); it('should parse help flag correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', help: true }, positionals: [] }); const result = parseCliArgs(); expect(result.help).toBe(true); }); it('should parse version flag correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', version: true }, positionals: [] }); const result = parseCliArgs(); expect(result.version).toBe(true); }); it('should handle missing boolean flags', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.dev).toBeUndefined(); expect(result.help).toBeUndefined(); expect(result.version).toBeUndefined(); }); }); describe('log-mode option', () => { it('should parse log-mode file correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'file' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('file'); expect(result.url).toBe('file:test.db'); }); it('should parse log-mode console correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'console' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('console'); }); it('should parse log-mode both correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'both' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('both'); }); it('should parse log-mode none correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'none' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('none'); }); it('should return undefined when log-mode is not specified', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBeUndefined(); }); it('should handle log-mode with other options', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'both', 'max-connections': '20', dev: true }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('both'); expect(result.maxConnections).toBe(20); expect(result.dev).toBe(true); expect(result.url).toBe('file:test.db'); }); }); describe('default behavior', () => { it('should default logMode to file when not specified', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); const logMode = result.logMode || 'file'; // This simulates the defaulting logic in main() expect(logMode).toBe('file'); }); }); describe('validation', () => { it('should preserve invalid log-mode values for validation', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'log-mode': 'invalid-mode' }, positionals: [] }); const result = parseCliArgs(); expect(result.logMode).toBe('invalid-mode'); // Note: Actual validation happens in validateOptions() function }); }); describe('comprehensive options parsing', () => { it('should handle all options together', () => { mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'min-connections': '3', 'max-connections': '25', 'connection-timeout': '5000', 'query-timeout': '12000', 'log-mode': 'both', dev: true, help: false, version: false }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('libsql://my-db.turso.io'); expect(result.minConnections).toBe(3); expect(result.maxConnections).toBe(25); expect(result.connectionTimeout).toBe(5000); expect(result.queryTimeout).toBe(12000); expect(result.logMode).toBe('both'); expect(result.dev).toBe(true); expect(result.help).toBe(false); expect(result.version).toBe(false); }); it('should handle minimal required options only', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:minimal.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('file:minimal.db'); expect(result.minConnections).toBeUndefined(); expect(result.maxConnections).toBeUndefined(); expect(result.connectionTimeout).toBeUndefined(); expect(result.queryTimeout).toBeUndefined(); expect(result.logMode).toBeUndefined(); expect(result.dev).toBeUndefined(); expect(result.help).toBeUndefined(); expect(result.version).toBeUndefined(); }); }); describe('edge cases and number parsing', () => { it('should handle string numbers correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'min-connections': '0', 'max-connections': '999', 'connection-timeout': '1000', 'query-timeout': '60000' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBe(0); expect(result.maxConnections).toBe(999); expect(result.connectionTimeout).toBe(1000); expect(result.queryTimeout).toBe(60000); }); it('should handle invalid numbers gracefully', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'min-connections': 'invalid', 'connection-timeout': 'abc', 'query-timeout': '1.5' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBeNaN(); expect(result.connectionTimeout).toBeNaN(); expect(result.queryTimeout).toBe(1); // parseInt truncates to integer }); it('should handle empty string values', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'max-connections': '', 'connection-timeout': '' }, positionals: [] }); const result = parseCliArgs(); expect(result.maxConnections).toBeUndefined(); // Empty string is falsy expect(result.connectionTimeout).toBeUndefined(); // Empty string is falsy }); it('should handle negative numbers', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db', 'min-connections': '-5', 'max-connections': '-10' }, positionals: [] }); const result = parseCliArgs(); expect(result.minConnections).toBe(-5); expect(result.maxConnections).toBe(-10); }); }); }); describe('Log Mode Validation', () => { const validLogModes = ['file', 'console', 'both', 'none']; it('should identify valid log modes', () => { validLogModes.forEach(mode => { expect(validLogModes.includes(mode)).toBe(true); }); }); it('should identify invalid log modes', () => { const invalidModes = ['invalid', 'stdout', 'stderr', '']; invalidModes.forEach(mode => { expect(validLogModes.includes(mode)).toBe(false); }); }); }); describe('CLI Examples from Help Text', () => { it('should parse basic example correctly', () => { // mcp-libsql-server --url "file:local.db" mockParseArgs.mockReturnValue({ values: { url: 'file:local.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('file:local.db'); expect(result.logMode).toBeUndefined(); // Should default to 'file' }); it('should parse turso example correctly', () => { // mcp-libsql-server --url "libsql://your-db.turso.io" --max-connections 20 mockParseArgs.mockReturnValue({ values: { url: 'libsql://your-db.turso.io', 'max-connections': '20' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('libsql://your-db.turso.io'); expect(result.maxConnections).toBe(20); }); it('should parse development example correctly', () => { // mcp-libsql-server --url "http://localhost:8080" --min-connections 2 --dev mockParseArgs.mockReturnValue({ values: { url: 'http://localhost:8080', 'min-connections': '2', dev: true }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('http://localhost:8080'); expect(result.minConnections).toBe(2); expect(result.dev).toBe(true); }); it('should parse log-mode example correctly', () => { // mcp-libsql-server --url "file:local.db" --log-mode console mockParseArgs.mockReturnValue({ values: { url: 'file:local.db', 'log-mode': 'console' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('file:local.db'); expect(result.logMode).toBe('console'); }); it('should parse turso auth token example correctly', () => { // mcp-libsql-server --url "libsql://your-db.turso.io" --auth-token "your-token" --max-connections 20 mockParseArgs.mockReturnValue({ values: { url: 'libsql://your-db.turso.io', 'auth-token': 'your-token', 'max-connections': '20' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('libsql://your-db.turso.io'); expect(result.authToken).toBe('your-token'); expect(result.maxConnections).toBe(20); }); }); describe('Authentication Token Parsing', () => { beforeEach(() => { vi.clearAllMocks(); // Clear environment variables before each test delete process.env['LIBSQL_AUTH_TOKEN']; }); describe('CLI auth-token parameter', () => { it('should parse auth-token correctly', () => { mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': 'test-cli-token-123' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe('test-cli-token-123'); }); it('should handle missing auth-token', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBeUndefined(); }); it('should handle empty auth-token', () => { mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': '' }, positionals: [] }); const result = parseCliArgs(); // Empty string is falsy, so environment variable fallback applies // Since no env var is set in test, should be undefined expect(result.authToken).toBeUndefined(); }); it('should handle long auth tokens', () => { const longToken = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDEzNDU2NzgsImlkIjoiYWJjZGVmZ2gtaWprbC1tbm9wLXFyc3QtdXZ3eHl6MTIzNDU2In0.example-long-jwt-token-for-turso-authentication'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': longToken }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe(longToken); }); }); describe('Environment variable auth token', () => { it('should use LIBSQL_AUTH_TOKEN environment variable when CLI token not provided', () => { process.env['LIBSQL_AUTH_TOKEN'] = 'test-env-token-456'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe('test-env-token-456'); }); it('should prioritize CLI auth-token over environment variable', () => { process.env['LIBSQL_AUTH_TOKEN'] = 'test-env-token-456'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': 'test-cli-token-123' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe('test-cli-token-123'); }); it('should handle empty environment variable', () => { process.env['LIBSQL_AUTH_TOKEN'] = ''; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe(''); }); it('should return undefined when neither CLI nor env token provided', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:test.db' }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBeUndefined(); }); }); describe('Authentication with other options', () => { it('should handle auth token with all other options', () => { process.env['LIBSQL_AUTH_TOKEN'] = 'env-token'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': 'cli-token', 'min-connections': '2', 'max-connections': '15', 'connection-timeout': '5000', 'query-timeout': '10000', 'log-mode': 'both', dev: true }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('libsql://my-db.turso.io'); expect(result.authToken).toBe('cli-token'); // CLI takes precedence expect(result.minConnections).toBe(2); expect(result.maxConnections).toBe(15); expect(result.connectionTimeout).toBe(5000); expect(result.queryTimeout).toBe(10000); expect(result.logMode).toBe('both'); expect(result.dev).toBe(true); }); it('should handle file database with auth token (unusual but valid)', () => { mockParseArgs.mockReturnValue({ values: { url: 'file:local.db', 'auth-token': 'unnecessary-token' }, positionals: [] }); const result = parseCliArgs(); expect(result.url).toBe('file:local.db'); expect(result.authToken).toBe('unnecessary-token'); }); }); describe('Real-world token examples', () => { it('should handle JWT-like tokens', () => { const jwtToken = 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDEzNDU2NzgsImlkIjoiYWJjZGVmZ2gtaWprbC1tbm9wLXFyc3QtdXZ3eHl6MTIzNDU2In0.signature'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': jwtToken }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe(jwtToken); }); it('should handle base64-like tokens', () => { const base64Token = 'dGVzdC10b2tlbi1mb3ItdHVyc28tYXV0aGVudGljYXRpb24tMTIzNDU2Nzg5MA=='; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': base64Token }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe(base64Token); }); it('should handle alphanumeric tokens', () => { const alphanumericToken = 'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz'; mockParseArgs.mockReturnValue({ values: { url: 'libsql://my-db.turso.io', 'auth-token': alphanumericToken }, positionals: [] }); const result = parseCliArgs(); expect(result.authToken).toBe(alphanumericToken); }); }); });

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/Xexr/mcp-libsql'

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