Skip to main content
Glama

pluggedin-mcp-proxy

slug-utils.test.ts9.15 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { generateSlug, generateUniqueSlug, isValidSlug, createSlugPrefixedToolName, parseSlugPrefixedToolName, clearSlugCache, getSlugCacheSize } from '../src/slug-utils'; describe('Slug Utilities', () => { beforeEach(() => { clearSlugCache(); }); describe('generateSlug', () => { it('should generate valid slug from normal string', () => { expect(generateSlug('Hello World')).toBe('hello-world'); expect(generateSlug('Test Server 123')).toBe('test-server-123'); }); it('should handle special characters', () => { expect(generateSlug('Test@#$%Server')).toBe('testserver'); expect(generateSlug('Hello!@#$%^&*()World')).toBe('helloworld'); }); it('should handle unicode characters', () => { expect(generateSlug('Café Société')).toBe('caf-socit'); expect(generateSlug('Über Alles')).toBe('ber-alles'); }); it('should handle multiple spaces and hyphens', () => { expect(generateSlug('Hello World')).toBe('hello-world'); expect(generateSlug('Test---Server')).toBe('test-server'); expect(generateSlug('--Leading-Trailing--')).toBe('leading-trailing'); }); it('should handle empty-like inputs', () => { expect(() => generateSlug(' ')).toThrow('Server name must be a non-empty string'); expect(generateSlug('!@#$%')).toBe('server'); }); it('should truncate long names', () => { const longName = 'a'.repeat(100); const slug = generateSlug(longName); expect(slug.length).toBeLessThanOrEqual(50); expect(slug).not.toMatch(/-$/); }); it('should throw error for invalid inputs', () => { expect(() => generateSlug(null as any)).toThrow('Server name must be a non-empty string'); expect(() => generateSlug(undefined as any)).toThrow('Server name must be a non-empty string'); expect(() => generateSlug(123 as any)).toThrow('Server name must be a non-empty string'); expect(() => generateSlug('')).toThrow('Server name must be a non-empty string'); }); it('should prevent XSS attacks', () => { // After sanitization, script tags are completely removed // '<script>alert("xss")</script>' becomes '' which defaults to 'server' expect(generateSlug('<script>alert("xss")</script>')).toBe('server'); // 'test<img src=x onerror=alert(1)>' becomes 'test' after sanitization expect(generateSlug('test<img src=x onerror=alert(1)>')).toBe('test'); // HTML attribute context: quotes and ampersands should be removed expect(generateSlug('test"quote&entity')).toBe('testquoteentity'); }); it('should use cache for repeated calls', () => { const name = 'Test Server'; const slug1 = generateSlug(name); expect(getSlugCacheSize()).toBe(1); const slug2 = generateSlug(name); expect(slug1).toBe(slug2); expect(getSlugCacheSize()).toBe(1); }); }); describe('generateUniqueSlug', () => { it('should return base slug if unique', () => { expect(generateUniqueSlug('test-server', [])).toBe('test-server'); expect(generateUniqueSlug('hello', ['world'])).toBe('hello'); }); it('should append number for conflicts', () => { expect(generateUniqueSlug('test', ['test'])).toBe('test-1'); expect(generateUniqueSlug('test', ['test', 'test-1'])).toBe('test-2'); expect(generateUniqueSlug('test', ['test', 'test-1', 'test-2'])).toBe('test-3'); }); it('should handle many conflicts', () => { const existing = Array.from({ length: 100 }, (_, i) => i === 0 ? 'test' : `test-${i}` ); // The 101st item will be test-101 since we have test and test-1 through test-100 expect(generateUniqueSlug('test', existing)).toMatch(/^test-\d+$/); }); it('should use timestamp for extreme conflicts', () => { const existing = Array.from({ length: 101 }, (_, i) => i === 0 ? 'test' : `test-${i}` ); const slug = generateUniqueSlug('test', existing); expect(slug).toMatch(/^test-\d{8}$/); }); it('should validate inputs', () => { expect(() => generateUniqueSlug('', [])).toThrow('Input cannot be empty'); expect(() => generateUniqueSlug('test', null as any)).toThrow('existingSlugs must be an array'); }); it('should perform efficiently with Set lookup', () => { const largeArray = Array.from({ length: 10000 }, (_, i) => `slug-${i}`); const start = Date.now(); const result = generateUniqueSlug('new-slug', largeArray); const duration = Date.now() - start; expect(result).toBe('new-slug'); expect(duration).toBeLessThan(10); // Should be very fast }); }); describe('isValidSlug', () => { it('should validate correct slugs', () => { expect(isValidSlug('hello-world')).toBe(true); expect(isValidSlug('test123')).toBe(true); expect(isValidSlug('a-b-c-d')).toBe(true); expect(isValidSlug('server-1')).toBe(true); }); it('should reject invalid slugs', () => { expect(isValidSlug('Hello-World')).toBe(false); // Uppercase expect(isValidSlug('hello world')).toBe(false); // Space expect(isValidSlug('hello_world')).toBe(false); // Underscore expect(isValidSlug('-hello')).toBe(false); // Leading hyphen expect(isValidSlug('hello-')).toBe(false); // Trailing hyphen expect(isValidSlug('')).toBe(false); // Empty expect(isValidSlug('a'.repeat(51))).toBe(false); // Too long }); it('should handle non-string inputs', () => { expect(isValidSlug(null)).toBe(false); expect(isValidSlug(undefined)).toBe(false); expect(isValidSlug(123)).toBe(false); expect(isValidSlug({})).toBe(false); }); }); describe('createSlugPrefixedToolName', () => { it('should create prefixed tool names', () => { expect(createSlugPrefixedToolName('my-server', 'read_file')).toBe('my-server__read_file'); expect(createSlugPrefixedToolName('test', 'tool_name')).toBe('test__tool_name'); }); it('should validate slug format', () => { expect(() => createSlugPrefixedToolName('Invalid-Slug', 'tool')).toThrow('Invalid server slug format'); expect(() => createSlugPrefixedToolName('', 'tool')).toThrow('Input cannot be empty'); }); it('should sanitize tool name', () => { // After sanitization, '<script>alert</script>' becomes '' // But empty names should throw an error expect(() => createSlugPrefixedToolName('server', '<script>alert</script>')).toThrow('Tool name becomes empty after sanitization'); }); it('should validate inputs', () => { expect(() => createSlugPrefixedToolName(null as any, 'tool')).toThrow('Input is required'); expect(() => createSlugPrefixedToolName('server', null as any)).toThrow('Input is required'); }); }); describe('parseSlugPrefixedToolName', () => { it('should parse prefixed tool names', () => { expect(parseSlugPrefixedToolName('my-server__read_file')).toEqual({ originalName: 'read_file', serverSlug: 'my-server' }); }); it('should return null for non-prefixed names', () => { expect(parseSlugPrefixedToolName('read_file')).toBe(null); expect(parseSlugPrefixedToolName('tool_name')).toBe(null); }); it('should validate slug portion', () => { expect(parseSlugPrefixedToolName('Invalid__tool')).toBe(null); // Invalid slug expect(parseSlugPrefixedToolName('__tool')).toBe(null); // Empty slug }); it('should handle edge cases', () => { expect(parseSlugPrefixedToolName('server__')).toBe(null); // Empty tool name expect(parseSlugPrefixedToolName('server__tool__with__underscores')).toEqual({ originalName: 'tool__with__underscores', serverSlug: 'server' }); }); it('should handle non-string inputs', () => { expect(parseSlugPrefixedToolName(null)).toBe(null); expect(parseSlugPrefixedToolName(undefined)).toBe(null); expect(parseSlugPrefixedToolName(123)).toBe(null); }); it('should sanitize input to prevent XSS', () => { const result = parseSlugPrefixedToolName('server__<script>alert</script>'); // After sanitization 'server__<script>alert</script>' becomes 'server__' // which has empty original name, so should return null expect(result).toBe(null); }); }); describe('Cache Management', () => { it('should manage cache size', () => { expect(getSlugCacheSize()).toBe(0); generateSlug('test1'); expect(getSlugCacheSize()).toBe(1); generateSlug('test2'); expect(getSlugCacheSize()).toBe(2); clearSlugCache(); expect(getSlugCacheSize()).toBe(0); }); it('should limit cache size', () => { // Generate more than MAX_CACHE_SIZE (1000) entries for (let i = 0; i < 1005; i++) { generateSlug(`test-${i}`); } // Cache should not exceed MAX_CACHE_SIZE expect(getSlugCacheSize()).toBeLessThanOrEqual(1000); }); }); });

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/VeriTeknik/pluggedin-mcp'

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