use crate::core::{
config::Config, constants::AccessTokenType, error::LarkAPIError, req_option::RequestOption,
};
use reqwest::RequestBuilder;
pub struct AuthHandler;
impl AuthHandler {
pub async fn apply_auth(
req_builder: RequestBuilder,
access_token_type: AccessTokenType,
config: &Config,
option: &RequestOption,
) -> Result<RequestBuilder, LarkAPIError> {
match access_token_type {
AccessTokenType::None => Ok(req_builder),
AccessTokenType::App => Self::apply_app_auth(req_builder, config, option).await,
AccessTokenType::Tenant => Self::apply_tenant_auth(req_builder, config, option).await,
AccessTokenType::User => Ok(Self::apply_user_auth(req_builder, option)),
}
}
async fn apply_app_auth(
req_builder: RequestBuilder,
config: &Config,
option: &RequestOption,
) -> Result<RequestBuilder, LarkAPIError> {
let app_access_token = if !option.app_access_token.is_empty() {
option.app_access_token.clone()
} else if config.enable_token_cache {
let token_manager = config.token_manager.lock().await;
token_manager
.get_app_access_token(config, &option.app_ticket, &config.app_ticket_manager)
.await?
} else {
return Err(LarkAPIError::MissingAccessToken);
};
Ok(Self::add_auth_header(req_builder, &app_access_token))
}
async fn apply_tenant_auth(
req_builder: RequestBuilder,
config: &Config,
option: &RequestOption,
) -> Result<RequestBuilder, LarkAPIError> {
let tenant_access_token = if !option.tenant_access_token.is_empty() {
option.tenant_access_token.clone()
} else if config.enable_token_cache {
let token_manager = config.token_manager.lock().await;
token_manager
.get_tenant_access_token(
config,
&option.tenant_key,
&option.app_ticket,
&config.app_ticket_manager,
)
.await?
} else {
return Err(LarkAPIError::MissingAccessToken);
};
Ok(Self::add_auth_header(req_builder, &tenant_access_token))
}
fn apply_user_auth(req_builder: RequestBuilder, option: &RequestOption) -> RequestBuilder {
Self::add_auth_header(req_builder, &option.user_access_token)
}
fn add_auth_header(req_builder: RequestBuilder, token: &str) -> RequestBuilder {
req_builder.header("Authorization", format!("Bearer {token}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::constants::AppType;
use reqwest::Client;
fn create_test_config() -> Config {
Config::builder()
.app_id("test_app_id")
.app_secret("test_app_secret")
.app_type(AppType::SelfBuild)
.enable_token_cache(false)
.build()
}
fn create_test_request_builder() -> RequestBuilder {
Client::new().get("https://test.api.example.com/test")
}
#[test]
fn test_auth_handler_struct_creation() {
let _handler = AuthHandler;
}
#[tokio::test]
async fn test_apply_auth_none_type() {
let req_builder = create_test_request_builder();
let config = create_test_config();
let option = RequestOption::default();
let result =
AuthHandler::apply_auth(req_builder, AccessTokenType::None, &config, &option).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_apply_auth_user_type() {
let req_builder = create_test_request_builder();
let config = create_test_config();
let option = RequestOption {
user_access_token: "user_token_123".to_string(),
..Default::default()
};
let result =
AuthHandler::apply_auth(req_builder, AccessTokenType::User, &config, &option).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_apply_app_auth_with_token_in_option() {
let req_builder = create_test_request_builder();
let config = create_test_config();
let option = RequestOption {
app_access_token: "app_token_123".to_string(),
..Default::default()
};
let result = AuthHandler::apply_app_auth(req_builder, &config, &option).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_apply_app_auth_no_cache_no_token() {
let req_builder = create_test_request_builder();
let config = create_test_config(); let option = RequestOption::default();
let result = AuthHandler::apply_app_auth(req_builder, &config, &option).await;
assert!(result.is_err());
match result {
Err(LarkAPIError::MissingAccessToken) => (),
_ => panic!("Expected MissingAccessToken error"),
}
}
#[tokio::test]
async fn test_apply_tenant_auth_with_token_in_option() {
let req_builder = create_test_request_builder();
let config = create_test_config();
let option = RequestOption {
tenant_access_token: "tenant_token_123".to_string(),
..Default::default()
};
let result = AuthHandler::apply_tenant_auth(req_builder, &config, &option).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_apply_tenant_auth_no_cache_no_token() {
let req_builder = create_test_request_builder();
let config = create_test_config(); let option = RequestOption::default();
let result = AuthHandler::apply_tenant_auth(req_builder, &config, &option).await;
assert!(result.is_err());
match result {
Err(LarkAPIError::MissingAccessToken) => (),
_ => panic!("Expected MissingAccessToken error"),
}
}
#[test]
fn test_apply_user_auth() {
let req_builder = create_test_request_builder();
let option = RequestOption {
user_access_token: "user_token_456".to_string(),
..Default::default()
};
let result = AuthHandler::apply_user_auth(req_builder, &option);
assert!(format!("{:?}", result).contains("RequestBuilder"));
}
#[test]
fn test_add_auth_header_with_token() {
let req_builder = create_test_request_builder();
let token = "test_token_789";
let result = AuthHandler::add_auth_header(req_builder, token);
assert!(format!("{:?}", result).contains("RequestBuilder"));
}
#[test]
fn test_add_auth_header_with_empty_token() {
let req_builder = create_test_request_builder();
let token = "";
let result = AuthHandler::add_auth_header(req_builder, token);
assert!(format!("{:?}", result).contains("RequestBuilder"));
}
#[tokio::test]
async fn test_apply_auth_all_types() {
let config = create_test_config();
let test_cases = vec![
(AccessTokenType::None, RequestOption::default()),
(
AccessTokenType::User,
RequestOption {
user_access_token: "user_token".to_string(),
..Default::default()
},
),
(
AccessTokenType::App,
RequestOption {
app_access_token: "app_token".to_string(),
..Default::default()
},
),
(
AccessTokenType::Tenant,
RequestOption {
tenant_access_token: "tenant_token".to_string(),
..Default::default()
},
),
];
for (token_type, option) in test_cases {
let req_builder = create_test_request_builder();
let result = AuthHandler::apply_auth(req_builder, token_type, &config, &option).await;
match token_type {
AccessTokenType::None | AccessTokenType::User => {
assert!(result.is_ok());
}
AccessTokenType::App | AccessTokenType::Tenant => {
assert!(result.is_ok());
}
}
}
}
#[tokio::test]
async fn test_apply_auth_with_cache_enabled() {
let config = Config::builder()
.app_id("test_app_id")
.app_secret("test_app_secret")
.app_type(AppType::SelfBuild)
.enable_token_cache(true)
.build();
let option = RequestOption::default();
let req_builder = create_test_request_builder();
let result =
AuthHandler::apply_auth(req_builder, AccessTokenType::App, &config, &option).await;
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_auth_handler_trait_implementations() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<AuthHandler>();
assert_sync::<AuthHandler>();
}
#[test]
fn test_add_auth_header_format() {
let req_builder = create_test_request_builder();
let token = "test123";
let _result = AuthHandler::add_auth_header(req_builder, token);
}
}