Skip to main content
Glama
skillUtils.test.ts6.73 kB
import { describe, expect, test } from 'vitest'; import { generateProjectName, generateProjectNameFromUrl, generateSkillDescription, toKebabCase, validateSkillName, } from '../../../src/core/skill/skillUtils.js'; describe('skillUtils', () => { describe('toKebabCase', () => { test('should convert PascalCase to kebab-case', () => { expect(toKebabCase('MyProjectName')).toBe('my-project-name'); }); test('should convert camelCase to kebab-case', () => { expect(toKebabCase('myProjectName')).toBe('my-project-name'); }); test('should convert snake_case to kebab-case', () => { expect(toKebabCase('my_project_name')).toBe('my-project-name'); }); test('should convert spaces to hyphens', () => { expect(toKebabCase('my project name')).toBe('my-project-name'); }); test('should remove invalid characters', () => { expect(toKebabCase('my@project#name!')).toBe('myprojectname'); }); test('should collapse multiple hyphens', () => { expect(toKebabCase('my--project--name')).toBe('my-project-name'); }); test('should trim leading and trailing hyphens', () => { expect(toKebabCase('-my-project-name-')).toBe('my-project-name'); }); test('should handle already kebab-case strings', () => { expect(toKebabCase('my-project-name')).toBe('my-project-name'); }); test('should handle empty string', () => { expect(toKebabCase('')).toBe(''); }); test('should handle mixed case with numbers', () => { // Numbers don't trigger hyphen insertion (only lowercase-to-uppercase transitions do) expect(toKebabCase('MyProject123Name')).toBe('my-project123name'); }); }); describe('validateSkillName', () => { test('should return kebab-case name', () => { expect(validateSkillName('MyProject')).toBe('my-project'); }); test('should truncate to 64 characters', () => { const longName = 'a'.repeat(100); expect(validateSkillName(longName).length).toBe(64); }); test('should throw error for empty name after normalization', () => { expect(() => validateSkillName('!@#$%')).toThrow('Skill name cannot be empty after normalization'); }); test('should handle valid kebab-case names', () => { expect(validateSkillName('my-valid-skill-name')).toBe('my-valid-skill-name'); }); // Security tests for path traversal prevention test('should reject path traversal attempts with forward slashes', () => { expect(() => validateSkillName('../../../etc/passwd')).toThrow('Skill name cannot contain path separators'); expect(() => validateSkillName('foo/bar')).toThrow('Skill name cannot contain path separators'); }); test('should reject path traversal attempts with backslashes', () => { expect(() => validateSkillName('..\\..\\etc\\passwd')).toThrow('Skill name cannot contain path separators'); expect(() => validateSkillName('foo\\bar')).toThrow('Skill name cannot contain path separators'); }); test('should reject null bytes', () => { expect(() => validateSkillName('foo\0bar')).toThrow('Skill name cannot contain path separators or null bytes'); }); test('should reject dot-only names', () => { expect(() => validateSkillName('.')).toThrow('Skill name cannot consist only of dots'); expect(() => validateSkillName('..')).toThrow('Skill name cannot consist only of dots'); expect(() => validateSkillName('...')).toThrow('Skill name cannot consist only of dots'); }); }); describe('generateProjectNameFromUrl', () => { test('should extract and convert GitHub URL to title case', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite')).toBe('Vite'); }); test('should handle kebab-case repo names', () => { expect(generateProjectNameFromUrl('https://github.com/yamadashy/repomix')).toBe('Repomix'); }); test('should handle snake_case repo names', () => { expect(generateProjectNameFromUrl('https://github.com/user/my_project_name')).toBe('My Project Name'); }); test('should handle shorthand format', () => { expect(generateProjectNameFromUrl('yamadashy/repomix')).toBe('Repomix'); }); test('should handle .git suffix', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite.git')).toBe('Vite'); }); test('should handle trailing slash', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite/')).toBe('Vite'); }); test('should handle multiple trailing slashes', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite///')).toBe('Vite'); }); test('should handle query string', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite?tab=readme')).toBe('Vite'); }); test('should handle fragment', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite#installation')).toBe('Vite'); }); test('should handle trailing slash with .git suffix', () => { expect(generateProjectNameFromUrl('https://github.com/vitejs/vite.git/')).toBe('Vite'); }); }); describe('generateProjectName', () => { test('should convert directory name to title case', () => { expect(generateProjectName(['my-project'])).toBe('My Project'); }); test('should handle snake_case directory names', () => { expect(generateProjectName(['my_project_name'])).toBe('My Project Name'); }); test('should use first directory when multiple provided', () => { expect(generateProjectName(['first-project', 'second-project'])).toBe('First Project'); }); test('should handle current directory', () => { // This depends on the actual directory name, so we just check it returns something const result = generateProjectName(['.']); expect(result).toBeTruthy(); }); }); describe('generateSkillDescription', () => { test('should generate description with skill and project names', () => { const description = generateSkillDescription('my-skill', 'My Project'); expect(description).toContain('My Project'); expect(description).toContain('Reference codebase'); }); test('should truncate to 1024 characters', () => { const longProjectName = 'A'.repeat(1000); const description = generateSkillDescription('my-skill', longProjectName); expect(description.length).toBeLessThanOrEqual(1024); }); test('should include usage guidance', () => { const description = generateSkillDescription('my-skill', 'My Project'); expect(description).toContain('Use this skill when'); }); }); });

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/yamadashy/repomix'

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