mod inc;
use api_huggingface::
{
components::input::InferenceParameters,
error::HuggingFaceError,
};
#[ test ]
fn test_inference_parameters_temperature_validation()
{
let valid_params = InferenceParameters::new()
.with_temperature( 0.1 )
.validate();
assert!( valid_params.is_ok() );
let valid_params = InferenceParameters::new()
.with_temperature( 1.0 )
.validate();
assert!( valid_params.is_ok() );
let valid_params = InferenceParameters::new()
.with_temperature( 2.0 )
.validate();
assert!( valid_params.is_ok() );
let invalid_params = InferenceParameters::new()
.with_temperature( -0.1 )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.to_lowercase().contains( "temperature" ) );
assert!( msg.contains( "0.0" ) );
assert!( msg.contains( "2.0" ) );
}
else
{
panic!( "Expected validation error for negative temperature" );
}
let invalid_params = InferenceParameters::new()
.with_temperature( 2.1 )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.to_lowercase().contains( "temperature" ) );
}
else
{
panic!( "Expected validation error for high temperature" );
}
}
#[ test ]
fn test_inference_parameters_max_tokens_validation()
{
let valid_params = InferenceParameters::new()
.with_max_new_tokens( 1 )
.validate();
assert!( valid_params.is_ok() );
let valid_params = InferenceParameters::new()
.with_max_new_tokens( 4096 )
.validate();
assert!( valid_params.is_ok() );
let invalid_params = InferenceParameters::new()
.with_max_new_tokens( 0 )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.contains( "max_new_tokens" ) );
assert!( msg.contains( "greater than 0" ) );
}
else
{
panic!( "Expected validation error for zero max_new_tokens" );
}
let invalid_params = InferenceParameters::new()
.with_max_new_tokens( 10000 )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.contains( "max_new_tokens" ) );
assert!( msg.contains( "8192" ) );
}
else
{
panic!( "Expected validation error for excessive max_new_tokens" );
}
}
#[ test ]
fn test_inference_parameters_top_p_validation()
{
let valid_params = InferenceParameters::new()
.with_top_p( 0.0 )
.validate();
assert!( valid_params.is_ok() );
let valid_params = InferenceParameters::new()
.with_top_p( 1.0 )
.validate();
assert!( valid_params.is_ok() );
let invalid_params = InferenceParameters::new()
.with_top_p( -0.1 )
.validate();
assert!( invalid_params.is_err() );
let invalid_params = InferenceParameters::new()
.with_top_p( 1.1 )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.to_lowercase().contains( "top_p" ) );
assert!( msg.contains( "0.0" ) );
assert!( msg.contains( "1.0" ) );
}
else
{
panic!( "Expected validation error for invalid top_p" );
}
}
#[ test ]
fn test_input_text_validation()
{
use api_huggingface::validation::validate_input_text;
let valid_text = "Hello, world!";
assert!( validate_input_text( valid_text ).is_ok() );
let medium_text = "a".repeat( 1000 );
assert!( validate_input_text( &medium_text ).is_ok() );
let empty_text = "";
let result = validate_input_text( empty_text );
assert!( result.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected validation error for empty input" );
}
let long_text = "a".repeat( 100_000 );
let result = validate_input_text( &long_text );
assert!( result.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "too long" ) );
assert!( msg.contains( "50000" ) );
}
else
{
panic!( "Expected validation error for excessively long input" );
}
let unicode_text = "Hello 🌍! 你好世界 مرحبا بالعالم";
assert!( validate_input_text( unicode_text ).is_ok() );
}
#[ test ]
fn test_model_identifier_validation()
{
use api_huggingface::validation::validate_model_identifier;
assert!( validate_model_identifier( "gpt2" ).is_ok() );
assert!( validate_model_identifier( "meta-llama/Llama-2-7b-hf" ).is_ok() );
assert!( validate_model_identifier( "microsoft/DialoGPT-medium" ).is_ok() );
assert!( validate_model_identifier( "sentence-transformers/all-MiniLM-L6-v2" ).is_ok() );
let long_model = "a".repeat( 300 );
let invalid_models = vec![
"", " ", "model with spaces", "model\nwith\nnewlines", "/leading-slash", "trailing-slash/", "double//slash", &long_model, ];
for invalid_model in invalid_models
{
let result = validate_model_identifier( invalid_model );
assert!( result.is_err(), "Model '{invalid_model}' should be invalid" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "model" ) );
}
else
{
panic!( "Expected validation error for model '{invalid_model}'" );
}
}
}
#[ test ]
fn test_batch_input_validation()
{
use api_huggingface::validation::validate_batch_inputs;
let valid_batch = vec![ "Hello".to_string(), "World".to_string() ];
assert!( validate_batch_inputs( &valid_batch ).is_ok() );
let empty_batch : Vec< String > = vec![];
let result = validate_batch_inputs( &empty_batch );
assert!( result.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected validation error for empty batch" );
}
let large_batch : Vec< String > = ( 0..1001 ).map( | i | format!( "input_{i}" ) ).collect();
let result = validate_batch_inputs( &large_batch );
assert!( result.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "too many" ) );
assert!( msg.contains( "1000" ) );
}
else
{
panic!( "Expected validation error for excessive batch size" );
}
let invalid_batch = vec![ "Valid input".to_string(), String::new() ];
let result = validate_batch_inputs( &invalid_batch );
assert!( result.is_err() );
}
#[ test ]
fn test_stop_sequences_validation()
{
let valid_params = InferenceParameters::new()
.with_stop_sequences( vec![ "\n".to_string(), "END".to_string() ] )
.validate();
assert!( valid_params.is_ok() );
let many_stops : Vec< String > = ( 0..20 ).map( | i | format!( "stop_{i}" ) ).collect();
let invalid_params = InferenceParameters::new()
.with_stop_sequences( many_stops )
.validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.contains( "stop" ) );
assert!( msg.contains( "10" ) );
}
else
{
panic!( "Expected validation error for too many stop sequences" );
}
let empty_stops = vec![ String::new() ];
let invalid_params = InferenceParameters::new()
.with_stop_sequences( empty_stops )
.validate();
assert!( invalid_params.is_err() );
}
#[ test ]
fn test_multiple_validation_errors()
{
let invalid_params = InferenceParameters::new()
.with_temperature( -1.0 ) .with_max_new_tokens( 0 ) .with_top_p( 2.0 ) .validate();
assert!( invalid_params.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = invalid_params
{
assert!( msg.to_lowercase().contains( "temperature" ) );
assert!( msg.to_lowercase().contains( "max_new_tokens" ) );
assert!( msg.to_lowercase().contains( "top_p" ) );
}
else
{
panic!( "Expected validation error with multiple issues" );
}
}
#[ test ]
fn test_default_parameters_valid()
{
let default_params = InferenceParameters::default();
assert!( default_params.validate().is_ok() );
let new_params = InferenceParameters::new();
assert!( new_params.validate().is_ok() );
}
#[ test ]
fn test_validate_input_text_control_chars()
{
use api_huggingface::validation::validate_input_text;
let ctrl_nul = "\x00";
assert!( validate_input_text( ctrl_nul ).is_err(), "NUL byte must be rejected" );
let ctrl_bel = "\x07";
assert!( validate_input_text( ctrl_bel ).is_err(), "BEL must be rejected" );
let ctrl_esc = "\x1B";
assert!( validate_input_text( ctrl_esc ).is_err(), "ESC must be rejected" );
let embedded = "hello\x01world";
assert!( validate_input_text( embedded ).is_err(), "Embedded ASCII control char must be rejected" );
assert!( validate_input_text( "line\nbreak" ).is_ok(), "\\n must be accepted" );
assert!( validate_input_text( "tab\there" ).is_ok(), "\\t must be accepted" );
assert!( validate_input_text( "cr\rchar" ).is_ok(), "\\r must be accepted" );
assert!( validate_input_text( "hello world" ).is_ok() );
assert!( validate_input_text( "unicode: 你好 🌍" ).is_ok() );
}
#[ test ]
fn test_validate_input_text_boundary_length()
{
use api_huggingface::validation::{ validate_input_text, MAX_INPUT_LENGTH };
let at_limit = "a".repeat( MAX_INPUT_LENGTH );
assert!(
validate_input_text( &at_limit ).is_ok(),
"Text at exactly MAX_INPUT_LENGTH must be valid"
);
let over_limit = "a".repeat( MAX_INPUT_LENGTH + 1 );
let result = validate_input_text( &over_limit );
assert!( result.is_err(), "Text exceeding MAX_INPUT_LENGTH must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "too long" ) );
}
else
{
panic!( "Expected Validation error for text over length limit" );
}
}
#[ test ]
fn test_validate_model_identifier_boundary_and_tab()
{
use api_huggingface::validation::{ validate_model_identifier, MAX_MODEL_ID_LENGTH };
let at_limit = "a".repeat( MAX_MODEL_ID_LENGTH );
assert!(
validate_model_identifier( &at_limit ).is_ok(),
"Model ID at exactly MAX_MODEL_ID_LENGTH must be valid"
);
let over_limit = "a".repeat( MAX_MODEL_ID_LENGTH + 1 );
assert!(
validate_model_identifier( &over_limit ).is_err(),
"Model ID exceeding MAX_MODEL_ID_LENGTH must be rejected"
);
let with_tab = "org/model\tname";
assert!(
validate_model_identifier( with_tab ).is_err(),
"Tab in model ID must be rejected"
);
assert!(
validate_model_identifier( "moonshotai/Kimi-K2-Instruct-0905:groq" ).is_ok(),
"Colon in model ID must be accepted"
);
assert!( validate_model_identifier( " org/model" ).is_err(), "Leading space must be rejected" );
assert!( validate_model_identifier( "org/model " ).is_err(), "Trailing space must be rejected" );
}
#[ test ]
fn test_validate_batch_inputs_boundary()
{
use api_huggingface::validation::{ validate_batch_inputs, MAX_BATCH_SIZE };
let at_limit : Vec< String > = ( 0..MAX_BATCH_SIZE ).map( | i | format!( "text_{i}" ) ).collect();
assert!(
validate_batch_inputs( &at_limit ).is_ok(),
"Batch at exactly MAX_BATCH_SIZE must be valid"
);
let over_limit : Vec< String > = ( 0..=MAX_BATCH_SIZE ).map( | i | format!( "text_{i}" ) ).collect();
let result = validate_batch_inputs( &over_limit );
assert!( result.is_err(), "Batch exceeding MAX_BATCH_SIZE must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "too many" ) );
}
else
{
panic!( "Expected Validation error for oversized batch" );
}
}
#[ test ]
fn test_validate_max_new_tokens_boundary()
{
use api_huggingface::validation::{ validate_max_new_tokens, MAX_NEW_TOKENS };
assert!(
validate_max_new_tokens( MAX_NEW_TOKENS ).is_ok(),
"max_new_tokens at exactly MAX_NEW_TOKENS must be valid"
);
let result = validate_max_new_tokens( MAX_NEW_TOKENS + 1 );
assert!( result.is_err(), "max_new_tokens exceeding MAX_NEW_TOKENS must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "max_new_tokens" ) );
}
else
{
panic!( "Expected Validation error for excessive max_new_tokens" );
}
}
#[ test ]
fn test_validate_temperature_special_floats()
{
use api_huggingface::validation::validate_temperature;
assert!( validate_temperature( f32::NAN ).is_err(), "NaN temperature must be rejected" );
assert!( validate_temperature( f32::INFINITY ).is_err(), "INFINITY temperature must be rejected" );
assert!( validate_temperature( f32::NEG_INFINITY ).is_err(), "NEG_INFINITY temperature must be rejected" );
assert!( validate_temperature( 0.0 ).is_ok(), "temperature 0.0 must be valid" );
assert!( validate_temperature( 2.0 ).is_ok(), "temperature 2.0 must be valid" );
}
#[ test ]
fn test_validate_top_p_special_floats()
{
use api_huggingface::validation::validate_top_p;
assert!( validate_top_p( f32::NAN ).is_err(), "NaN top_p must be rejected" );
assert!( validate_top_p( f32::INFINITY ).is_err(), "INFINITY top_p must be rejected" );
assert!( validate_top_p( f32::NEG_INFINITY ).is_err(), "NEG_INFINITY top_p must be rejected" );
assert!( validate_top_p( 0.0 ).is_ok(), "top_p 0.0 must be valid" );
assert!( validate_top_p( 1.0 ).is_ok(), "top_p 1.0 must be valid" );
}
#[ test ]
fn test_validate_top_k()
{
use api_huggingface::validation::validate_top_k;
let result = validate_top_k( 0 );
assert!( result.is_err(), "top_k=0 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "top_k" ) );
assert!( msg.contains( "greater than 0" ) );
}
else
{
panic!( "Expected Validation error for top_k=0" );
}
assert!( validate_top_k( 1 ).is_ok(), "top_k=1 (minimum) must be valid" );
assert!( validate_top_k( 50 ).is_ok(), "top_k=50 must be valid" );
assert!( validate_top_k( 1000 ).is_ok(), "top_k=1000 (boundary) must be valid" );
let result = validate_top_k( 1001 );
assert!( result.is_err(), "top_k=1001 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "top_k" ) );
assert!( msg.contains( "1000" ) );
}
else
{
panic!( "Expected Validation error for top_k over 1000" );
}
}
#[ test ]
fn test_validate_frequency_penalty()
{
use api_huggingface::validation::validate_frequency_penalty;
assert!( validate_frequency_penalty( -2.0 ).is_ok(), "frequency_penalty=-2.0 (boundary) must be valid" );
assert!( validate_frequency_penalty( 0.0 ).is_ok(), "frequency_penalty=0.0 must be valid" );
assert!( validate_frequency_penalty( 2.0 ).is_ok(), "frequency_penalty=2.0 (boundary) must be valid" );
let result = validate_frequency_penalty( -2.1 );
assert!( result.is_err(), "frequency_penalty=-2.1 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "frequency_penalty" ) );
}
else
{
panic!( "Expected Validation error for frequency_penalty below -2.0" );
}
let result = validate_frequency_penalty( 2.1 );
assert!( result.is_err(), "frequency_penalty=2.1 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "frequency_penalty" ) );
}
else
{
panic!( "Expected Validation error for frequency_penalty above 2.0" );
}
assert!( validate_frequency_penalty( f32::NAN ).is_err(), "NaN frequency_penalty must be rejected" );
assert!( validate_frequency_penalty( f32::INFINITY ).is_err(), "INFINITY frequency_penalty must be rejected" );
}
#[ test ]
fn test_validate_presence_penalty()
{
use api_huggingface::validation::validate_presence_penalty;
assert!( validate_presence_penalty( -2.0 ).is_ok(), "presence_penalty=-2.0 (boundary) must be valid" );
assert!( validate_presence_penalty( 0.0 ).is_ok(), "presence_penalty=0.0 must be valid" );
assert!( validate_presence_penalty( 2.0 ).is_ok(), "presence_penalty=2.0 (boundary) must be valid" );
assert!( validate_presence_penalty( -2.1 ).is_err(), "presence_penalty=-2.1 must be rejected" );
assert!( validate_presence_penalty( 2.1 ).is_err(), "presence_penalty=2.1 must be rejected" );
assert!( validate_presence_penalty( f32::NAN ).is_err(), "NaN presence_penalty must be rejected" );
assert!( validate_presence_penalty( f32::NEG_INFINITY ).is_err(), "NEG_INFINITY presence_penalty must be rejected" );
}
#[ test ]
fn test_validate_repetition_penalty()
{
use api_huggingface::validation::validate_repetition_penalty;
let result = validate_repetition_penalty( 0.0 );
assert!( result.is_err(), "repetition_penalty=0.0 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "repetition_penalty" ) );
assert!( msg.to_lowercase().contains( "positive" ) );
}
else
{
panic!( "Expected Validation error for non-positive repetition_penalty" );
}
assert!( validate_repetition_penalty( -0.1 ).is_err(), "Negative repetition_penalty must be rejected" );
assert!( validate_repetition_penalty( 0.1 ).is_ok(), "repetition_penalty=0.1 must be valid" );
assert!( validate_repetition_penalty( 1.0 ).is_ok(), "repetition_penalty=1.0 must be valid" );
assert!( validate_repetition_penalty( 10.0 ).is_ok(), "repetition_penalty=10.0 (boundary) must be valid" );
let result = validate_repetition_penalty( 10.1 );
assert!( result.is_err(), "repetition_penalty=10.1 must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "repetition_penalty" ) );
}
else
{
panic!( "Expected Validation error for excessive repetition_penalty" );
}
assert!( validate_repetition_penalty( f32::NAN ).is_err(), "NaN repetition_penalty must be rejected" );
assert!( validate_repetition_penalty( f32::INFINITY ).is_err(), "INFINITY repetition_penalty must be rejected" );
}
#[ test ]
fn test_validate_message_role()
{
use api_huggingface::validation::validate_message_role;
assert!( validate_message_role( "system" ).is_ok() );
assert!( validate_message_role( "user" ).is_ok() );
assert!( validate_message_role( "assistant" ).is_ok() );
assert!( validate_message_role( "tool" ).is_ok() );
assert!( validate_message_role( "function" ).is_ok() );
let invalid_roles = [ "bot", "ai", "human", "System", "USER", "", " user" ];
for role in invalid_roles
{
let result = validate_message_role( role );
assert!(
result.is_err(),
"Role '{role}' should be invalid"
);
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "role" ) || msg.to_lowercase().contains( "invalid" ) );
}
else
{
panic!( "Expected Validation error for role '{role}'" );
}
}
}
#[ test ]
fn test_validate_message_content()
{
use api_huggingface::validation::{ validate_message_content, MAX_INPUT_LENGTH };
let result = validate_message_content( "" );
assert!( result.is_err(), "Empty message content must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected Validation error for empty message content" );
}
assert!( validate_message_content( "Hello, how are you?" ).is_ok() );
assert!( validate_message_content( "Multi\nline\ncontent" ).is_ok() );
let at_limit = "a".repeat( MAX_INPUT_LENGTH );
assert!( validate_message_content( &at_limit ).is_ok(), "Message content at limit must be valid" );
let over_limit = "a".repeat( MAX_INPUT_LENGTH + 1 );
let result = validate_message_content( &over_limit );
assert!( result.is_err(), "Message content over limit must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "too long" ) );
}
else
{
panic!( "Expected Validation error for excessive message content" );
}
}
#[ test ]
fn test_validate_tool_choice()
{
use api_huggingface::validation::validate_tool_choice;
assert!( validate_tool_choice( "auto" ).is_ok() );
assert!( validate_tool_choice( "none" ).is_ok() );
assert!( validate_tool_choice( "required" ).is_ok() );
assert!( validate_tool_choice( "my_function" ).is_ok() );
assert!( validate_tool_choice( "get_weather" ).is_ok() );
let result = validate_tool_choice( "" );
assert!( result.is_err(), "Empty tool_choice must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "tool_choice" ) );
assert!( msg.to_lowercase().contains( "empty" ) );
}
else
{
panic!( "Expected Validation error for empty tool_choice" );
}
}
#[ test ]
fn test_validate_image_size()
{
use api_huggingface::validation::{ validate_image_size, MAX_IMAGE_SIZE_BYTES };
let result = validate_image_size( &[] );
assert!( result.is_err(), "Empty image data must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected Validation error for empty image data" );
}
let small_data = vec![ 0u8; 1024 ];
assert!( validate_image_size( &small_data ).is_ok(), "1 KB image must be valid" );
let at_limit = vec![ 0u8; MAX_IMAGE_SIZE_BYTES ];
assert!(
validate_image_size( &at_limit ).is_ok(),
"Image exactly at MAX_IMAGE_SIZE_BYTES must be valid"
);
let over_limit = vec![ 0u8; MAX_IMAGE_SIZE_BYTES + 1 ];
let result = validate_image_size( &over_limit );
assert!( result.is_err(), "Image over MAX_IMAGE_SIZE_BYTES must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "image" ) );
assert!( msg.to_lowercase().contains( "large" ) || msg.to_lowercase().contains( "too large" ) );
}
else
{
panic!( "Expected Validation error for image over size limit" );
}
}
#[ test ]
fn test_validate_audio_size()
{
use api_huggingface::validation::{ validate_audio_size, MAX_AUDIO_SIZE_BYTES };
let result = validate_audio_size( &[] );
assert!( result.is_err(), "Empty audio data must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected Validation error for empty audio data" );
}
let small_data = vec![ 0u8; 1024 ];
assert!( validate_audio_size( &small_data ).is_ok(), "1 KB audio must be valid" );
let at_limit = vec![ 0u8; MAX_AUDIO_SIZE_BYTES ];
assert!(
validate_audio_size( &at_limit ).is_ok(),
"Audio exactly at MAX_AUDIO_SIZE_BYTES must be valid"
);
let over_limit = vec![ 0u8; MAX_AUDIO_SIZE_BYTES + 1 ];
assert!( validate_audio_size( &over_limit ).is_err(), "Audio over MAX_AUDIO_SIZE_BYTES must be rejected" );
}
#[ test ]
fn test_validate_url()
{
use api_huggingface::validation::validate_url;
assert!( validate_url( "http://example.com" ).is_ok() );
assert!( validate_url( "https://api.huggingface.co/v1/models" ).is_ok() );
assert!( validate_url( "https://router.huggingface.co/v1/chat/completions" ).is_ok() );
let result = validate_url( "" );
assert!( result.is_err(), "Empty URL must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "empty" ) );
}
else
{
panic!( "Expected Validation error for empty URL" );
}
let result = validate_url( "ftp://example.com" );
assert!( result.is_err(), "FTP URL must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "http://" ) || msg.contains( "https://" ) );
}
else
{
panic!( "Expected Validation error for non-http(s) URL" );
}
assert!( validate_url( "example.com" ).is_err(), "URL without protocol must be rejected" );
let long_but_valid = format!( "https://example.com/{}", "a".repeat( 2048 - "https://example.com/".len() ) );
assert_eq!( long_but_valid.len(), 2048 );
assert!( validate_url( &long_but_valid ).is_ok(), "URL at exactly 2048 chars must be valid" );
let too_long = format!( "https://example.com/{}", "a".repeat( 2048 - "https://example.com/".len() + 1 ) );
assert!( too_long.len() > 2048 );
let result = validate_url( &too_long );
assert!( result.is_err(), "URL exceeding 2048 chars must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "too long" ) || msg.contains( "2048" ) );
}
else
{
panic!( "Expected Validation error for URL over 2048 chars" );
}
}
#[ test ]
fn test_validate_url_protocol_only_rejected()
{
use api_huggingface::validation::validate_url;
let result = validate_url( "http://" );
assert!( result.is_err(), "http:// with no hostname must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.to_lowercase().contains( "host" ) || msg.to_lowercase().contains( "empty" ),
"Error should mention missing host, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for http:// with no hostname" );
}
let result = validate_url( "https://" );
assert!( result.is_err(), "https:// with no hostname must be rejected" );
assert!( validate_url( "http://a" ).is_ok(), "http://a must be valid (minimal http URL)" );
assert!( validate_url( "https://a" ).is_ok(), "https://a must be valid (minimal https URL)" );
}
#[ test ]
fn test_validate_stop_sequences_boundary()
{
use api_huggingface::validation::{ validate_stop_sequences, MAX_STOP_SEQUENCES };
let at_limit : Vec< String > = ( 0..MAX_STOP_SEQUENCES ).map( | i | format!( "stop{i}" ) ).collect();
assert!(
validate_stop_sequences( &at_limit ).is_ok(),
"Exactly MAX_STOP_SEQUENCES sequences must be valid"
);
let over_limit : Vec< String > = ( 0..=MAX_STOP_SEQUENCES ).map( | i | format!( "stop{i}" ) ).collect();
let result = validate_stop_sequences( &over_limit );
assert!( result.is_err(), "More than MAX_STOP_SEQUENCES must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.contains( "stop" ) || msg.contains( "sequences" ) );
}
else
{
panic!( "Expected Validation error for too many stop sequences" );
}
let at_char_limit = vec![ "a".repeat( 100 ) ];
assert!(
validate_stop_sequences( &at_char_limit ).is_ok(),
"Stop sequence at exactly 100 chars must be valid"
);
let over_char_limit = vec![ "a".repeat( 101 ) ];
let result = validate_stop_sequences( &over_char_limit );
assert!( result.is_err(), "Stop sequence over 100 chars must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!( msg.to_lowercase().contains( "stop" ) || msg.to_lowercase().contains( "long" ) );
}
else
{
panic!( "Expected Validation error for stop sequence over 100 chars" );
}
}
#[ test ]
fn test_inference_provider_api()
{
use api_huggingface::providers::InferenceProvider;
assert_eq!( InferenceProvider::OpenAI.as_str(), "openai" );
assert_eq!( InferenceProvider::Cohere.as_str(), "cohere" );
assert_eq!( InferenceProvider::Together.as_str(), "together" );
assert_eq!( InferenceProvider::Groq.as_str(), "groq" );
assert_eq!( InferenceProvider::HfInference.as_str(), "hf-inference" );
for provider in [ InferenceProvider::OpenAI, InferenceProvider::Cohere, InferenceProvider::Together, InferenceProvider::Groq, InferenceProvider::HfInference ]
{
let models = provider.available_models();
assert!( !models.is_empty(), "{provider:?} must have at least one available model" );
for model in models
{
assert!( !model.is_empty(), "Model identifier must not be empty" );
assert!( model.contains( '/' ), "HuggingFace model ID must contain org/name separator" );
}
}
let known = "meta-llama/Llama-2-7b-chat-hf";
let provider = InferenceProvider::for_model( known );
assert!( provider.is_some(), "for_model({known}) must return Some provider" );
let unknown = "nonexistent/model-that-does-not-exist";
let result = InferenceProvider::for_model( unknown );
assert!( result.is_none(), "for_model({unknown}) must return None" );
use api_huggingface::environment::HuggingFaceEnvironmentImpl;
let default_model = api_huggingface::providers::Providers::< HuggingFaceEnvironmentImpl >::default_pro_model();
assert!( !default_model.is_empty() );
let fallback = api_huggingface::providers::Providers::< HuggingFaceEnvironmentImpl >::fallback_model();
assert!( !fallback.is_empty() );
}
#[ test ]
fn test_validate_temperature_nan_gives_valid_number_error()
{
use api_huggingface::{ validation::validate_temperature, error::HuggingFaceError };
let result = validate_temperature( f32::NAN );
assert!( result.is_err(), "NaN temperature must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"NaN should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for NaN temperature" );
}
let result = validate_temperature( f32::INFINITY );
assert!( result.is_err(), "INFINITY temperature must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"INFINITY should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for INFINITY temperature" );
}
}
#[ test ]
fn test_validate_top_p_nan_gives_valid_number_error()
{
use api_huggingface::{ validation::validate_top_p, error::HuggingFaceError };
let result = validate_top_p( f32::NAN );
assert!( result.is_err(), "NaN top_p must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"NaN top_p should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for NaN top_p" );
}
}
#[ test ]
fn test_validate_frequency_penalty_nan_gives_valid_number_error()
{
use api_huggingface::{ validation::validate_frequency_penalty, error::HuggingFaceError };
let result = validate_frequency_penalty( f32::NAN );
assert!( result.is_err(), "NaN frequency_penalty must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"NaN frequency_penalty should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for NaN frequency_penalty" );
}
}
#[ test ]
fn test_validate_presence_penalty_nan_gives_valid_number_error()
{
use api_huggingface::{ validation::validate_presence_penalty, error::HuggingFaceError };
let result = validate_presence_penalty( f32::NAN );
assert!( result.is_err(), "NaN presence_penalty must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"NaN presence_penalty should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for NaN presence_penalty" );
}
}
#[ test ]
fn test_validate_repetition_penalty_infinity_gives_valid_number_error()
{
use api_huggingface::{ validation::validate_repetition_penalty, error::HuggingFaceError };
let result = validate_repetition_penalty( f32::INFINITY );
assert!( result.is_err(), "INFINITY repetition_penalty must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"INFINITY repetition_penalty should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for INFINITY repetition_penalty" );
}
let result = validate_repetition_penalty( f32::NEG_INFINITY );
assert!( result.is_err(), "NEG_INFINITY repetition_penalty must be rejected" );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
assert!(
msg.contains( "valid number" ),
"NEG_INFINITY repetition_penalty should produce 'valid number' error, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for NEG_INFINITY repetition_penalty" );
}
}
#[ test ]
fn test_validate_input_text_multibyte_character_boundary()
{
use api_huggingface::validation::{ validate_input_text, MAX_INPUT_LENGTH };
let two_byte_char = 'é'; let half_limit_multibyte = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH / 2 );
assert_eq!( half_limit_multibyte.len(), MAX_INPUT_LENGTH, "sanity: byte length is at the limit" );
assert_eq!(
half_limit_multibyte.chars().count(),
MAX_INPUT_LENGTH / 2,
"sanity: char count is half the limit"
);
assert!(
validate_input_text( &half_limit_multibyte ).is_ok(),
"String with {} 2-byte chars (byte len={}) must be valid; MAX chars={}",
MAX_INPUT_LENGTH / 2,
half_limit_multibyte.len(),
MAX_INPUT_LENGTH,
);
let at_char_limit_multibyte = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH );
assert_eq!( at_char_limit_multibyte.chars().count(), MAX_INPUT_LENGTH );
assert!(
validate_input_text( &at_char_limit_multibyte ).is_ok(),
"String at exactly MAX_INPUT_LENGTH 2-byte chars must be valid"
);
let over_char_limit = "a".repeat( MAX_INPUT_LENGTH + 1 );
assert!(
validate_input_text( &over_char_limit ).is_err(),
"String with MAX_INPUT_LENGTH+1 chars must be rejected"
);
let two_byte_over = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH + 1 );
let result = validate_input_text( &two_byte_over );
assert!( result.is_err() );
if let Err( api_huggingface::error::HuggingFaceError::Validation( msg ) ) = result
{
let char_count = ( MAX_INPUT_LENGTH + 1 ).to_string();
assert!(
msg.contains( &char_count ),
"Error message must report char count {char_count}, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for text over char limit" );
}
}
#[ test ]
fn test_validate_message_content_multibyte_character_boundary()
{
use api_huggingface::validation::{ validate_message_content, MAX_INPUT_LENGTH };
let two_byte_char = 'é';
let half_limit = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH / 2 );
assert_eq!( half_limit.len(), MAX_INPUT_LENGTH, "sanity: byte length equals the limit" );
assert_eq!( half_limit.chars().count(), MAX_INPUT_LENGTH / 2, "sanity: char count is half" );
assert!(
validate_message_content( &half_limit ).is_ok(),
"Content with {} 2-byte chars (byte_len={}) must be valid; MAX chars={}",
MAX_INPUT_LENGTH / 2,
half_limit.len(),
MAX_INPUT_LENGTH,
);
let at_char_limit = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH );
assert_eq!( at_char_limit.chars().count(), MAX_INPUT_LENGTH );
assert!(
validate_message_content( &at_char_limit ).is_ok(),
"Content at exactly MAX_INPUT_LENGTH 2-byte chars must be valid"
);
let over_char_limit = "a".repeat( MAX_INPUT_LENGTH + 1 );
assert!(
validate_message_content( &over_char_limit ).is_err(),
"Content with MAX_INPUT_LENGTH+1 chars must be rejected"
);
let two_byte_over = two_byte_char.to_string().repeat( MAX_INPUT_LENGTH + 1 );
let result = validate_message_content( &two_byte_over );
assert!( result.is_err() );
if let Err( HuggingFaceError::Validation( msg ) ) = result
{
let char_count = ( MAX_INPUT_LENGTH + 1 ).to_string();
assert!(
msg.contains( &char_count ),
"Error message must report char count {char_count}, got: {msg}"
);
}
else
{
panic!( "Expected Validation error for content over char limit" );
}
}
#[ cfg( feature = "integration" ) ]
use api_huggingface::
{
Client,
environment::HuggingFaceEnvironmentImpl,
secret::Secret,
validation::{ validate_model_identifier, validate_batch_inputs },
};
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_validation_with_real_api_calls()
{
let api_key_string = crate::inc::get_api_key_for_integration();
let api_key = Secret::new( api_key_string );
let env = HuggingFaceEnvironmentImpl::build( api_key, None )
.expect( "Environment build should succeed" );
let client = Client::build( env )
.expect( "Client build should succeed" );
let invalid_params = InferenceParameters::new()
.with_temperature( -1.0 )
.with_max_new_tokens( 0 );
let validation_result = invalid_params.validate();
assert!( validation_result.is_err(), "Invalid parameters should fail validation" );
let api_result = client.inference()
.create_with_parameters( "test", "microsoft/DialoGPT-medium", invalid_params )
.await;
assert!( api_result.is_err(), "Invalid parameters should cause API call to fail" );
}
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_model_validation_with_real_api()
{
let api_key_string = crate::inc::get_api_key_for_integration();
let api_key = Secret::new( api_key_string );
let env = HuggingFaceEnvironmentImpl::build( api_key, None )
.expect( "Environment build should succeed" );
let client = Client::build( env )
.expect( "Client build should succeed" );
assert!( validate_model_identifier( "" ).is_err(), "Empty model should be invalid" );
assert!( validate_model_identifier( "invalid model name with spaces" ).is_err(), "Spaces should be invalid" );
assert!( validate_model_identifier( "valid/model-name" ).is_ok(), "Valid format should pass" );
let result = client.embeddings()
.create( "test", "invalid_model_name" )
.await;
assert!( result.is_err(), "Invalid model name should cause API failure : {result:?}" );
}
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_batch_validation_with_real_api()
{
let api_key_string = crate::inc::get_api_key_for_integration();
let api_key = Secret::new( api_key_string );
let env = HuggingFaceEnvironmentImpl::build( api_key, None )
.expect( "Environment build should succeed" );
let client = Client::build( env )
.expect( "Client build should succeed" );
let large_batch : Vec< String > = ( 0..1001 ).map( |i| format!( "input_{i}" ) ).collect();
assert!( validate_batch_inputs( &large_batch ).is_err(), "Large batch should be invalid" );
let small_batch = vec![ "test1".to_string(), "test2".to_string() ];
assert!( validate_batch_inputs( &small_batch ).is_ok(), "Small batch should be valid" );
let response = client.embeddings()
.create_batch( small_batch.clone(), "BAAI/bge-large-en-v1.5" )
.await;
match response
{
Ok( embeddings ) =>
{
match embeddings
{
api_huggingface::components::embeddings::EmbeddingResponse::Batch( batch_embeddings ) =>
{
assert_eq!( batch_embeddings.len(), small_batch.len(), "Should get embedding for each input" );
},
api_huggingface::components::embeddings::EmbeddingResponse::Single( _ ) => {},
}
},
Err( e ) => panic!( "Integration embedding batch should succeed with valid credentials: {e}" ),
}
}