mod inc;
use api_huggingface::
{
environment::{ HuggingFaceEnvironmentImpl, HuggingFaceEnvironment, EnvironmentInterface },
secret::Secret,
error::{ HuggingFaceError, ApiErrorWrap, Result },
};
#[ tokio::test ]
async fn error_message_structure()
{
let result : Result< () > = Err( HuggingFaceError::Authentication( "Authentication failed".to_string() ) );
match result
{
Err( HuggingFaceError::Authentication( msg ) ) =>
{
assert!( !msg.is_empty(), "Error message should not be empty" );
assert!( msg.contains( "Authentication" ), "Error message should describe the error" );
},
_ => panic!( "Expected Authentication error" ),
}
}
#[ tokio::test ]
async fn invalid_base_url_handling()
{
let api_key = Secret::new( "test-key".to_string() );
let env = HuggingFaceEnvironmentImpl::build( api_key, None ).expect( "Environment build should succeed" );
let result = env.endpoint_url( "not-a-valid-path with spaces" );
assert!( result.is_ok(), "Should handle URL construction gracefully" );
}
#[ tokio::test ]
async fn environment_with_invalid_url()
{
let api_key = Secret::new( "test-key".to_string() );
let invalid_url = Some( "not-a-valid-url".to_string() );
let env_result = HuggingFaceEnvironmentImpl::build( api_key, invalid_url );
assert!( env_result.is_ok(), "Environment build should succeed" );
let env = env_result.expect( "[environment_invalid_url_handling] Environment should be Ok after is_ok() check - check HuggingFaceEnvironmentImpl::build() implementation" );
let url_result = env.endpoint_url( "/test" );
assert!( url_result.is_err(), "Should fail with invalid base URL" );
match url_result.unwrap_err()
{
HuggingFaceError::InvalidArgument( _ ) => {}, other => panic!( "Expected InvalidArgument error, got : {other:?}" ),
}
}
#[ tokio::test ]
async fn header_generation_with_special_characters()
{
let api_key = Secret::new( "test-key-with-special-chars-!@#$%".to_string() );
let env = HuggingFaceEnvironmentImpl::build( api_key, None ).expect( "Environment build should succeed" );
let headers_result = env.headers();
assert!( headers_result.is_ok(), "Should handle special characters in API key" );
}
#[ tokio::test ]
async fn error_display_formatting()
{
let errors = vec!
[
HuggingFaceError::Authentication( "Auth failed".to_string() ),
HuggingFaceError::InvalidArgument( "Invalid arg".to_string() ),
HuggingFaceError::Http( "HTTP error".to_string() ),
HuggingFaceError::RateLimit( "Rate limited".to_string() ),
];
for error in errors
{
let display_str = format!( "{error}" );
assert!( !display_str.is_empty(), "Error display should not be empty" );
let debug_str = format!( "{error:?}" );
assert!( !debug_str.is_empty(), "Error debug should not be empty" );
}
}
#[ tokio::test ]
async fn error_chain_propagation()
{
fn simulate_nested_error() -> Result< String >
{
Err( HuggingFaceError::Http( "Connection failed".to_string() ) )
.map_err( | e | HuggingFaceError::InvalidArgument( format!( "Wrapped : {e}" ) ) )
}
let result = simulate_nested_error();
assert!( result.is_err(), "Should propagate error" );
match result.unwrap_err()
{
HuggingFaceError::InvalidArgument( msg ) =>
{
assert!( msg.contains( "Wrapped :" ), "Should contain wrapped error context" );
assert!( msg.contains( "Connection failed" ), "Should contain original error message" );
},
other => panic!( "Expected InvalidArgument error, got : {other:?}" ),
}
}
#[ test ]
fn test_all_error_variants_display_content()
{
let cases : Vec< ( HuggingFaceError, &str ) > = vec!
[
( HuggingFaceError::Api( ApiErrorWrap::new( "api msg" ) ), "api msg" ),
( HuggingFaceError::Http( "http msg".into() ), "http msg" ),
( HuggingFaceError::Authentication( "auth msg".into() ), "auth msg" ),
( HuggingFaceError::Validation( "val msg".into() ), "val msg" ),
( HuggingFaceError::RateLimit( "rate msg".into() ), "rate msg" ),
( HuggingFaceError::ModelUnavailable( "model msg".into() ), "model msg" ),
( HuggingFaceError::Stream( "stream msg".into() ), "stream msg" ),
( HuggingFaceError::Serialization( "serial msg".into() ), "serial msg" ),
( HuggingFaceError::InvalidArgument( "arg msg".into() ), "arg msg" ),
( HuggingFaceError::Generic( "generic msg".into() ), "generic msg" ),
];
for ( error, expected_fragment ) in cases
{
let display = format!( "{error}" );
assert!( !display.is_empty(), "Display must not be empty for {error:?}" );
assert!(
display.contains( expected_fragment ),
"Display must contain inner message. Got: {display:?}, expected fragment: {expected_fragment:?}"
);
}
}
#[ test ]
fn test_api_error_wrap_display_combinations()
{
let wrap = ApiErrorWrap::new( "something went wrong" );
let s = format!( "{wrap}" );
assert_eq!( s, "something went wrong", "bare message only" );
let wrap = ApiErrorWrap::new( "model failed" ).with_error_type( "ModelError" );
let s = format!( "{wrap}" );
assert!( s.contains( "[ModelError]" ), "should contain bracketed error type; got: {s:?}" );
assert!( s.contains( "model failed" ), "should contain message; got: {s:?}" );
assert!( !s.contains( "HTTP" ), "no HTTP status when status_code absent; got: {s:?}" );
let wrap = ApiErrorWrap::new( "not found" ).with_status_code( 404 );
let s = format!( "{wrap}" );
assert!( s.contains( "not found" ), "should contain message; got: {s:?}" );
assert!( s.contains( "(HTTP 404)" ), "should contain HTTP status; got: {s:?}" );
assert!( !s.contains( '[' ), "no bracket when error_type absent; got: {s:?}" );
let wrap = ApiErrorWrap::new( "internal error" )
.with_error_type( "ServerError" )
.with_status_code( 500 );
let s = format!( "{wrap}" );
assert!( s.contains( "[ServerError]" ), "should contain error type; got: {s:?}" );
assert!( s.contains( "internal error" ), "should contain message; got: {s:?}" );
assert!( s.contains( "(HTTP 500)" ), "should contain HTTP status; got: {s:?}" );
}
#[ cfg( feature = "integration" ) ]
use api_huggingface::
{
Client,
components::input::InferenceParameters,
};
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_authentication_error_with_invalid_key()
{
let invalid_key = Secret::new( "invalid-key-12345".to_string() );
let env = HuggingFaceEnvironmentImpl::build( invalid_key, None )
.expect( "Environment build should succeed" );
let client = Client::build( env )
.expect( "Client build should succeed" );
let result = client.inference()
.create( "test", "microsoft/DialoGPT-medium" )
.await;
assert!( result.is_err(), "Invalid API key should cause error" );
let error = result.unwrap_err();
let error_string = format!( "{error}" );
assert!(
error_string.contains( "401" ) || error_string.contains( "auth" ) || error_string.contains( "token" ),
"Error should indicate authentication issue : {error_string}"
);
}
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_rate_limit_error_handling()
{
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 mut results = Vec::new();
for i in 0..5
{
let result = client.inference()
.create_with_parameters( &format!( "test {i}" ), "microsoft/DialoGPT-medium", InferenceParameters::default().with_max_new_tokens( 10 ) )
.await;
results.push( result );
tokio::time::sleep( core::time::Duration::from_millis( 100 ) ).await;
}
let successful_calls = results.iter().filter( |r| r.is_ok() ).count();
let failed_calls = results.iter().filter( |r| r.is_err() ).count();
assert!(
successful_calls > 0 || failed_calls == results.len(),
"Either some calls should succeed or all should fail with proper errors. Successful : {successful_calls}, Failed : {failed_calls}"
);
for ( i, result ) in results.iter().enumerate()
{
if let Err( error ) = result
{
let error_string = format!( "{error}" );
println!( "Call {i} error : {error_string}" );
assert!( !error_string.is_empty(), "Error message should not be empty" );
}
}
}
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_invalid_model_error_handling()
{
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 result = client.embeddings()
.create( "test", "non-existent-model-12345" )
.await;
assert!( result.is_err(), "Non-existent model should cause error" );
let error = result.unwrap_err();
let error_string = format!( "{error}" );
let lower = error_string.to_lowercase();
assert!(
lower.contains( "model" ) || lower.contains( "404" ) || lower.contains( "not found" ) || lower.contains( "not supported" ),
"Error should indicate model issue : {error_string}"
);
}
#[ cfg( feature = "integration" ) ]
#[ tokio::test ]
async fn integration_network_error_recovery()
{
let api_key = Secret::new( crate::inc::get_api_key_for_integration() );
let invalid_url = Some( "https://invalid-domain-that-does-not-exist-12345.com".to_string() );
let env = HuggingFaceEnvironmentImpl::build( api_key, invalid_url )
.expect( "Environment build should succeed" );
let client = Client::build( env )
.expect( "Client build should succeed" );
let result = client.inference()
.create( "test", "microsoft/DialoGPT-medium" )
.await;
assert!( result.is_err(), "Invalid URL should cause network error" );
let error = result.unwrap_err();
let error_string = format!( "{error}" );
assert!( !error_string.is_empty(), "Network error should have descriptive message" );
let has_network_keywords = error_string.contains( "dns" )
|| error_string.contains( "connection" )
|| error_string.contains( "network" )
|| error_string.contains( "resolve" )
|| error_string.contains( "sending request" )
|| error_string.contains( "error" );
assert!( has_network_keywords, "Network error should contain relevant keywords : {error_string}" );
}