|
| 1 | +import { validateToolName, validateAndWarnToolName, issueToolNameWarning } from './toolNameValidation.js'; |
| 2 | + |
| 3 | +// Spy on console.warn to capture output |
| 4 | +let warnSpy: jest.SpyInstance; |
| 5 | + |
| 6 | +beforeEach(() => { |
| 7 | + warnSpy = jest.spyOn(console, 'warn').mockImplementation(); |
| 8 | +}); |
| 9 | + |
| 10 | +afterEach(() => { |
| 11 | + jest.restoreAllMocks(); |
| 12 | +}); |
| 13 | + |
| 14 | +describe('validateToolName', () => { |
| 15 | + describe('valid tool names', () => { |
| 16 | + test.each` |
| 17 | + description | toolName |
| 18 | + ${'simple alphanumeric names'} | ${'getUser'} |
| 19 | + ${'names with underscores'} | ${'get_user_profile'} |
| 20 | + ${'names with dashes'} | ${'user-profile-update'} |
| 21 | + ${'names with dots'} | ${'admin.tools.list'} |
| 22 | + ${'mixed character names'} | ${'DATA_EXPORT_v2.1'} |
| 23 | + ${'single character names'} | ${'a'} |
| 24 | + ${'128 character names'} | ${'a'.repeat(128)} |
| 25 | + `('should accept $description', ({ toolName }) => { |
| 26 | + const result = validateToolName(toolName); |
| 27 | + expect(result.isValid).toBe(true); |
| 28 | + expect(result.warnings).toHaveLength(0); |
| 29 | + }); |
| 30 | + }); |
| 31 | + |
| 32 | + describe('invalid tool names', () => { |
| 33 | + test.each` |
| 34 | + description | toolName | expectedWarning |
| 35 | + ${'empty names'} | ${''} | ${'Tool name cannot be empty'} |
| 36 | + ${'names longer than 128 characters'} | ${'a'.repeat(129)} | ${'Tool name exceeds maximum length of 128 characters (current: 129)'} |
| 37 | + ${'names with spaces'} | ${'get user profile'} | ${'Tool name contains invalid characters: " "'} |
| 38 | + ${'names with commas'} | ${'get,user,profile'} | ${'Tool name contains invalid characters: ","'} |
| 39 | + ${'names with forward slashes'} | ${'user/profile/update'} | ${'Tool name contains invalid characters: "/"'} |
| 40 | + ${'names with other special chars'} | ${'[email protected]'} | ${'Tool name contains invalid characters: "@"'} |
| 41 | + ${'names with multiple invalid chars'} | ${'user name@domain,com'} | ${'Tool name contains invalid characters: " ", "@", ","'} |
| 42 | + ${'names with unicode characters'} | ${'user-ñame'} | ${'Tool name contains invalid characters: "ñ"'} |
| 43 | + `('should reject $description', ({ toolName, expectedWarning }) => { |
| 44 | + const result = validateToolName(toolName); |
| 45 | + expect(result.isValid).toBe(false); |
| 46 | + expect(result.warnings).toContain(expectedWarning); |
| 47 | + }); |
| 48 | + }); |
| 49 | + |
| 50 | + describe('warnings for potentially problematic patterns', () => { |
| 51 | + test.each` |
| 52 | + description | toolName | expectedWarning | shouldBeValid |
| 53 | + ${'names with spaces'} | ${'get user profile'} | ${'Tool name contains spaces, which may cause parsing issues'} | ${false} |
| 54 | + ${'names with commas'} | ${'get,user,profile'} | ${'Tool name contains commas, which may cause parsing issues'} | ${false} |
| 55 | + ${'names starting with dash'} | ${'-get-user'} | ${'Tool name starts or ends with a dash, which may cause parsing issues in some contexts'} | ${true} |
| 56 | + ${'names ending with dash'} | ${'get-user-'} | ${'Tool name starts or ends with a dash, which may cause parsing issues in some contexts'} | ${true} |
| 57 | + ${'names starting with dot'} | ${'.get.user'} | ${'Tool name starts or ends with a dot, which may cause parsing issues in some contexts'} | ${true} |
| 58 | + ${'names ending with dot'} | ${'get.user.'} | ${'Tool name starts or ends with a dot, which may cause parsing issues in some contexts'} | ${true} |
| 59 | + ${'names with leading and trailing dots'} | ${'.get.user.'} | ${'Tool name starts or ends with a dot, which may cause parsing issues in some contexts'} | ${true} |
| 60 | + `('should warn about $description', ({ toolName, expectedWarning, shouldBeValid }) => { |
| 61 | + const result = validateToolName(toolName); |
| 62 | + expect(result.isValid).toBe(shouldBeValid); |
| 63 | + expect(result.warnings).toContain(expectedWarning); |
| 64 | + }); |
| 65 | + }); |
| 66 | +}); |
| 67 | + |
| 68 | +describe('issueToolNameWarning', () => { |
| 69 | + test('should output warnings to console.warn', () => { |
| 70 | + const warnings = ['Warning 1', 'Warning 2']; |
| 71 | + issueToolNameWarning('test-tool', warnings); |
| 72 | + |
| 73 | + expect(warnSpy).toHaveBeenCalledTimes(6); // Header + 2 warnings + 3 guidance lines |
| 74 | + const calls = warnSpy.mock.calls.map(call => call.join(' ')); |
| 75 | + expect(calls[0]).toContain('Tool name validation warning for "test-tool"'); |
| 76 | + expect(calls[1]).toContain('- Warning 1'); |
| 77 | + expect(calls[2]).toContain('- Warning 2'); |
| 78 | + expect(calls[3]).toContain('Tool registration will proceed, but this may cause compatibility issues.'); |
| 79 | + expect(calls[4]).toContain('Consider updating the tool name'); |
| 80 | + expect(calls[5]).toContain('See SEP: Specify Format for Tool Names'); |
| 81 | + }); |
| 82 | + |
| 83 | + test('should handle empty warnings array', () => { |
| 84 | + issueToolNameWarning('test-tool', []); |
| 85 | + expect(warnSpy).toHaveBeenCalledTimes(0); |
| 86 | + }); |
| 87 | +}); |
| 88 | + |
| 89 | +describe('validateAndWarnToolName', () => { |
| 90 | + test.each` |
| 91 | + description | toolName | expectedResult | shouldWarn |
| 92 | + ${'valid names with warnings'} | ${'-get-user-'} | ${true} | ${true} |
| 93 | + ${'completely valid names'} | ${'get-user-profile'} | ${true} | ${false} |
| 94 | + ${'invalid names with spaces'} | ${'get user profile'} | ${false} | ${true} |
| 95 | + ${'empty names'} | ${''} | ${false} | ${true} |
| 96 | + ${'names exceeding length limit'} | ${'a'.repeat(129)} | ${false} | ${true} |
| 97 | + `('should handle $description', ({ toolName, expectedResult, shouldWarn }) => { |
| 98 | + const result = validateAndWarnToolName(toolName); |
| 99 | + expect(result).toBe(expectedResult); |
| 100 | + |
| 101 | + if (shouldWarn) { |
| 102 | + expect(warnSpy).toHaveBeenCalled(); |
| 103 | + } else { |
| 104 | + expect(warnSpy).not.toHaveBeenCalled(); |
| 105 | + } |
| 106 | + }); |
| 107 | + |
| 108 | + test('should include space warning for invalid names with spaces', () => { |
| 109 | + validateAndWarnToolName('get user profile'); |
| 110 | + const warningCalls = warnSpy.mock.calls.map(call => call.join(' ')); |
| 111 | + expect(warningCalls.some(call => call.includes('Tool name contains spaces'))).toBe(true); |
| 112 | + }); |
| 113 | +}); |
| 114 | + |
| 115 | +describe('edge cases and robustness', () => { |
| 116 | + test.each` |
| 117 | + description | toolName | shouldBeValid | expectedWarning |
| 118 | + ${'names with only dots'} | ${'...'} | ${true} | ${'Tool name starts or ends with a dot, which may cause parsing issues in some contexts'} |
| 119 | + ${'names with only dashes'} | ${'---'} | ${true} | ${'Tool name starts or ends with a dash, which may cause parsing issues in some contexts'} |
| 120 | + ${'names with only forward slashes'} | ${'///'} | ${false} | ${'Tool name contains invalid characters: "/"'} |
| 121 | + ${'names with mixed valid/invalid chars'} | ${'user@name123'} | ${false} | ${'Tool name contains invalid characters: "@"'} |
| 122 | + `('should handle $description', ({ toolName, shouldBeValid, expectedWarning }) => { |
| 123 | + const result = validateToolName(toolName); |
| 124 | + expect(result.isValid).toBe(shouldBeValid); |
| 125 | + expect(result.warnings).toContain(expectedWarning); |
| 126 | + }); |
| 127 | +}); |
0 commit comments