1use log::warn;
2use serde::{Deserialize, Serialize};
3use std::{
4 sync::{
5 atomic::{AtomicU64, Ordering},
6 Arc,
7 },
8 time::{Duration, Instant},
9};
10use tokio::{
11 sync::{Mutex, RwLock},
12 time::interval,
13};
14use tracing::{info_span, Instrument};
15
16use crate::core::{
17 api_resp::{ApiResponseTrait, RawResponse, ResponseFormat},
18 app_ticket_manager::AppTicketManager,
19 cache::QuickCache,
20 config::Config,
21 constants::{
22 AppType, APP_ACCESS_TOKEN_INTERNAL_URL_PATH, APP_ACCESS_TOKEN_KEY_PREFIX,
23 APP_ACCESS_TOKEN_URL_PATH, EXPIRY_DELTA, TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH,
24 TENANT_ACCESS_TOKEN_URL_PATH,
25 },
26 error::LarkAPIError,
27 SDKResult,
28};
29
30#[derive(Debug, Clone)]
32pub struct PreheatingConfig {
33 pub check_interval_seconds: u64,
35 pub preheat_threshold_seconds: u64,
37 pub enable_tenant_preheating: bool,
39 pub max_concurrent_preheat: usize,
41}
42
43impl Default for PreheatingConfig {
44 fn default() -> Self {
45 Self {
46 check_interval_seconds: 1800, preheat_threshold_seconds: 900, enable_tenant_preheating: true,
49 max_concurrent_preheat: 3,
50 }
51 }
52}
53
54#[derive(Debug, Default)]
56pub struct TokenMetrics {
57 pub app_cache_hits: AtomicU64,
59 pub app_cache_misses: AtomicU64,
61 pub tenant_cache_hits: AtomicU64,
63 pub tenant_cache_misses: AtomicU64,
65 pub refresh_success: AtomicU64,
67 pub refresh_failures: AtomicU64,
69 pub read_lock_acquisitions: AtomicU64,
71 pub write_lock_acquisitions: AtomicU64,
73}
74
75impl TokenMetrics {
76 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn app_cache_hit_rate(&self) -> f64 {
82 let hits = self.app_cache_hits.load(Ordering::Relaxed) as f64;
83 let misses = self.app_cache_misses.load(Ordering::Relaxed) as f64;
84 let total = hits + misses;
85 if total > 0.0 {
86 hits / total
87 } else {
88 0.0
89 }
90 }
91
92 pub fn tenant_cache_hit_rate(&self) -> f64 {
94 let hits = self.tenant_cache_hits.load(Ordering::Relaxed) as f64;
95 let misses = self.tenant_cache_misses.load(Ordering::Relaxed) as f64;
96 let total = hits + misses;
97 if total > 0.0 {
98 hits / total
99 } else {
100 0.0
101 }
102 }
103
104 pub fn refresh_success_rate(&self) -> f64 {
106 let success = self.refresh_success.load(Ordering::Relaxed) as f64;
107 let failures = self.refresh_failures.load(Ordering::Relaxed) as f64;
108 let total = success + failures;
109 if total > 0.0 {
110 success / total
111 } else {
112 0.0
113 }
114 }
115
116 pub fn performance_report(&self) -> String {
118 format!(
119 "TokenManager Performance Metrics:\n\
120 - App Cache Hit Rate: {:.2}%\n\
121 - Tenant Cache Hit Rate: {:.2}%\n\
122 - Refresh Success Rate: {:.2}%\n\
123 - Total Read Locks: {}\n\
124 - Total Write Locks: {}\n\
125 - App Cache: {} hits, {} misses\n\
126 - Tenant Cache: {} hits, {} misses\n\
127 - Refreshes: {} success, {} failures",
128 self.app_cache_hit_rate() * 100.0,
129 self.tenant_cache_hit_rate() * 100.0,
130 self.refresh_success_rate() * 100.0,
131 self.read_lock_acquisitions.load(Ordering::Relaxed),
132 self.write_lock_acquisitions.load(Ordering::Relaxed),
133 self.app_cache_hits.load(Ordering::Relaxed),
134 self.app_cache_misses.load(Ordering::Relaxed),
135 self.tenant_cache_hits.load(Ordering::Relaxed),
136 self.tenant_cache_misses.load(Ordering::Relaxed),
137 self.refresh_success.load(Ordering::Relaxed),
138 self.refresh_failures.load(Ordering::Relaxed)
139 )
140 }
141}
142
143#[derive(Debug)]
144pub struct TokenManager {
145 cache: Arc<RwLock<QuickCache<String>>>,
146 metrics: Arc<TokenMetrics>,
147 preheating_handle: Option<tokio::task::JoinHandle<()>>,
149}
150
151impl Default for TokenManager {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl TokenManager {
158 pub fn new() -> Self {
159 Self {
160 cache: Arc::new(RwLock::new(QuickCache::new())),
161 metrics: Arc::new(TokenMetrics::new()),
162 preheating_handle: None,
163 }
164 }
165
166 pub fn metrics(&self) -> &Arc<TokenMetrics> {
168 &self.metrics
169 }
170
171 pub fn get_cache(&self) -> Arc<RwLock<QuickCache<String>>> {
173 self.cache.clone()
174 }
175
176 pub fn get_metrics(&self) -> Arc<TokenMetrics> {
178 self.metrics.clone()
179 }
180
181 pub fn log_performance_metrics(&self) {
183 log::info!("{}", self.metrics.performance_report());
184 }
185
186 pub fn start_background_preheating(
194 &mut self,
195 config: Config,
196 app_ticket_manager: Arc<Mutex<AppTicketManager>>,
197 ) {
198 self.start_background_preheating_with_config(
199 config,
200 app_ticket_manager,
201 PreheatingConfig::default(),
202 )
203 }
204
205 pub fn start_background_preheating_with_config(
207 &mut self,
208 config: Config,
209 app_ticket_manager: Arc<Mutex<AppTicketManager>>,
210 preheat_config: PreheatingConfig,
211 ) {
212 if self.preheating_handle.is_some() {
214 log::info!("🔄 停止现有预热任务,启动新配置的预热任务");
215 self.stop_background_preheating();
216 }
217
218 let cache = self.cache.clone();
219 let metrics = self.metrics.clone();
220
221 let handle = tokio::spawn(async move {
222 let mut interval = interval(Duration::from_secs(preheat_config.check_interval_seconds));
223 log::info!(
224 "🔄 Token后台预热机制已启动,检查间隔: {}分钟,预热阈值: {}分钟",
225 preheat_config.check_interval_seconds / 60,
226 preheat_config.preheat_threshold_seconds / 60
227 );
228
229 loop {
230 interval.tick().await;
231
232 if let Err(e) = Self::preheat_tokens_if_needed_with_config(
233 &cache,
234 &metrics,
235 &config,
236 &app_ticket_manager,
237 &preheat_config,
238 )
239 .await
240 {
241 log::warn!("⚠️ Token预热过程中发生错误: {e:?}");
242 }
244 }
245 });
246
247 self.preheating_handle = Some(handle);
248 log::info!("✅ Token后台预热任务已启动并注册到TokenManager");
249 }
250
251 #[allow(dead_code)]
253 async fn preheat_tokens_if_needed(
254 cache: &Arc<RwLock<QuickCache<String>>>,
255 metrics: &Arc<TokenMetrics>,
256 config: &Config,
257 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
258 ) -> SDKResult<()> {
259 Self::preheat_tokens_if_needed_with_config(
260 cache,
261 metrics,
262 config,
263 app_ticket_manager,
264 &PreheatingConfig::default(),
265 )
266 .await
267 }
268
269 async fn preheat_tokens_if_needed_with_config(
271 cache: &Arc<RwLock<QuickCache<String>>>,
272 metrics: &Arc<TokenMetrics>,
273 config: &Config,
274 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
275 preheat_config: &PreheatingConfig,
276 ) -> SDKResult<()> {
277 log::debug!("🔍 检查需要预热的token...");
278
279 let mut preheated_count = 0;
280
281 let app_key = app_access_token_key(&config.app_id);
283 if Self::should_preheat_token_with_threshold(
284 cache,
285 &app_key,
286 preheat_config.preheat_threshold_seconds,
287 )
288 .await
289 {
290 log::info!("🔄 开始预热 app access token");
291 if let Err(e) = Self::preheat_app_token(cache, config, app_ticket_manager).await {
292 log::warn!("❌ App token预热失败: {e:?}");
293 metrics.refresh_failures.fetch_add(1, Ordering::Relaxed);
294 } else {
295 log::info!("✅ App token预热成功");
296 metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
297 preheated_count += 1;
298 }
299 }
300
301 if preheat_config.enable_tenant_preheating {
303 let tenant_keys = Self::get_cached_tenant_keys(cache, &config.app_id).await;
304 for tenant_key in tenant_keys
305 .into_iter()
306 .take(preheat_config.max_concurrent_preheat)
307 {
308 let tenant_cache_key = tenant_access_token_key(&config.app_id, &tenant_key);
309 if Self::should_preheat_token_with_threshold(
310 cache,
311 &tenant_cache_key,
312 preheat_config.preheat_threshold_seconds,
313 )
314 .await
315 {
316 log::info!("🔄 开始预热 tenant access token: {tenant_key}");
317 if let Err(e) =
318 Self::preheat_tenant_token(cache, config, &tenant_key, app_ticket_manager)
319 .await
320 {
321 log::warn!("❌ Tenant token预热失败 ({tenant_key}): {e:?}");
322 metrics.refresh_failures.fetch_add(1, Ordering::Relaxed);
323 } else {
324 log::info!("✅ Tenant token预热成功: {tenant_key}");
325 metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
326 preheated_count += 1;
327 }
328 }
329 }
330 }
331
332 if preheated_count > 0 {
333 log::info!("🎯 本轮预热完成,共刷新了 {preheated_count} 个token");
334 } else {
335 log::debug!("✨ 所有token状态良好,无需预热");
336 }
337
338 Ok(())
339 }
340
341 #[allow(dead_code)]
343 async fn should_preheat_token(cache: &Arc<RwLock<QuickCache<String>>>, key: &str) -> bool {
344 Self::should_preheat_token_with_threshold(cache, key, 900).await
345 }
346
347 async fn should_preheat_token_with_threshold(
351 cache: &Arc<RwLock<QuickCache<String>>>,
352 key: &str,
353 threshold_seconds: u64,
354 ) -> bool {
355 let cache_read = cache.read().await;
356
357 if cache_read.get(key).is_none_or(|token| token.is_empty()) {
359 log::debug!("🔍 Token {key} 不存在,需要预热");
360 return true;
361 }
362
363 if let Some(expiry_info) = cache_read.get_with_expiry(key) {
365 let remaining_seconds = expiry_info.expiry_seconds();
366 if remaining_seconds < threshold_seconds {
368 log::debug!(
369 "🔍 Token {key} 将在{remaining_seconds}秒后过期,阈值{threshold_seconds}秒,需要预热"
370 );
371 return true;
372 }
373 }
374
375 false
376 }
377
378 async fn get_cached_tenant_keys(
380 _cache: &Arc<RwLock<QuickCache<String>>>,
381 _app_id: &str,
382 ) -> Vec<String> {
383 vec![]
387 }
388
389 async fn preheat_app_token(
391 cache: &Arc<RwLock<QuickCache<String>>>,
392 config: &Config,
393 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
394 ) -> SDKResult<String> {
395 let temp_manager = TokenManager {
397 cache: cache.clone(),
398 metrics: Arc::new(TokenMetrics::new()), preheating_handle: None,
400 };
401
402 match config.app_type {
403 AppType::SelfBuild => {
404 temp_manager
405 .get_custom_app_access_token_then_cache(config)
406 .await
407 }
408 _ => {
409 temp_manager
410 .get_marketplace_app_access_token_then_cache(config, "", app_ticket_manager)
411 .await
412 }
413 }
414 }
415
416 async fn preheat_tenant_token(
418 cache: &Arc<RwLock<QuickCache<String>>>,
419 config: &Config,
420 tenant_key: &str,
421 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
422 ) -> SDKResult<String> {
423 let temp_manager = TokenManager {
425 cache: cache.clone(),
426 metrics: Arc::new(TokenMetrics::new()), preheating_handle: None,
428 };
429
430 if config.app_type == AppType::SelfBuild {
431 temp_manager
432 .get_custom_tenant_access_token_then_cache(config, tenant_key)
433 .await
434 } else {
435 temp_manager
436 .get_marketplace_tenant_access_token_then_cache(
437 config,
438 tenant_key,
439 "",
440 app_ticket_manager,
441 )
442 .await
443 }
444 }
445
446 pub fn stop_background_preheating(&mut self) {
448 if let Some(handle) = self.preheating_handle.take() {
449 handle.abort();
450 log::info!("🛑 Token后台预热机制已停止");
451 }
452 }
453
454 pub fn is_preheating_active(&self) -> bool {
456 self.preheating_handle.is_some()
457 }
458 pub async fn get_app_access_token(
459 &self,
460 config: &Config,
461 app_ticket: &str,
462 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
463 ) -> SDKResult<String> {
464 let span = info_span!(
465 "token_get_app",
466 app_id = %config.app_id,
467 app_type = ?config.app_type,
468 cache_hit = tracing::field::Empty,
469 duration_ms = tracing::field::Empty,
470 );
471
472 async move {
473 let start_time = Instant::now();
474 let key = app_access_token_key(&config.app_id);
475
476 {
478 self.metrics
479 .read_lock_acquisitions
480 .fetch_add(1, Ordering::Relaxed);
481 let cache = self.cache.read().await;
482 if let Some(token) = cache.get(&key) {
483 if !token.is_empty() {
484 self.metrics.app_cache_hits.fetch_add(1, Ordering::Relaxed);
485 let current_span = tracing::Span::current();
486 current_span.record("cache_hit", true);
487 current_span.record("duration_ms", start_time.elapsed().as_millis() as u64);
488 log::debug!("App token cache hit in {:?}", start_time.elapsed());
489 return Ok(token);
490 }
491 }
492 }
493
494 tracing::Span::current().record("cache_hit", false);
496 self.metrics
497 .app_cache_misses
498 .fetch_add(1, Ordering::Relaxed);
499
500 self.metrics
502 .write_lock_acquisitions
503 .fetch_add(1, Ordering::Relaxed);
504 let cache = self.cache.write().await;
505
506 if let Some(token) = cache.get(&key) {
508 if !token.is_empty() {
509 self.metrics
511 .app_cache_misses
512 .fetch_sub(1, Ordering::Relaxed);
513 self.metrics.app_cache_hits.fetch_add(1, Ordering::Relaxed);
514 log::debug!("App token double-check hit in {:?}", start_time.elapsed());
515 return Ok(token);
516 }
517 }
518
519 drop(cache); log::debug!("App token cache miss, refreshing token");
522
523 let app_type = config.app_type;
524 let result = if app_type == AppType::SelfBuild {
525 self.get_custom_app_access_token_then_cache(config).await
526 } else {
527 self.get_marketplace_app_access_token_then_cache(
528 config,
529 app_ticket,
530 app_ticket_manager,
531 )
532 .await
533 };
534
535 match &result {
537 Ok(_) => {
538 self.metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
539 log::debug!("App token refresh succeeded in {:?}", start_time.elapsed());
540 }
541 Err(e) => {
542 self.metrics
543 .refresh_failures
544 .fetch_add(1, Ordering::Relaxed);
545 log::warn!(
546 "App token refresh failed in {:?}: {:?}",
547 start_time.elapsed(),
548 e
549 );
550 }
551 }
552
553 tracing::Span::current().record("duration_ms", start_time.elapsed().as_millis() as u64);
554 result
555 }
556 .instrument(span)
557 .await
558 }
559
560 async fn get_custom_app_access_token_then_cache(&self, config: &Config) -> SDKResult<String> {
561 let url = format!("{}{}", config.base_url, APP_ACCESS_TOKEN_INTERNAL_URL_PATH);
562
563 let body = SelfBuiltAppAccessTokenReq {
564 app_id: config.app_id.clone(),
565 app_secret: config.app_secret.clone(),
566 };
567
568 let response = config.http_client.post(&url).json(&body).send().await?;
569
570 let resp: AppAccessTokenResp = response.json().await?;
571 self.handle_app_access_token_response(resp, &config.app_id)
572 .await
573 }
574
575 async fn handle_app_access_token_response(
577 &self,
578 resp: AppAccessTokenResp,
579 app_id: &str,
580 ) -> SDKResult<String> {
581 if resp.raw_response.code == 0 {
582 let expire = resp.expire - EXPIRY_DELTA;
583 {
584 let mut cache = self.cache.write().await;
585 cache.set(
586 &app_access_token_key(app_id),
587 resp.app_access_token.clone(),
588 expire,
589 );
590 }
591 Ok(resp.app_access_token)
592 } else {
593 warn!("app access token response error: {:#?}", resp.raw_response);
594 Err(LarkAPIError::illegal_param(resp.raw_response.msg.clone()))
595 }
596 }
597 async fn get_marketplace_app_access_token_then_cache(
598 &self,
599 config: &Config,
600 app_ticket: &str,
601 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
602 ) -> SDKResult<String> {
603 let mut app_ticket = app_ticket.to_string();
604 if app_ticket.is_empty() {
605 match app_ticket_manager.lock().await.get(config).await {
606 None => return Err(LarkAPIError::illegal_param("App ticket is empty")),
607 Some(ticket) => {
608 app_ticket = ticket;
609 }
610 }
611 }
612
613 let url = format!("{}{}", config.base_url, APP_ACCESS_TOKEN_URL_PATH);
614
615 let body = MarketplaceAppAccessTokenReq {
616 app_id: config.app_id.clone(),
617 app_secret: config.app_secret.clone(),
618 app_ticket,
619 };
620
621 let response = config.http_client.post(&url).json(&body).send().await?;
622
623 let resp: AppAccessTokenResp = response.json().await?;
624 self.handle_app_access_token_response(resp, &config.app_id)
625 .await
626 }
627
628 pub async fn get_tenant_access_token(
629 &self,
630 config: &Config,
631 tenant_key: &str,
632 app_ticket: &str,
633 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
634 ) -> SDKResult<String> {
635 let start_time = Instant::now();
636 let key = tenant_access_token_key(&config.app_id, tenant_key);
637
638 {
640 self.metrics
641 .read_lock_acquisitions
642 .fetch_add(1, Ordering::Relaxed);
643 let cache = self.cache.read().await;
644 if let Some(token) = cache.get(&key) {
645 if !token.is_empty() {
646 self.metrics
647 .tenant_cache_hits
648 .fetch_add(1, Ordering::Relaxed);
649 log::debug!("Tenant token cache hit in {:?}", start_time.elapsed());
650 return Ok(token);
651 }
652 }
653 }
654
655 self.metrics
657 .tenant_cache_misses
658 .fetch_add(1, Ordering::Relaxed);
659
660 self.metrics
662 .write_lock_acquisitions
663 .fetch_add(1, Ordering::Relaxed);
664 let cache = self.cache.write().await;
665
666 if let Some(token) = cache.get(&key) {
668 if !token.is_empty() {
669 self.metrics
671 .tenant_cache_misses
672 .fetch_sub(1, Ordering::Relaxed);
673 self.metrics
674 .tenant_cache_hits
675 .fetch_add(1, Ordering::Relaxed);
676 log::debug!(
677 "Tenant token double-check hit in {:?}",
678 start_time.elapsed()
679 );
680 return Ok(token);
681 }
682 }
683
684 drop(cache); log::debug!("Tenant token cache miss, refreshing token");
687
688 let result = if config.app_type == AppType::SelfBuild {
689 self.get_custom_tenant_access_token_then_cache(config, tenant_key)
690 .await
691 } else {
692 self.get_marketplace_tenant_access_token_then_cache(
693 config,
694 tenant_key,
695 app_ticket,
696 app_ticket_manager,
697 )
698 .await
699 };
700
701 match &result {
703 Ok(_) => {
704 self.metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
705 log::debug!(
706 "Tenant token refresh succeeded in {:?}",
707 start_time.elapsed()
708 );
709 }
710 Err(e) => {
711 self.metrics
712 .refresh_failures
713 .fetch_add(1, Ordering::Relaxed);
714 log::warn!(
715 "Tenant token refresh failed in {:?}: {:?}",
716 start_time.elapsed(),
717 e
718 );
719 }
720 }
721
722 result
723 }
724
725 async fn get_custom_tenant_access_token_then_cache(
726 &self,
727 config: &Config,
728 tenant_key: &str,
729 ) -> SDKResult<String> {
730 let url = format!(
731 "{}{}",
732 config.base_url, TENANT_ACCESS_TOKEN_INTERNAL_URL_PATH
733 );
734
735 let body = SelfBuiltTenantAccessTokenReq {
736 app_id: config.app_id.clone(),
737 app_secret: config.app_secret.clone(),
738 };
739
740 let response = config.http_client.post(&url).json(&body).send().await?;
741
742 let resp: TenantAccessTokenResp = response.json().await?;
743 self.handle_tenant_access_token_response(resp, &config.app_id, tenant_key)
744 .await
745 }
746
747 async fn handle_tenant_access_token_response(
749 &self,
750 resp: TenantAccessTokenResp,
751 app_id: &str,
752 tenant_key: &str,
753 ) -> SDKResult<String> {
754 if resp.raw_response.code == 0 {
755 let expire = resp.expire - EXPIRY_DELTA;
756 {
757 let mut cache = self.cache.write().await;
758 cache.set(
759 &tenant_access_token_key(app_id, tenant_key),
760 resp.tenant_access_token.clone(),
761 expire,
762 );
763 }
764 Ok(resp.tenant_access_token)
765 } else {
766 warn!(
767 "tenant access token response error: {:#?}",
768 resp.raw_response
769 );
770 Err(LarkAPIError::illegal_param(resp.raw_response.msg.clone()))
771 }
772 }
773
774 async fn get_marketplace_tenant_access_token_then_cache(
775 &self,
776 config: &Config,
777 tenant_key: &str,
778 app_ticket: &str,
779 app_ticket_manager: &Arc<Mutex<AppTicketManager>>,
780 ) -> SDKResult<String> {
781 let app_access_token = self
782 .get_marketplace_app_access_token_then_cache(config, app_ticket, app_ticket_manager)
783 .await?;
784
785 let url = format!("{}{}", config.base_url, TENANT_ACCESS_TOKEN_URL_PATH);
786
787 let body = MarketplaceTenantAccessTokenReq {
788 app_access_token,
789 tenant_key: tenant_key.to_string(),
790 };
791
792 let response = config
793 .http_client
794 .post(&url)
795 .json(&body)
796 .header(
797 "Authorization",
798 &format!("Bearer {}", &body.app_access_token),
799 )
800 .send()
801 .await?;
802
803 let resp: TenantAccessTokenResp = response.json().await?;
804 self.handle_tenant_access_token_response(resp, &config.app_id, tenant_key)
805 .await
806 }
807}
808
809fn app_access_token_key(app_id: &str) -> String {
810 format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}")
811}
812
813fn tenant_access_token_key(app_id: &str, tenant_key: &str) -> String {
814 format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}-{tenant_key}")
815}
816
817#[derive(Debug, Serialize, Deserialize)]
818struct SelfBuiltAppAccessTokenReq {
819 app_id: String,
820 app_secret: String,
821}
822
823#[derive(Debug, Serialize, Deserialize)]
824struct SelfBuiltTenantAccessTokenReq {
825 app_id: String,
826 app_secret: String,
827}
828
829#[derive(Serialize, Deserialize, Debug)]
830struct AppAccessTokenResp {
831 #[serde(flatten)]
832 raw_response: RawResponse,
833 expire: i32,
834 app_access_token: String,
835}
836
837impl ApiResponseTrait for AppAccessTokenResp {
838 fn data_format() -> ResponseFormat {
839 ResponseFormat::Flatten
840 }
841}
842
843#[derive(Serialize, Deserialize)]
844struct MarketplaceAppAccessTokenReq {
845 app_id: String,
846 app_secret: String,
847 app_ticket: String,
848}
849
850#[derive(Serialize, Deserialize)]
851struct MarketplaceTenantAccessTokenReq {
852 app_access_token: String,
853 tenant_key: String,
854}
855
856#[derive(Serialize, Deserialize, Debug)]
857struct TenantAccessTokenResp {
858 #[serde(flatten)]
859 raw_response: RawResponse,
860 expire: i32,
861 tenant_access_token: String,
862}
863
864impl ApiResponseTrait for TenantAccessTokenResp {
865 fn data_format() -> ResponseFormat {
866 ResponseFormat::Flatten
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use super::*;
873 use crate::core::{config::Config, constants::AppType};
874 use std::{sync::Arc, time::Duration};
875 use tokio::sync::Mutex;
876
877 #[test]
878 fn test_token_manager_creation() {
879 let manager = TokenManager::new();
880 let _cache = &manager.cache;
883 }
884
885 #[test]
886 fn test_app_access_token_key_generation() {
887 let app_id = "test_app_id";
888 let key = app_access_token_key(app_id);
889 assert_eq!(key, format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}"));
890 }
891
892 #[test]
893 fn test_tenant_access_token_key_generation() {
894 let app_id = "test_app_id";
895 let tenant_key = "test_tenant";
896 let key = tenant_access_token_key(app_id, tenant_key);
897 assert_eq!(
898 key,
899 format!("{APP_ACCESS_TOKEN_KEY_PREFIX}-{app_id}-{tenant_key}")
900 );
901 }
902
903 #[tokio::test]
904 async fn test_cache_miss_returns_empty_string() {
905 let manager = TokenManager::new();
906 let key = "non_existent_key";
907
908 let cache = manager.cache.read().await;
910 let result = cache.get(key).unwrap_or_default();
911 assert_eq!(result, String::new());
912 }
913
914 #[tokio::test]
915 async fn test_get_app_access_token_cache_miss_does_not_error() {
916 let manager = TokenManager::new();
917 let config = Config::builder()
918 .app_id("test_app")
919 .app_secret("test_secret")
920 .app_type(AppType::SelfBuild)
921 .base_url("https://open.feishu.cn")
922 .enable_token_cache(true)
923 .req_timeout(Duration::from_secs(30))
924 .build();
925
926 let app_ticket_manager = Arc::new(Mutex::new(
927 crate::core::app_ticket_manager::AppTicketManager::new(),
928 ));
929
930 let result = manager
933 .get_app_access_token(&config, "", &app_ticket_manager)
934 .await;
935
936 if let Err(error) = result {
938 let error_msg = format!("{error:?}");
939 assert!(
940 !error_msg.contains("cache error"),
941 "应该不再出现'cache error',而是实际的API调用错误: {error_msg}"
942 );
943 }
944 }
945
946 #[test]
947 fn test_token_metrics_creation() {
948 let metrics = TokenMetrics::new();
949
950 assert_eq!(metrics.app_cache_hit_rate(), 0.0);
952 assert_eq!(metrics.tenant_cache_hit_rate(), 0.0);
953 assert_eq!(metrics.refresh_success_rate(), 0.0);
954 }
955
956 #[test]
957 fn test_token_metrics_cache_hit_rate_calculation() {
958 let metrics = TokenMetrics::new();
959
960 metrics.app_cache_hits.store(8, Ordering::Relaxed);
962 metrics.app_cache_misses.store(2, Ordering::Relaxed);
963
964 assert_eq!(metrics.app_cache_hit_rate(), 0.8); metrics.tenant_cache_hits.store(9, Ordering::Relaxed);
968 metrics.tenant_cache_misses.store(1, Ordering::Relaxed);
969
970 assert_eq!(metrics.tenant_cache_hit_rate(), 0.9); }
972
973 #[test]
974 fn test_token_metrics_refresh_success_rate() {
975 let metrics = TokenMetrics::new();
976
977 metrics.refresh_success.store(19, Ordering::Relaxed);
979 metrics.refresh_failures.store(1, Ordering::Relaxed);
980
981 assert_eq!(metrics.refresh_success_rate(), 0.95); }
983
984 #[test]
985 fn test_token_metrics_performance_report() {
986 let metrics = TokenMetrics::new();
987
988 metrics.app_cache_hits.store(80, Ordering::Relaxed);
990 metrics.app_cache_misses.store(20, Ordering::Relaxed);
991 metrics.refresh_success.store(95, Ordering::Relaxed);
992 metrics.refresh_failures.store(5, Ordering::Relaxed);
993
994 let report = metrics.performance_report();
995
996 assert!(report.contains("80.00%")); assert!(report.contains("95.00%")); assert!(report.contains("80 hits, 20 misses")); }
1001
1002 #[tokio::test]
1003 async fn test_token_manager_metrics_integration() {
1004 let manager = TokenManager::new();
1005
1006 let metrics = manager.metrics();
1008 assert_eq!(metrics.read_lock_acquisitions.load(Ordering::Relaxed), 0);
1009
1010 manager.log_performance_metrics(); }
1013
1014 #[tokio::test]
1015 async fn test_preheating_config_default_values() {
1016 let config = PreheatingConfig::default();
1017 assert_eq!(config.check_interval_seconds, 1800); assert_eq!(config.preheat_threshold_seconds, 900); assert!(config.enable_tenant_preheating);
1020 assert_eq!(config.max_concurrent_preheat, 3);
1021 }
1022
1023 #[tokio::test]
1024 async fn test_should_preheat_token_with_custom_threshold() {
1025 let manager = TokenManager::new();
1026 let key = "test_token_key";
1027
1028 assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1030
1031 {
1033 let mut cache = manager.cache.write().await;
1034 cache.set(key, "test_token_value".to_string(), 3600); }
1036
1037 assert!(!TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await);
1039
1040 assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 3700).await);
1042 }
1043
1044 #[tokio::test]
1045 async fn test_get_cached_tenant_keys() {
1046 let manager = TokenManager::new();
1047
1048 let tenant_keys = TokenManager::get_cached_tenant_keys(&manager.cache, "test_app").await;
1050 assert!(tenant_keys.is_empty());
1051 }
1052
1053 #[test]
1054 fn test_cache_entry_expiry_calculations() {
1055 use crate::core::cache::CacheEntry;
1056 use std::time::Duration;
1057 use tokio::time::Instant;
1058
1059 let now = Instant::now();
1060 let expires_in_10_mins = now + Duration::from_secs(600);
1061
1062 let entry = CacheEntry {
1063 value: "test_value".to_string(),
1064 expires_at: expires_in_10_mins,
1065 current_time: now,
1066 };
1067
1068 assert_eq!(entry.expiry_seconds(), 600);
1070
1071 assert!(entry.expires_within(700)); assert!(!entry.expires_within(500)); }
1075
1076 #[test]
1077 fn test_preheating_config_custom_values() {
1078 let config = PreheatingConfig {
1079 check_interval_seconds: 900,
1080 preheat_threshold_seconds: 300,
1081 enable_tenant_preheating: false,
1082 max_concurrent_preheat: 5,
1083 };
1084
1085 assert_eq!(config.check_interval_seconds, 900);
1086 assert_eq!(config.preheat_threshold_seconds, 300);
1087 assert!(!config.enable_tenant_preheating);
1088 assert_eq!(config.max_concurrent_preheat, 5);
1089 }
1090
1091 #[test]
1092 fn test_preheating_config_clone() {
1093 let config = PreheatingConfig::default();
1094 let cloned = config.clone();
1095
1096 assert_eq!(config.check_interval_seconds, cloned.check_interval_seconds);
1097 assert_eq!(
1098 config.preheat_threshold_seconds,
1099 cloned.preheat_threshold_seconds
1100 );
1101 assert_eq!(
1102 config.enable_tenant_preheating,
1103 cloned.enable_tenant_preheating
1104 );
1105 assert_eq!(config.max_concurrent_preheat, cloned.max_concurrent_preheat);
1106 }
1107
1108 #[test]
1109 fn test_preheating_config_debug() {
1110 let config = PreheatingConfig::default();
1111 let debug_str = format!("{:?}", config);
1112
1113 assert!(debug_str.contains("PreheatingConfig"));
1114 assert!(debug_str.contains("1800"));
1115 assert!(debug_str.contains("900"));
1116 }
1117
1118 #[test]
1119 fn test_token_metrics_zero_division_safety() {
1120 let metrics = TokenMetrics::new();
1121
1122 assert_eq!(metrics.app_cache_hit_rate(), 0.0);
1124 assert_eq!(metrics.tenant_cache_hit_rate(), 0.0);
1125 assert_eq!(metrics.refresh_success_rate(), 0.0);
1126 }
1127
1128 #[test]
1129 fn test_token_metrics_edge_cases() {
1130 let metrics = TokenMetrics::new();
1131
1132 metrics.app_cache_hits.store(100, Ordering::Relaxed);
1134 assert_eq!(metrics.app_cache_hit_rate(), 1.0);
1135
1136 metrics.app_cache_hits.store(0, Ordering::Relaxed);
1138 metrics.app_cache_misses.store(50, Ordering::Relaxed);
1139 assert_eq!(metrics.app_cache_hit_rate(), 0.0);
1140 }
1141
1142 #[test]
1143 fn test_token_metrics_debug() {
1144 let metrics = TokenMetrics::new();
1145 let debug_str = format!("{:?}", metrics);
1146
1147 assert!(debug_str.contains("TokenMetrics"));
1148 assert!(debug_str.contains("app_cache_hits"));
1149 assert!(debug_str.contains("refresh_success"));
1150 }
1151
1152 #[test]
1153 fn test_token_metrics_all_rates_with_data() {
1154 let metrics = TokenMetrics::new();
1155
1156 metrics.app_cache_hits.store(75, Ordering::Relaxed);
1158 metrics.app_cache_misses.store(25, Ordering::Relaxed);
1159 metrics.tenant_cache_hits.store(60, Ordering::Relaxed);
1160 metrics.tenant_cache_misses.store(40, Ordering::Relaxed);
1161 metrics.refresh_success.store(90, Ordering::Relaxed);
1162 metrics.refresh_failures.store(10, Ordering::Relaxed);
1163 metrics.read_lock_acquisitions.store(200, Ordering::Relaxed);
1164 metrics.write_lock_acquisitions.store(50, Ordering::Relaxed);
1165
1166 assert_eq!(metrics.app_cache_hit_rate(), 0.75);
1167 assert_eq!(metrics.tenant_cache_hit_rate(), 0.6);
1168 assert_eq!(metrics.refresh_success_rate(), 0.9);
1169 assert_eq!(metrics.read_lock_acquisitions.load(Ordering::Relaxed), 200);
1170 assert_eq!(metrics.write_lock_acquisitions.load(Ordering::Relaxed), 50);
1171 }
1172
1173 #[test]
1174 fn test_token_request_structs_serialization() {
1175 let self_built_req = SelfBuiltAppAccessTokenReq {
1176 app_id: "test_app".to_string(),
1177 app_secret: "test_secret".to_string(),
1178 };
1179
1180 let json = serde_json::to_string(&self_built_req).unwrap();
1181 assert!(json.contains("test_app"));
1182 assert!(json.contains("test_secret"));
1183
1184 let deserialized: SelfBuiltAppAccessTokenReq = serde_json::from_str(&json).unwrap();
1185 assert_eq!(deserialized.app_id, "test_app");
1186 assert_eq!(deserialized.app_secret, "test_secret");
1187 }
1188
1189 #[test]
1190 fn test_marketplace_token_request_serialization() {
1191 let marketplace_req = MarketplaceAppAccessTokenReq {
1192 app_id: "marketplace_app".to_string(),
1193 app_secret: "marketplace_secret".to_string(),
1194 app_ticket: "test_ticket".to_string(),
1195 };
1196
1197 let json = serde_json::to_string(&marketplace_req).unwrap();
1198 assert!(json.contains("marketplace_app"));
1199 assert!(json.contains("marketplace_secret"));
1200 assert!(json.contains("test_ticket"));
1201
1202 let deserialized: MarketplaceAppAccessTokenReq = serde_json::from_str(&json).unwrap();
1203 assert_eq!(deserialized.app_id, "marketplace_app");
1204 assert_eq!(deserialized.app_ticket, "test_ticket");
1205 }
1206
1207 #[test]
1208 fn test_tenant_token_request_serialization() {
1209 let tenant_req = MarketplaceTenantAccessTokenReq {
1210 app_access_token: "app_token_123".to_string(),
1211 tenant_key: "tenant_abc".to_string(),
1212 };
1213
1214 let json = serde_json::to_string(&tenant_req).unwrap();
1215 assert!(json.contains("app_token_123"));
1216 assert!(json.contains("tenant_abc"));
1217
1218 let deserialized: MarketplaceTenantAccessTokenReq = serde_json::from_str(&json).unwrap();
1219 assert_eq!(deserialized.app_access_token, "app_token_123");
1220 assert_eq!(deserialized.tenant_key, "tenant_abc");
1221 }
1222
1223 #[test]
1224 fn test_app_access_token_response() {
1225 use crate::core::api_resp::{RawResponse, ResponseFormat};
1226
1227 assert!(matches!(
1229 AppAccessTokenResp::data_format(),
1230 ResponseFormat::Flatten
1231 ));
1232
1233 let raw_resp = RawResponse {
1235 code: 0,
1236 msg: "success".to_string(),
1237 err: None,
1238 };
1239
1240 let resp = AppAccessTokenResp {
1241 raw_response: raw_resp,
1242 expire: 3600,
1243 app_access_token: "test_token_123".to_string(),
1244 };
1245
1246 assert_eq!(resp.expire, 3600);
1247 assert_eq!(resp.app_access_token, "test_token_123");
1248 assert_eq!(resp.raw_response.code, 0);
1249 }
1250
1251 #[test]
1252 fn test_tenant_access_token_response() {
1253 use crate::core::api_resp::{RawResponse, ResponseFormat};
1254
1255 assert!(matches!(
1257 TenantAccessTokenResp::data_format(),
1258 ResponseFormat::Flatten
1259 ));
1260
1261 let raw_resp = RawResponse {
1263 code: 0,
1264 msg: "success".to_string(),
1265 err: None,
1266 };
1267
1268 let resp = TenantAccessTokenResp {
1269 raw_response: raw_resp,
1270 expire: 7200,
1271 tenant_access_token: "tenant_token_456".to_string(),
1272 };
1273
1274 assert_eq!(resp.expire, 7200);
1275 assert_eq!(resp.tenant_access_token, "tenant_token_456");
1276 assert_eq!(resp.raw_response.code, 0);
1277 }
1278
1279 #[tokio::test]
1280 async fn test_token_manager_stop_preheating() {
1281 let mut manager = TokenManager::new();
1282
1283 assert!(!manager.is_preheating_active());
1285
1286 manager.stop_background_preheating();
1288 assert!(!manager.is_preheating_active());
1289 }
1290
1291 #[tokio::test]
1292 async fn test_token_manager_concurrent_access() {
1293 let manager = Arc::new(TokenManager::new());
1294 let manager_clone = manager.clone();
1295
1296 let handle1 = tokio::spawn(async move {
1298 let cache = manager_clone.cache.read().await;
1299 cache.get("test_key")
1300 });
1301
1302 let handle2 = tokio::spawn(async move {
1303 let cache = manager.cache.read().await;
1304 cache.get("another_key")
1305 });
1306
1307 let (result1, result2) = tokio::join!(handle1, handle2);
1308 assert!(result1.is_ok());
1309 assert!(result2.is_ok());
1310 }
1311
1312 #[test]
1313 fn test_key_generation_with_special_characters() {
1314 let app_id_with_special = "app-id_with.special@chars";
1315 let tenant_with_special = "tenant.key-with_special@chars";
1316
1317 let app_key = app_access_token_key(app_id_with_special);
1318 let tenant_key = tenant_access_token_key(app_id_with_special, tenant_with_special);
1319
1320 assert!(app_key.contains(app_id_with_special));
1321 assert!(tenant_key.contains(app_id_with_special));
1322 assert!(tenant_key.contains(tenant_with_special));
1323 }
1324
1325 #[test]
1326 fn test_key_generation_with_unicode() {
1327 let app_id = "应用标识符";
1328 let tenant_key = "租户标识符";
1329
1330 let app_key = app_access_token_key(app_id);
1331 let tenant_access_key = tenant_access_token_key(app_id, tenant_key);
1332
1333 assert!(app_key.contains(app_id));
1334 assert!(tenant_access_key.contains(app_id));
1335 assert!(tenant_access_key.contains(tenant_key));
1336 }
1337
1338 #[tokio::test]
1339 async fn test_token_metrics_atomic_operations() {
1340 let metrics = Arc::new(TokenMetrics::new());
1341 let metrics_clone = metrics.clone();
1342
1343 let handle = tokio::spawn(async move {
1345 for _ in 0..100 {
1346 metrics_clone.app_cache_hits.fetch_add(1, Ordering::Relaxed);
1347 metrics_clone
1348 .app_cache_misses
1349 .fetch_add(1, Ordering::Relaxed);
1350 }
1351 });
1352
1353 for _ in 0..50 {
1354 metrics.refresh_success.fetch_add(1, Ordering::Relaxed);
1355 metrics.refresh_failures.fetch_add(1, Ordering::Relaxed);
1356 }
1357
1358 handle.await.unwrap();
1359
1360 assert_eq!(metrics.app_cache_hits.load(Ordering::Relaxed), 100);
1361 assert_eq!(metrics.app_cache_misses.load(Ordering::Relaxed), 100);
1362 assert_eq!(metrics.refresh_success.load(Ordering::Relaxed), 50);
1363 assert_eq!(metrics.refresh_failures.load(Ordering::Relaxed), 50);
1364 }
1365
1366 #[test]
1367 fn test_self_built_tenant_access_token_req() {
1368 let req = SelfBuiltTenantAccessTokenReq {
1369 app_id: "self_built_app".to_string(),
1370 app_secret: "self_built_secret".to_string(),
1371 };
1372
1373 let json = serde_json::to_string(&req).unwrap();
1374 assert!(json.contains("self_built_app"));
1375 assert!(json.contains("self_built_secret"));
1376
1377 let deserialized: SelfBuiltTenantAccessTokenReq = serde_json::from_str(&json).unwrap();
1378 assert_eq!(deserialized.app_id, "self_built_app");
1379 assert_eq!(deserialized.app_secret, "self_built_secret");
1380 }
1381
1382 #[test]
1383 fn test_token_response_debug() {
1384 use crate::core::api_resp::RawResponse;
1385
1386 let raw_resp = RawResponse {
1387 code: 0,
1388 msg: "success".to_string(),
1389 err: None,
1390 };
1391
1392 let app_resp = AppAccessTokenResp {
1393 raw_response: raw_resp.clone(),
1394 expire: 3600,
1395 app_access_token: "debug_token".to_string(),
1396 };
1397
1398 let debug_str = format!("{:?}", app_resp);
1399 assert!(debug_str.contains("AppAccessTokenResp"));
1400 assert!(debug_str.contains("debug_token"));
1401 assert!(debug_str.contains("3600"));
1402
1403 let tenant_resp = TenantAccessTokenResp {
1404 raw_response: raw_resp,
1405 expire: 7200,
1406 tenant_access_token: "tenant_debug_token".to_string(),
1407 };
1408
1409 let debug_str = format!("{:?}", tenant_resp);
1410 assert!(debug_str.contains("TenantAccessTokenResp"));
1411 assert!(debug_str.contains("tenant_debug_token"));
1412 assert!(debug_str.contains("7200"));
1413 }
1414
1415 #[test]
1416 fn test_token_manager_memory_efficiency() {
1417 let managers: Vec<TokenManager> = (0..50).map(|_| TokenManager::new()).collect();
1419
1420 assert_eq!(managers.len(), 50);
1421
1422 for manager in &managers {
1424 assert!(manager.cache.try_read().is_ok());
1425 }
1426 }
1427
1428 #[test]
1429 fn test_token_manager_default_implementation() {
1430 let manager1 = TokenManager::new();
1431 let manager2 = TokenManager::default();
1432
1433 assert!(!manager1.is_preheating_active());
1435 assert!(!manager2.is_preheating_active());
1436
1437 assert!(manager1.metrics().app_cache_hit_rate() == manager2.metrics().app_cache_hit_rate());
1439 }
1440
1441 #[test]
1442 fn test_token_manager_get_cache_and_metrics() {
1443 let manager = TokenManager::new();
1444
1445 let cache_ref = manager.get_cache();
1447 assert!(cache_ref.try_read().is_ok());
1448
1449 let metrics_ref = manager.get_metrics();
1451 assert_eq!(metrics_ref.app_cache_hit_rate(), 0.0);
1452 }
1453
1454 #[tokio::test]
1455 async fn test_handle_app_access_token_response_success() {
1456 let manager = TokenManager::new();
1457
1458 let successful_resp = AppAccessTokenResp {
1459 raw_response: crate::core::api_resp::RawResponse {
1460 code: 0,
1461 msg: "success".to_string(),
1462 err: None,
1463 },
1464 expire: 3600,
1465 app_access_token: "test_success_token".to_string(),
1466 };
1467
1468 let result = manager
1469 .handle_app_access_token_response(successful_resp, "test_app")
1470 .await;
1471 assert!(result.is_ok());
1472 assert_eq!(result.unwrap(), "test_success_token");
1473
1474 let cache = manager.cache.read().await;
1476 let cached_token = cache.get(&app_access_token_key("test_app"));
1477 assert_eq!(cached_token, Some("test_success_token".to_string()));
1478 }
1479
1480 #[tokio::test]
1481 async fn test_handle_app_access_token_response_error() {
1482 let manager = TokenManager::new();
1483
1484 let error_resp = AppAccessTokenResp {
1485 raw_response: crate::core::api_resp::RawResponse {
1486 code: 40001,
1487 msg: "invalid app_id".to_string(),
1488 err: None,
1489 },
1490 expire: 0,
1491 app_access_token: "".to_string(),
1492 };
1493
1494 let result = manager
1495 .handle_app_access_token_response(error_resp, "invalid_app")
1496 .await;
1497 assert!(result.is_err());
1498
1499 let error_msg = format!("{:?}", result.unwrap_err());
1501 assert!(error_msg.contains("invalid app_id"));
1502 }
1503
1504 #[tokio::test]
1505 async fn test_handle_tenant_access_token_response_success() {
1506 let manager = TokenManager::new();
1507
1508 let successful_resp = TenantAccessTokenResp {
1509 raw_response: crate::core::api_resp::RawResponse {
1510 code: 0,
1511 msg: "success".to_string(),
1512 err: None,
1513 },
1514 expire: 7200,
1515 tenant_access_token: "test_tenant_token".to_string(),
1516 };
1517
1518 let result = manager
1519 .handle_tenant_access_token_response(successful_resp, "test_app", "test_tenant")
1520 .await;
1521 assert!(result.is_ok());
1522 assert_eq!(result.unwrap(), "test_tenant_token");
1523
1524 let cache = manager.cache.read().await;
1526 let cached_token = cache.get(&tenant_access_token_key("test_app", "test_tenant"));
1527 assert_eq!(cached_token, Some("test_tenant_token".to_string()));
1528 }
1529
1530 #[tokio::test]
1531 async fn test_handle_tenant_access_token_response_error() {
1532 let manager = TokenManager::new();
1533
1534 let error_resp = TenantAccessTokenResp {
1535 raw_response: crate::core::api_resp::RawResponse {
1536 code: 40002,
1537 msg: "invalid tenant_key".to_string(),
1538 err: None,
1539 },
1540 expire: 0,
1541 tenant_access_token: "".to_string(),
1542 };
1543
1544 let result = manager
1545 .handle_tenant_access_token_response(error_resp, "test_app", "invalid_tenant")
1546 .await;
1547 assert!(result.is_err());
1548
1549 let error_msg = format!("{:?}", result.unwrap_err());
1551 assert!(error_msg.contains("invalid tenant_key"));
1552 }
1553
1554 #[tokio::test]
1555 async fn test_tenant_token_double_check_hit() {
1556 let manager = Arc::new(TokenManager::new());
1557 let config = Config::builder()
1558 .app_id("test_app")
1559 .app_secret("test_secret")
1560 .app_type(AppType::SelfBuild)
1561 .base_url("https://open.feishu.cn")
1562 .build();
1563
1564 let app_ticket_manager = Arc::new(Mutex::new(
1565 crate::core::app_ticket_manager::AppTicketManager::new(),
1566 ));
1567
1568 {
1570 let mut cache = manager.cache.write().await;
1571 cache.set(
1572 &tenant_access_token_key("test_app", "test_tenant"),
1573 "cached_token".to_string(),
1574 3600,
1575 );
1576 }
1577
1578 let manager_clone = manager.clone();
1580 let config_clone = config.clone();
1581 let app_ticket_manager_clone = app_ticket_manager.clone();
1582
1583 let handle1 = tokio::spawn(async move {
1584 manager_clone
1585 .get_tenant_access_token(
1586 &config_clone,
1587 "test_tenant",
1588 "",
1589 &app_ticket_manager_clone,
1590 )
1591 .await
1592 });
1593
1594 let handle2 = tokio::spawn(async move {
1595 manager
1596 .get_tenant_access_token(&config, "test_tenant", "", &app_ticket_manager)
1597 .await
1598 });
1599
1600 let (result1, result2) = tokio::join!(handle1, handle2);
1601
1602 if let (Ok(Ok(token1)), Ok(Ok(token2))) = (result1, result2) {
1604 assert_eq!(token1, "cached_token");
1605 assert_eq!(token2, "cached_token");
1606 }
1607 }
1608
1609 #[tokio::test]
1610 async fn test_app_token_double_check_hit() {
1611 let manager = Arc::new(TokenManager::new());
1612 let config = Config::builder()
1613 .app_id("test_app")
1614 .app_secret("test_secret")
1615 .app_type(AppType::SelfBuild)
1616 .base_url("https://open.feishu.cn")
1617 .build();
1618
1619 let app_ticket_manager = Arc::new(Mutex::new(
1620 crate::core::app_ticket_manager::AppTicketManager::new(),
1621 ));
1622
1623 {
1625 let mut cache = manager.cache.write().await;
1626 cache.set(
1627 &app_access_token_key("test_app"),
1628 "cached_app_token".to_string(),
1629 3600,
1630 );
1631 }
1632
1633 let manager_clone = manager.clone();
1635 let config_clone = config.clone();
1636 let app_ticket_manager_clone = app_ticket_manager.clone();
1637
1638 let handle1 = tokio::spawn(async move {
1639 manager_clone
1640 .get_app_access_token(&config_clone, "", &app_ticket_manager_clone)
1641 .await
1642 });
1643
1644 let handle2 = tokio::spawn(async move {
1645 manager
1646 .get_app_access_token(&config, "", &app_ticket_manager)
1647 .await
1648 });
1649
1650 let (result1, result2) = tokio::join!(handle1, handle2);
1651
1652 if let (Ok(Ok(token1)), Ok(Ok(token2))) = (result1, result2) {
1654 assert_eq!(token1, "cached_app_token");
1655 assert_eq!(token2, "cached_app_token");
1656 }
1657 }
1658
1659 #[tokio::test]
1660 async fn test_metrics_record_correctly_during_operations() {
1661 let manager = TokenManager::new();
1662 let config = Config::builder()
1663 .app_id("metrics_test_app")
1664 .app_secret("test_secret")
1665 .build();
1666
1667 let app_ticket_manager = Arc::new(Mutex::new(
1668 crate::core::app_ticket_manager::AppTicketManager::new(),
1669 ));
1670
1671 assert_eq!(manager.metrics().app_cache_hits.load(Ordering::Relaxed), 0);
1673 assert_eq!(
1674 manager.metrics().app_cache_misses.load(Ordering::Relaxed),
1675 0
1676 );
1677
1678 let _result = manager
1680 .get_app_access_token(&config, "", &app_ticket_manager)
1681 .await;
1682
1683 assert!(manager.metrics().app_cache_misses.load(Ordering::Relaxed) > 0);
1685 }
1686
1687 #[tokio::test]
1688 async fn test_empty_string_token_handling() {
1689 let manager = TokenManager::new();
1690
1691 {
1693 let mut cache = manager.cache.write().await;
1694 cache.set(&app_access_token_key("empty_app"), "".to_string(), 3600);
1695 }
1696
1697 let config = Config::builder()
1698 .app_id("empty_app")
1699 .app_secret("test_secret")
1700 .build();
1701
1702 let app_ticket_manager = Arc::new(Mutex::new(
1703 crate::core::app_ticket_manager::AppTicketManager::new(),
1704 ));
1705
1706 let _result = manager
1708 .get_app_access_token(&config, "", &app_ticket_manager)
1709 .await;
1710
1711 assert!(manager.metrics().app_cache_misses.load(Ordering::Relaxed) > 0);
1713 }
1714
1715 #[tokio::test]
1716 async fn test_concurrent_cache_access_with_writes() {
1717 let manager = Arc::new(TokenManager::new());
1718 let mut handles = vec![];
1719
1720 for i in 0..10 {
1722 let manager_clone = manager.clone();
1723 let handle = tokio::spawn(async move {
1724 let cache = manager_clone.cache.read().await;
1725 cache.get(&format!("test_key_{}", i))
1726 });
1727 handles.push(handle);
1728 }
1729
1730 let manager_write = manager.clone();
1732 let write_handle = tokio::spawn(async move {
1733 let mut cache = manager_write.cache.write().await;
1734 cache.set("write_key", "write_value".to_string(), 3600);
1735 });
1736
1737 for handle in handles {
1739 assert!(handle.await.is_ok());
1740 }
1741 assert!(write_handle.await.is_ok());
1742
1743 let cache = manager.cache.read().await;
1745 assert_eq!(cache.get("write_key"), Some("write_value".to_string()));
1746 }
1747
1748 #[tokio::test]
1749 async fn test_preheat_app_token_direct() {
1750 let manager = TokenManager::new();
1751 let cache = manager.get_cache();
1752 let config = Config::builder()
1753 .app_id("preheat_app")
1754 .app_secret("preheat_secret")
1755 .app_type(AppType::SelfBuild)
1756 .base_url("https://open.feishu.cn")
1757 .build();
1758
1759 let app_ticket_manager = Arc::new(Mutex::new(
1760 crate::core::app_ticket_manager::AppTicketManager::new(),
1761 ));
1762
1763 let result = TokenManager::preheat_app_token(&cache, &config, &app_ticket_manager).await;
1765
1766 if let Err(error) = result {
1768 let error_msg = format!("{:?}", error);
1769 assert!(!error_msg.contains("cache error"));
1770 }
1771 }
1772
1773 #[tokio::test]
1774 async fn test_preheat_tenant_token_direct() {
1775 let manager = TokenManager::new();
1776 let cache = manager.get_cache();
1777 let config = Config::builder()
1778 .app_id("preheat_app")
1779 .app_secret("preheat_secret")
1780 .app_type(AppType::SelfBuild)
1781 .base_url("https://open.feishu.cn")
1782 .build();
1783
1784 let app_ticket_manager = Arc::new(Mutex::new(
1785 crate::core::app_ticket_manager::AppTicketManager::new(),
1786 ));
1787
1788 let result =
1790 TokenManager::preheat_tenant_token(&cache, &config, "test_tenant", &app_ticket_manager)
1791 .await;
1792
1793 if let Err(error) = result {
1795 let error_msg = format!("{:?}", error);
1796 assert!(!error_msg.contains("cache error"));
1797 }
1798 }
1799
1800 #[tokio::test]
1801 async fn test_should_preheat_token_with_different_thresholds() {
1802 let manager = TokenManager::new();
1803 let key = "threshold_test_key";
1804
1805 {
1807 let mut cache = manager.cache.write().await;
1808 cache.set(key, "test_value".to_string(), 3600);
1809 }
1810
1811 assert!(!TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 600).await); assert!(
1814 !TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 1800).await
1815 ); assert!(
1817 !TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 3000).await
1818 ); assert!(TokenManager::should_preheat_token_with_threshold(&manager.cache, key, 3700).await);
1820 }
1822
1823 #[tokio::test]
1824 async fn test_should_preheat_token_default_threshold() {
1825 let manager = TokenManager::new();
1826 let key = "default_threshold_key";
1827
1828 assert!(TokenManager::should_preheat_token(&manager.cache, key).await);
1830
1831 {
1833 let mut cache = manager.cache.write().await;
1834 cache.set(key, "test_value".to_string(), 1200);
1835 }
1836
1837 assert!(!TokenManager::should_preheat_token(&manager.cache, key).await);
1839
1840 {
1842 let mut cache = manager.cache.write().await;
1843 cache.set(key, "test_value2".to_string(), 600);
1844 }
1845
1846 assert!(TokenManager::should_preheat_token(&manager.cache, key).await);
1848 }
1849
1850 #[tokio::test]
1851 async fn test_preheat_tokens_if_needed_default() {
1852 let manager = TokenManager::new();
1853 let cache = manager.get_cache();
1854 let metrics = manager.get_metrics();
1855 let config = Config::builder()
1856 .app_id("preheat_test_app")
1857 .app_secret("test_secret")
1858 .app_type(AppType::SelfBuild)
1859 .base_url("https://open.feishu.cn")
1860 .build();
1861
1862 let app_ticket_manager = Arc::new(Mutex::new(
1863 crate::core::app_ticket_manager::AppTicketManager::new(),
1864 ));
1865
1866 let result =
1868 TokenManager::preheat_tokens_if_needed(&cache, &metrics, &config, &app_ticket_manager)
1869 .await;
1870
1871 match result {
1874 Ok(_) => {
1875 println!("预热检查完成,无需预热或预热成功");
1877 }
1878 Err(e) => {
1879 println!("预热过程中发生网络/API错误(预期): {:?}", e);
1881 }
1882 }
1883 }
1884
1885 #[tokio::test]
1886 async fn test_preheat_tokens_with_custom_config() {
1887 let manager = TokenManager::new();
1888 let cache = manager.get_cache();
1889 let metrics = manager.get_metrics();
1890 let config = Config::builder()
1891 .app_id("custom_preheat_app")
1892 .app_secret("test_secret")
1893 .app_type(AppType::SelfBuild)
1894 .base_url("https://open.feishu.cn")
1895 .build();
1896
1897 let app_ticket_manager = Arc::new(Mutex::new(
1898 crate::core::app_ticket_manager::AppTicketManager::new(),
1899 ));
1900
1901 let custom_config = PreheatingConfig {
1902 check_interval_seconds: 300, preheat_threshold_seconds: 600, enable_tenant_preheating: false, max_concurrent_preheat: 1,
1906 };
1907
1908 let result = TokenManager::preheat_tokens_if_needed_with_config(
1910 &cache,
1911 &metrics,
1912 &config,
1913 &app_ticket_manager,
1914 &custom_config,
1915 )
1916 .await;
1917
1918 match result {
1920 Ok(_) => {
1921 println!("自定义配置预热检查完成");
1922 }
1923 Err(e) => {
1924 println!("自定义配置预热过程中发生错误(可能是网络错误): {:?}", e);
1925 }
1926 }
1927 }
1928
1929 #[tokio::test]
1930 async fn test_start_background_preheating_basic() {
1931 let mut manager = TokenManager::new();
1932 let config = Config::builder()
1933 .app_id("background_app")
1934 .app_secret("test_secret")
1935 .build();
1936
1937 let app_ticket_manager = Arc::new(Mutex::new(
1938 crate::core::app_ticket_manager::AppTicketManager::new(),
1939 ));
1940
1941 manager.start_background_preheating(config, app_ticket_manager);
1943
1944 assert!(manager.is_preheating_active());
1946
1947 manager.stop_background_preheating();
1949 assert!(!manager.is_preheating_active());
1950 }
1951
1952 #[tokio::test]
1953 async fn test_start_background_preheating_with_custom_config() {
1954 let mut manager = TokenManager::new();
1955 let config = Config::builder()
1956 .app_id("custom_background_app")
1957 .app_secret("test_secret")
1958 .build();
1959
1960 let app_ticket_manager = Arc::new(Mutex::new(
1961 crate::core::app_ticket_manager::AppTicketManager::new(),
1962 ));
1963
1964 let custom_config = PreheatingConfig {
1965 check_interval_seconds: 120, preheat_threshold_seconds: 300, enable_tenant_preheating: true,
1968 max_concurrent_preheat: 2,
1969 };
1970
1971 manager.start_background_preheating_with_config(config, app_ticket_manager, custom_config);
1973
1974 assert!(manager.is_preheating_active());
1976
1977 manager.stop_background_preheating();
1979 assert!(!manager.is_preheating_active());
1980 }
1981
1982 #[tokio::test]
1983 async fn test_restart_background_preheating() {
1984 let mut manager = TokenManager::new();
1985 let config = Config::builder()
1986 .app_id("restart_app")
1987 .app_secret("test_secret")
1988 .build();
1989
1990 let app_ticket_manager = Arc::new(Mutex::new(
1991 crate::core::app_ticket_manager::AppTicketManager::new(),
1992 ));
1993
1994 manager.start_background_preheating(config.clone(), app_ticket_manager.clone());
1996 assert!(manager.is_preheating_active());
1997
1998 let new_config = PreheatingConfig {
2000 check_interval_seconds: 60,
2001 preheat_threshold_seconds: 180,
2002 enable_tenant_preheating: false,
2003 max_concurrent_preheat: 1,
2004 };
2005
2006 manager.start_background_preheating_with_config(config, app_ticket_manager, new_config);
2007 assert!(manager.is_preheating_active());
2008
2009 manager.stop_background_preheating();
2011 assert!(!manager.is_preheating_active());
2012 }
2013
2014 #[test]
2015 fn test_token_metrics_performance_report_format() {
2016 let metrics = TokenMetrics::new();
2017
2018 metrics.app_cache_hits.store(85, Ordering::Relaxed);
2020 metrics.app_cache_misses.store(15, Ordering::Relaxed);
2021 metrics.tenant_cache_hits.store(70, Ordering::Relaxed);
2022 metrics.tenant_cache_misses.store(30, Ordering::Relaxed);
2023 metrics.refresh_success.store(92, Ordering::Relaxed);
2024 metrics.refresh_failures.store(8, Ordering::Relaxed);
2025 metrics.read_lock_acquisitions.store(500, Ordering::Relaxed);
2026 metrics
2027 .write_lock_acquisitions
2028 .store(100, Ordering::Relaxed);
2029
2030 let report = metrics.performance_report();
2031
2032 assert!(report.contains("TokenManager Performance Metrics"));
2034 assert!(report.contains("85.00%")); assert!(report.contains("70.00%")); assert!(report.contains("92.00%")); assert!(report.contains("500")); assert!(report.contains("100")); assert!(report.contains("85 hits, 15 misses")); assert!(report.contains("70 hits, 30 misses")); assert!(report.contains("92 success, 8 failures")); }
2043
2044 #[test]
2045 fn test_request_structs_debug_trait() {
2046 let self_built_req = SelfBuiltAppAccessTokenReq {
2047 app_id: "debug_app".to_string(),
2048 app_secret: "debug_secret".to_string(),
2049 };
2050
2051 let debug_str = format!("{:?}", self_built_req);
2052 assert!(debug_str.contains("SelfBuiltAppAccessTokenReq"));
2053 assert!(debug_str.contains("debug_app"));
2054
2055 let tenant_req = SelfBuiltTenantAccessTokenReq {
2056 app_id: "tenant_debug_app".to_string(),
2057 app_secret: "tenant_debug_secret".to_string(),
2058 };
2059
2060 let debug_str = format!("{:?}", tenant_req);
2061 assert!(debug_str.contains("SelfBuiltTenantAccessTokenReq"));
2062 assert!(debug_str.contains("tenant_debug_app"));
2063 }
2064
2065 #[tokio::test]
2066 async fn test_marketplace_vs_selfbuild_app_type_handling() {
2067 let manager = TokenManager::new();
2068
2069 let self_build_config = Config::builder()
2071 .app_id("self_build_app")
2072 .app_secret("self_build_secret")
2073 .app_type(AppType::SelfBuild)
2074 .base_url("https://open.feishu.cn")
2075 .build();
2076
2077 let marketplace_config = Config::builder()
2079 .app_id("marketplace_app")
2080 .app_secret("marketplace_secret")
2081 .app_type(AppType::Marketplace)
2082 .base_url("https://open.feishu.cn")
2083 .build();
2084
2085 let app_ticket_manager = Arc::new(Mutex::new(
2086 crate::core::app_ticket_manager::AppTicketManager::new(),
2087 ));
2088
2089 let self_build_result = manager
2091 .get_app_access_token(&self_build_config, "", &app_ticket_manager)
2092 .await;
2093 let marketplace_result = manager
2094 .get_app_access_token(&marketplace_config, "test_ticket", &app_ticket_manager)
2095 .await;
2096
2097 if let Err(e) = self_build_result {
2099 let error_msg = format!("{:?}", e);
2100 assert!(!error_msg.contains("config error"));
2101 }
2102
2103 if let Err(e) = marketplace_result {
2104 let error_msg = format!("{:?}", e);
2105 assert!(!error_msg.contains("config error"));
2106 }
2107 }
2108
2109 #[tokio::test]
2110 async fn test_token_key_generation_edge_cases() {
2111 let empty_app_key = app_access_token_key("");
2113 assert!(empty_app_key.contains(APP_ACCESS_TOKEN_KEY_PREFIX));
2114
2115 let empty_tenant_key = tenant_access_token_key("", "");
2116 assert!(empty_tenant_key.contains(APP_ACCESS_TOKEN_KEY_PREFIX));
2117
2118 let long_app_id = "a".repeat(1000);
2120 let long_tenant_key = "b".repeat(1000);
2121
2122 let long_app_key = app_access_token_key(&long_app_id);
2123 let long_tenant_cache_key = tenant_access_token_key(&long_app_id, &long_tenant_key);
2124
2125 assert!(long_app_key.len() > 1000);
2126 assert!(long_tenant_cache_key.len() > 2000);
2127 assert!(long_app_key.contains(&long_app_id));
2128 assert!(long_tenant_cache_key.contains(&long_tenant_key));
2129 }
2130
2131 #[tokio::test]
2132 async fn test_metrics_counter_overflow_safety() {
2133 let metrics = TokenMetrics::new();
2134
2135 metrics
2137 .app_cache_hits
2138 .store(u64::MAX - 1, Ordering::Relaxed);
2139 metrics.app_cache_misses.store(1, Ordering::Relaxed);
2140
2141 let hit_rate = metrics.app_cache_hit_rate();
2143 assert!((0.0..=1.0).contains(&hit_rate));
2144
2145 let old_value = metrics.app_cache_hits.fetch_add(1, Ordering::Relaxed);
2147 assert_eq!(old_value, u64::MAX - 1);
2148
2149 let new_value = metrics.app_cache_hits.load(Ordering::Relaxed);
2151 assert!(new_value == u64::MAX || new_value == 0); }
2153
2154 #[tokio::test]
2155 async fn test_cache_token_with_special_expiry_values() {
2156 let manager = TokenManager::new();
2157
2158 let successful_resp = AppAccessTokenResp {
2160 raw_response: crate::core::api_resp::RawResponse {
2161 code: 0,
2162 msg: "success".to_string(),
2163 err: None,
2164 },
2165 expire: 3600,
2166 app_access_token: "expiry_test_token".to_string(),
2167 };
2168
2169 let result = manager
2170 .handle_app_access_token_response(successful_resp, "expiry_app")
2171 .await;
2172 assert!(result.is_ok());
2173
2174 let cache = manager.cache.read().await;
2176 if let Some(entry) = cache.get_with_expiry(&app_access_token_key("expiry_app")) {
2177 let expected_expiry = 3600 - EXPIRY_DELTA;
2179 assert!(entry.expiry_seconds() <= expected_expiry as u64);
2180 }
2181 }
2182}