use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessToken {
pub access_token: String,
pub token_type: String,
pub expires_in: u64,
pub created_at: DateTime<Utc>,
}
impl AccessToken {
pub fn new(access_token: String, token_type: String, expires_in: u64) -> Self {
Self {
access_token,
token_type,
expires_in,
created_at: Utc::now(),
}
}
pub fn is_expired(&self) -> bool {
let elapsed = Utc::now().signed_duration_since(self.created_at);
elapsed.num_seconds() >= (self.expires_in as i64 - 60) }
pub fn remaining_seconds(&self) -> i64 {
let elapsed = Utc::now().signed_duration_since(self.created_at);
(self.expires_in as i64) - elapsed.num_seconds()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserAccessToken {
pub access_token: String,
pub refresh_token: String,
pub token_type: String,
pub expires_in: u64,
pub refresh_expires_in: u64,
pub scope: String,
pub created_at: DateTime<Utc>,
}
impl UserAccessToken {
pub fn new(
access_token: String,
refresh_token: String,
token_type: String,
expires_in: u64,
refresh_expires_in: u64,
scope: String,
) -> Self {
Self {
access_token,
refresh_token,
token_type,
expires_in,
refresh_expires_in,
scope,
created_at: Utc::now(),
}
}
pub fn is_access_token_expired(&self) -> bool {
let elapsed = Utc::now().signed_duration_since(self.created_at);
elapsed.num_seconds() >= (self.expires_in as i64 - 60) }
pub fn is_refresh_token_expired(&self) -> bool {
let elapsed = Utc::now().signed_duration_since(self.created_at);
elapsed.num_seconds() >= (self.refresh_expires_in as i64 - 60)
}
pub fn access_token_remaining_seconds(&self) -> i64 {
let elapsed = Utc::now().signed_duration_since(self.created_at);
(self.expires_in as i64) - elapsed.num_seconds()
}
pub fn refresh_token_remaining_seconds(&self) -> i64 {
let elapsed = Utc::now().signed_duration_since(self.created_at);
(self.refresh_expires_in as i64) - elapsed.num_seconds()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInfo {
pub content: serde_json::Value,
pub verified_at: DateTime<Utc>,
}
impl TokenInfo {
pub fn new(content: serde_json::Value) -> Self {
Self {
content,
verified_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub user_id: String,
pub name: String,
pub email: Option<String>,
pub mobile: Option<String>,
pub avatar: Option<String>,
pub department_ids: Option<Vec<String>>,
pub fetched_at: DateTime<Utc>,
}
impl UserInfo {
pub fn new(
user_id: String,
name: String,
email: Option<String>,
mobile: Option<String>,
avatar: Option<String>,
department_ids: Option<Vec<String>>,
) -> Self {
Self {
user_id,
name,
email,
mobile,
avatar,
department_ids,
fetched_at: Utc::now(),
}
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use super::*;
#[test]
fn test_access_token_creation() {
let token = AccessToken::new(
"test_access_token".to_string(),
"Bearer".to_string(),
7200, );
assert_eq!(token.access_token, "test_access_token");
assert_eq!(token.token_type, "Bearer");
assert_eq!(token.expires_in, 7200);
assert!(!token.is_expired()); assert!(token.remaining_seconds() > 7000); }
#[test]
fn test_access_token_expiry() {
let token = AccessToken {
access_token: "expired_token".to_string(),
token_type: "Bearer".to_string(),
expires_in: 120, created_at: Utc::now() - chrono::Duration::seconds(61), };
assert!(token.is_expired());
assert!(token.remaining_seconds() <= 60);
}
#[test]
fn test_access_token_remaining_seconds() {
let token = AccessToken::new(
"test_token".to_string(),
"Bearer".to_string(),
3600, );
let remaining = token.remaining_seconds();
assert!(remaining > 3500); assert!(remaining <= 3600); }
#[test]
fn test_user_access_token_creation() {
let user_token = UserAccessToken::new(
"user_access_token".to_string(),
"refresh_token".to_string(),
"Bearer".to_string(),
7200, 2592000, "scope1 scope2".to_string(),
);
assert_eq!(user_token.access_token, "user_access_token");
assert_eq!(user_token.refresh_token, "refresh_token");
assert_eq!(user_token.token_type, "Bearer");
assert_eq!(user_token.expires_in, 7200);
assert_eq!(user_token.refresh_expires_in, 2592000);
assert_eq!(user_token.scope, "scope1 scope2");
assert!(!user_token.is_access_token_expired());
assert!(!user_token.is_refresh_token_expired());
}
#[test]
fn test_user_access_token_expiry() {
let user_token = UserAccessToken {
access_token: "expired_user_token".to_string(),
refresh_token: "refresh_token".to_string(),
token_type: "Bearer".to_string(),
expires_in: 120, refresh_expires_in: 180, scope: "test_scope".to_string(),
created_at: Utc::now() - chrono::Duration::seconds(121),
};
assert!(user_token.is_access_token_expired());
assert!(user_token.is_refresh_token_expired());
}
#[test]
fn test_user_access_token_remaining_seconds() {
let user_token = UserAccessToken::new(
"test_user_token".to_string(),
"refresh_token".to_string(),
"Bearer".to_string(),
3600, 7200, "scope".to_string(),
);
let access_remaining = user_token.access_token_remaining_seconds();
let refresh_remaining = user_token.refresh_token_remaining_seconds();
assert!(access_remaining > 3500 && access_remaining <= 3600);
assert!(refresh_remaining > 7000 && refresh_remaining <= 7200);
}
#[test]
fn test_token_info_creation() {
let content = serde_json::json!({
"user_id": "test_user",
"tenant_key": "test_tenant"
});
let token_info = TokenInfo::new(content.clone());
assert_eq!(token_info.content, content);
let now = Utc::now();
let time_diff = (now - token_info.verified_at).num_seconds().abs();
assert!(time_diff <= 1);
}
#[test]
fn test_user_info_creation() {
let departments = vec!["dept1".to_string(), "dept2".to_string()];
let user_info = UserInfo::new(
"user_123".to_string(),
"张三".to_string(),
Some("zhangsan@example.com".to_string()),
Some("+86 138 0013 8000".to_string()),
Some("https://example.com/avatar.jpg".to_string()),
Some(departments.clone()),
);
assert_eq!(user_info.user_id, "user_123");
assert_eq!(user_info.name, "张三");
assert_eq!(user_info.email, Some("zhangsan@example.com".to_string()));
assert_eq!(user_info.mobile, Some("+86 138 0013 8000".to_string()));
assert_eq!(
user_info.avatar,
Some("https://example.com/avatar.jpg".to_string())
);
assert_eq!(user_info.department_ids, Some(departments));
let now = Utc::now();
let time_diff = (now - user_info.fetched_at).num_seconds().abs();
assert!(time_diff <= 1);
}
#[test]
fn test_user_info_optional_fields() {
let user_info = UserInfo::new(
"user_456".to_string(),
"李四".to_string(),
None,
None,
None,
None,
);
assert_eq!(user_info.user_id, "user_456");
assert_eq!(user_info.name, "李四");
assert!(user_info.email.is_none());
assert!(user_info.mobile.is_none());
assert!(user_info.avatar.is_none());
assert!(user_info.department_ids.is_none());
}
#[test]
fn test_token_serialization() {
let token = AccessToken::new("test_token".to_string(), "Bearer".to_string(), 3600);
let json_str = serde_json::to_string(&token).unwrap();
let parsed: AccessToken = serde_json::from_str(&json_str).unwrap();
assert_eq!(parsed.access_token, token.access_token);
assert_eq!(parsed.token_type, token.token_type);
assert_eq!(parsed.expires_in, token.expires_in);
}
#[test]
fn test_user_token_serialization() {
let user_token = UserAccessToken::new(
"access_token".to_string(),
"refresh_token".to_string(),
"Bearer".to_string(),
3600,
7200,
"read write".to_string(),
);
let json_str = serde_json::to_string(&user_token).unwrap();
let parsed: UserAccessToken = serde_json::from_str(&json_str).unwrap();
assert_eq!(parsed.access_token, user_token.access_token);
assert_eq!(parsed.refresh_token, user_token.refresh_token);
assert_eq!(parsed.token_type, user_token.token_type);
assert_eq!(parsed.scope, user_token.scope);
}
#[test]
fn test_edge_case_expiry_times() {
let token = AccessToken::new(
"boundary_token".to_string(),
"Bearer".to_string(),
0, );
assert!(token.is_expired());
let negative_token =
AccessToken::new("negative_token".to_string(), "Bearer".to_string(), 0);
assert!(negative_token.is_expired());
}
#[test]
fn test_debug_formatting() {
let token = AccessToken::new("debug_token".to_string(), "Bearer".to_string(), 3600);
let debug_str = format!("{:?}", token);
assert!(debug_str.contains("AccessToken"));
assert!(debug_str.contains("debug_token"));
}
#[test]
fn test_clone_functionality() {
let original_token = AccessToken::new(
"clone_test_token".to_string(),
"Bearer".to_string(),
1800, );
let cloned_token = original_token.clone();
assert_eq!(original_token.access_token, cloned_token.access_token);
assert_eq!(original_token.token_type, cloned_token.token_type);
assert_eq!(original_token.expires_in, cloned_token.expires_in);
assert_eq!(original_token.created_at, cloned_token.created_at);
let original_user_token = UserAccessToken::new(
"user_access_token".to_string(),
"refresh_token".to_string(),
"Bearer".to_string(),
3600,
7200,
"read".to_string(),
);
let cloned_user_token = original_user_token.clone();
assert_eq!(
original_user_token.access_token,
cloned_user_token.access_token
);
assert_eq!(
original_user_token.refresh_token,
cloned_user_token.refresh_token
);
}
}