import { describe, it, expect } from 'vitest';
import { isGradioTool, createGradioToolName } from '../../../src/server/utils/gradio-utils.js';
describe('isGradioTool', () => {
describe('should return true for valid Gradio tool names', () => {
it('should detect basic gr tools', () => {
expect(isGradioTool('gr1_tool')).toBe(true);
expect(isGradioTool('gr2_another_tool')).toBe(true);
expect(isGradioTool('gr999_test')).toBe(true);
});
it('should detect grp (private) tools', () => {
expect(isGradioTool('grp1_private_tool')).toBe(true);
expect(isGradioTool('grp2_another_private')).toBe(true);
expect(isGradioTool('grp10_test')).toBe(true);
});
it('should detect real-world Gradio tool names', () => {
expect(isGradioTool('gr1_evalstate_flux1_schnell')).toBe(true);
expect(isGradioTool('grp3_my_private_space')).toBe(true);
expect(isGradioTool('gr42_image_generator')).toBe(true);
});
it('should detect tool names generated by createGradioToolName', () => {
// Generate examples using the actual tool creation logic
const publicTool1 = createGradioToolName('flux1_schnell', 0, false);
const publicTool2 = createGradioToolName('EasyGhibli', 1, false);
const privateTool = createGradioToolName('private-model', 2, true);
expect(isGradioTool(publicTool1)).toBe(true);
expect(isGradioTool(publicTool2)).toBe(true);
expect(isGradioTool(privateTool)).toBe(true);
});
it('should handle complex tool names with multiple underscores', () => {
expect(isGradioTool('gr1_some_complex_tool_name_here')).toBe(true);
expect(isGradioTool('grp5_another_complex_private_tool')).toBe(true);
});
});
describe('should return false for non-Gradio tool names', () => {
it('should reject standard HF tools', () => {
expect(isGradioTool('hf_doc_search')).toBe(false);
expect(isGradioTool('hf_model_search')).toBe(false);
expect(isGradioTool('hf_whoami')).toBe(false);
});
it('should reject tools with missing number', () => {
expect(isGradioTool('gr_tool')).toBe(false);
expect(isGradioTool('grp_tool')).toBe(false);
});
it('should reject tools with missing underscore', () => {
expect(isGradioTool('gr1tool')).toBe(false);
expect(isGradioTool('grp2tool')).toBe(false);
});
it('should reject tools that do not start with gr/grp', () => {
expect(isGradioTool('1_gr_tool')).toBe(false);
expect(isGradioTool('some_gr1_tool')).toBe(false);
expect(isGradioTool('prefix_grp2_tool')).toBe(false);
});
it('should reject empty or invalid inputs', () => {
expect(isGradioTool('')).toBe(false);
expect(isGradioTool('gr')).toBe(false);
expect(isGradioTool('grp')).toBe(false);
expect(isGradioTool('gr1')).toBe(false);
expect(isGradioTool('grp1')).toBe(false);
});
it('should reject regular tool names', () => {
expect(isGradioTool('regular_tool')).toBe(false);
expect(isGradioTool('some_function')).toBe(false);
expect(isGradioTool('tool_name')).toBe(false);
expect(isGradioTool('api_call')).toBe(false);
});
it('should reject tools with invalid format variations', () => {
expect(isGradioTool('gra1_tool')).toBe(false);
expect(isGradioTool('grpp1_tool')).toBe(false);
expect(isGradioTool('gr_1_tool')).toBe(false);
expect(isGradioTool('grp_1_tool')).toBe(false);
});
});
describe('edge cases', () => {
it('should handle tools with numbers in the name part', () => {
expect(isGradioTool('gr1_tool2')).toBe(true);
expect(isGradioTool('grp1_v2_api')).toBe(true);
});
it('should handle tools with special characters in the name part', () => {
expect(isGradioTool('gr1_tool-name')).toBe(true);
expect(isGradioTool('grp1_tool.api')).toBe(true);
});
it('should require at least one digit', () => {
expect(isGradioTool('gr_tool')).toBe(false);
expect(isGradioTool('grp_tool')).toBe(false);
});
});
});
describe('createGradioToolName', () => {
describe('should generate correct tool names', () => {
it('should create public tool names with gr prefix', () => {
expect(createGradioToolName('flux1_schnell', 0, false)).toBe('gr1_flux1_schnell');
expect(createGradioToolName('model', 5, false)).toBe('gr6_model');
});
it('should create private tool names with grp prefix', () => {
expect(createGradioToolName('private-model', 0, true)).toBe('grp1_private_model');
expect(createGradioToolName('secret', 2, true)).toBe('grp3_secret');
});
it('should sanitize tool names correctly', () => {
// Initial sanitization still happens (spaces, dots, dashes -> underscores)
// But no underscore normalization
expect(createGradioToolName('model-name.v2', 0, false)).toBe('gr1_model_name_v2');
expect(createGradioToolName('my space test', 1, false)).toBe('gr2_my_space_test');
expect(createGradioToolName('multi--dash__test', 0, false)).toBe('gr1_multi_dash__test');
});
it('should handle edge cases', () => {
expect(createGradioToolName('', 0, false)).toBe('gr1_unknown');
expect(createGradioToolName('simple', 0, false)).toBe('gr1_simple');
expect(createGradioToolName('UPPERCASE-Model', 0, false)).toBe('gr1_uppercase_model');
});
it('should convert zero-based index to one-based tool names', () => {
expect(createGradioToolName('test', 0, false)).toBe('gr1_test');
expect(createGradioToolName('test', 1, false)).toBe('gr2_test');
expect(createGradioToolName('test', 10, true)).toBe('grp11_test');
});
it('should enforce 49-character limit with middle truncation', () => {
// Test basic truncation
const longName = 'very_long_tool_name_that_exceeds_forty_nine_characters_total_and_more';
const result1 = createGradioToolName(longName, 0, false);
expect(result1.length).toBe(49);
expect(result1).toBe('gr1_very_long_tool_name__haracters_total_and_more');
// Test with larger index (less room for name)
const result2 = createGradioToolName(longName, 999, true);
expect(result2.length).toBe(49);
expect(result2).toBe('grp1000_very_long_tool_name__cters_total_and_more');
// Test that ending is preserved
const toolWithUniqueEnd = 'common_prefix_for_tool_with_very_unique_ending_xyz123';
const result3 = createGradioToolName(toolWithUniqueEnd, 0, false);
expect(result3.length).toBe(49);
expect(result3).toBe('gr1_common_prefix_for_to_ery_unique_ending_xyz123');
expect(result3.endsWith('_xyz123')).toBe(true);
});
it('should handle edge cases with exactly 49 characters', () => {
// Create a name that will result in exactly 49 chars: gr1_ + 45 chars = 49 total
const exactName = 'tool_name_that_is_exactly_forty_five_characte'; // 45 chars
const result = createGradioToolName(exactName, 0, false);
expect(result).toBe('gr1_tool_name_that_is_exactly_forty_five_characte');
expect(result.length).toBe(49);
});
it('should preserve unique endings when truncating', () => {
// Names with same prefix but different endings - need longer names to trigger truncation at 49
const tool1 = 'image_generation_model_for_advanced_processing_version_1_final';
const tool2 = 'image_generation_model_for_advanced_processing_version_2_final';
const result1 = createGradioToolName(tool1, 0, false);
const result2 = createGradioToolName(tool2, 0, false);
// Both should be truncated but keep different endings
expect(result1).toBe('gr1_image_generation_mod_ocessing_version_1_final');
expect(result2).toBe('gr1_image_generation_mod_ocessing_version_2_final');
expect(result1).not.toBe(result2);
});
it('should not collide when one name is at limit and another exceeds by one char', () => {
// Test case where one name is exactly at the limit and another is one char over
// With index 29 (becomes "gr30"), we have 44 chars available for the name
const tool1 = 'image_utilities_mcp_update_text_image_______'; // 44 chars - exactly at limit
const tool2 = 'image_utilities_mcp_update_text_image________'; // 45 chars - exceeds by 1
const result1 = createGradioToolName(tool1, 29, false);
const result2 = createGradioToolName(tool2, 29, false);
// No normalization for tool1, tool2 is truncated
expect(result1).toBe('gr30_image_utilities_mcp_update_text_image_______');
expect(result2).toBe('gr30_image_utilities_mcp__date_text_image________');
expect(result1).not.toBe(result2);
// First keeps its length, second is truncated to exactly 49
expect(result1.length).toBe(49);
expect(result2.length).toBe(49);
});
it('should prepend tool index to truncated names when provided', () => {
// Test that toolIndex is prepended when truncation occurs
const longName = 'very_long_tool_name_that_exceeds_forty_nine_characters_total_and_more';
// Without toolIndex
const result1 = createGradioToolName(longName, 0, false);
expect(result1).toBe('gr1_very_long_tool_name__haracters_total_and_more');
expect(result1.length).toBe(49);
// With toolIndex 0
const result2 = createGradioToolName(longName, 0, false, 0);
expect(result2).toBe('gr1_0_very_long_tool_name__racters_total_and_more');
expect(result2.length).toBe(49);
// With toolIndex 1
const result3 = createGradioToolName(longName, 0, false, 1);
expect(result3).toBe('gr1_1_very_long_tool_name__racters_total_and_more');
expect(result3.length).toBe(49);
// Different tools should have different names
expect(result2).not.toBe(result3);
});
it('should handle similar tool names with different indices correctly', () => {
// Test multiple similar tool names that would collide without toolIndex
const baseName = 'image_utilities_mcp_update_text_image________';
const tools = [0, 1, 2, 3].map(toolIdx =>
createGradioToolName(baseName, 29, false, toolIdx)
);
// All should be unique
const uniqueTools = new Set(tools);
expect(uniqueTools.size).toBe(4);
// Each should start with its tool index after the prefix
expect(tools[0]).toBe('gr30_0_image_utilities_mcp__te_text_image________');
expect(tools[1]).toBe('gr30_1_image_utilities_mcp__te_text_image________');
expect(tools[2]).toBe('gr30_2_image_utilities_mcp__te_text_image________');
expect(tools[3]).toBe('gr30_3_image_utilities_mcp__te_text_image________');
});
it('should not add toolIndex when not truncating', () => {
// Short names should not get toolIndex appended
const shortName = 'simple_tool';
const result1 = createGradioToolName(shortName, 0, false, 0);
const result2 = createGradioToolName(shortName, 0, false, 1);
// Both should be the same since no truncation happened
expect(result1).toBe('gr1_simple_tool');
expect(result2).toBe('gr1_simple_tool');
});
});
describe('integration with tool names from endpoints', () => {
it('should generate correct names for known tool names', () => {
// These should match the examples of tool names from Gradio endpoints
expect(createGradioToolName('flux1_schnell', 0, false)).toBe('gr1_flux1_schnell');
expect(createGradioToolName('EasyGhibli', 1, false)).toBe('gr2_easyghibli');
});
});
});