use serde::{Deserialize, Serialize};
use crate::core::{
cache::QuickCache,
config::Config,
constants::{APPLY_APP_TICKET_PATH, APP_TICKET_KEY_PREFIX},
SDKResult,
};
#[derive(Debug)]
pub struct AppTicketManager {
pub cache: QuickCache<String>,
}
impl Default for AppTicketManager {
fn default() -> Self {
Self::new()
}
}
impl AppTicketManager {
pub fn new() -> Self {
Self {
cache: QuickCache::new(),
}
}
pub fn set(&mut self, app_id: &str, value: &str, expire_time: i32) {
let key = app_ticket_key(app_id);
self.cache.set(&key, value.to_string(), expire_time);
}
pub async fn get(&self, config: &Config) -> Option<String> {
let key = app_ticket_key(&config.app_id);
match self.cache.get(&key) {
None => None,
Some(ticket) => {
if ticket.is_empty() {
apply_app_ticket(config).await.ok();
}
Some(ticket)
}
}
}
}
fn app_ticket_key(app_id: &str) -> String {
format!("{APP_TICKET_KEY_PREFIX}-{app_id}")
}
pub async fn apply_app_ticket(config: &Config) -> SDKResult<()> {
let url = format!("{}{}", config.base_url, APPLY_APP_TICKET_PATH);
let body = ResendAppTicketReq {
app_id: config.app_id.clone(),
app_secret: config.app_secret.clone(),
};
let _response = config.http_client.post(&url).json(&body).send().await?;
Ok(())
}
#[derive(Serialize, Deserialize)]
struct ResendAppTicketReq {
app_id: String,
app_secret: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::config::Config;
use std::time::Duration;
fn create_test_config() -> Config {
Config::builder()
.app_id("test_app_id")
.app_secret("test_app_secret")
.base_url("https://test.api.com")
.build()
}
#[test]
fn test_app_ticket_manager_creation() {
let manager = AppTicketManager::new();
assert!(format!("{:?}", manager).contains("AppTicketManager"));
}
#[test]
fn test_app_ticket_manager_default() {
let manager = AppTicketManager::default();
assert!(format!("{:?}", manager).contains("AppTicketManager"));
}
#[test]
fn test_app_ticket_key_generation() {
let app_id = "test_app_123";
let key = app_ticket_key(app_id);
assert!(key.contains("test_app_123"));
assert!(key.starts_with("app_ticket"));
}
#[test]
fn test_set_and_get_ticket() {
let mut manager = AppTicketManager::new();
let app_id = "test_app";
let ticket_value = "test_ticket_value";
manager.set(app_id, ticket_value, 60);
let key = app_ticket_key(app_id);
let cached_ticket = manager.cache.get(&key);
assert_eq!(cached_ticket, Some(ticket_value.to_string()));
}
#[test]
fn test_set_ticket_with_different_app_ids() {
let mut manager = AppTicketManager::new();
manager.set("app1", "ticket1", 60);
manager.set("app2", "ticket2", 60);
let key1 = app_ticket_key("app1");
let key2 = app_ticket_key("app2");
assert_eq!(manager.cache.get(&key1), Some("ticket1".to_string()));
assert_eq!(manager.cache.get(&key2), Some("ticket2".to_string()));
}
#[test]
fn test_ticket_expiration() {
let mut manager = AppTicketManager::new();
let app_id = "test_app";
manager.set(app_id, "short_lived_ticket", 1);
let key = app_ticket_key(app_id);
assert!(manager.cache.get(&key).is_some());
std::thread::sleep(Duration::from_secs(2));
assert!(manager.cache.get(&key).is_none());
}
#[test]
fn test_overwrite_existing_ticket() {
let mut manager = AppTicketManager::new();
let app_id = "test_app";
manager.set(app_id, "initial_ticket", 60);
manager.set(app_id, "updated_ticket", 60);
let key = app_ticket_key(app_id);
assert_eq!(manager.cache.get(&key), Some("updated_ticket".to_string()));
}
#[test]
fn test_empty_app_id() {
let mut manager = AppTicketManager::new();
manager.set("", "ticket", 60);
let key = app_ticket_key("");
assert!(manager.cache.get(&key).is_some());
}
#[test]
fn test_special_characters_in_app_id() {
let mut manager = AppTicketManager::new();
let special_app_id = "app-id_with.special@chars";
manager.set(special_app_id, "special_ticket", 60);
let key = app_ticket_key(special_app_id);
assert_eq!(manager.cache.get(&key), Some("special_ticket".to_string()));
}
#[test]
fn test_zero_expiration_time() {
let mut manager = AppTicketManager::new();
let app_id = "test_app";
manager.set(app_id, "zero_exp_ticket", 0);
std::thread::sleep(Duration::from_millis(10));
let key = app_ticket_key(app_id);
let _ = manager.cache.get(&key);
}
#[test]
fn test_very_long_ticket_value() {
let mut manager = AppTicketManager::new();
let app_id = "test_app";
let long_ticket = "a".repeat(10000);
manager.set(app_id, &long_ticket, 60);
let key = app_ticket_key(app_id);
assert_eq!(manager.cache.get(&key), Some(long_ticket));
}
#[tokio::test]
async fn test_get_with_cached_ticket() {
let mut manager = AppTicketManager::new();
let config = create_test_config();
manager.set(&config.app_id, "cached_ticket", 60);
let result = manager.get(&config).await;
assert_eq!(result, Some("cached_ticket".to_string()));
}
#[tokio::test]
async fn test_get_with_empty_ticket() {
let mut manager = AppTicketManager::new();
let config = create_test_config();
manager.set(&config.app_id, "", 60);
let result = manager.get(&config).await;
assert_eq!(result, Some("".to_string()));
}
#[tokio::test]
async fn test_get_without_cached_ticket() {
let manager = AppTicketManager::new();
let config = create_test_config();
let result = manager.get(&config).await;
assert_eq!(result, None);
}
#[test]
fn test_resend_app_ticket_req_serialization() {
let req = ResendAppTicketReq {
app_id: "test_app".to_string(),
app_secret: "test_secret".to_string(),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("test_app"));
assert!(json.contains("test_secret"));
let deserialized: ResendAppTicketReq = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.app_id, "test_app");
assert_eq!(deserialized.app_secret, "test_secret");
}
#[test]
fn test_multiple_managers_independence() {
let mut manager1 = AppTicketManager::new();
let mut manager2 = AppTicketManager::new();
manager1.set("app1", "ticket1", 60);
manager2.set("app2", "ticket2", 60);
let key1 = app_ticket_key("app1");
let key2 = app_ticket_key("app2");
assert_eq!(manager1.cache.get(&key1), Some("ticket1".to_string()));
assert_eq!(manager1.cache.get(&key2), None);
assert_eq!(manager2.cache.get(&key2), Some("ticket2".to_string()));
assert_eq!(manager2.cache.get(&key1), None);
}
#[test]
fn test_app_ticket_key_format() {
let test_cases = vec![
("simple_app", "app_ticket-simple_app"),
("app-with-dashes", "app_ticket-app-with-dashes"),
("app_with_underscores", "app_ticket-app_with_underscores"),
("123numeric", "app_ticket-123numeric"),
];
for (app_id, _expected_suffix) in test_cases {
let key = app_ticket_key(app_id);
assert!(key.ends_with(&format!("-{}", app_id)));
assert!(key.starts_with("app_ticket"));
}
}
}