open_lark/core/
app_ticket_manager.rs1use serde::{Deserialize, Serialize};
2
3use crate::core::{
4 cache::QuickCache,
5 config::Config,
6 constants::{APPLY_APP_TICKET_PATH, APP_TICKET_KEY_PREFIX},
7 SDKResult,
8};
9
10#[derive(Debug)]
11pub struct AppTicketManager {
12 pub cache: QuickCache<String>,
13}
14
15impl Default for AppTicketManager {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl AppTicketManager {
22 pub fn new() -> Self {
23 Self {
24 cache: QuickCache::new(),
25 }
26 }
27
28 pub fn set(&mut self, app_id: &str, value: &str, expire_time: i32) {
29 let key = app_ticket_key(app_id);
30 self.cache.set(&key, value.to_string(), expire_time);
31 }
32
33 pub async fn get(&self, config: &Config) -> Option<String> {
34 let key = app_ticket_key(&config.app_id);
35 match self.cache.get(&key) {
36 None => None,
37 Some(ticket) => {
38 if ticket.is_empty() {
39 apply_app_ticket(config).await.ok();
40 }
41
42 Some(ticket)
43 }
44 }
45 }
46}
47
48fn app_ticket_key(app_id: &str) -> String {
49 format!("{APP_TICKET_KEY_PREFIX}-{app_id}")
50}
51
52pub async fn apply_app_ticket(config: &Config) -> SDKResult<()> {
53 let url = format!("{}{}", config.base_url, APPLY_APP_TICKET_PATH);
54
55 let body = ResendAppTicketReq {
56 app_id: config.app_id.clone(),
57 app_secret: config.app_secret.clone(),
58 };
59
60 let _response = config.http_client.post(&url).json(&body).send().await?;
61
62 Ok(())
63}
64
65#[derive(Serialize, Deserialize)]
66struct ResendAppTicketReq {
67 app_id: String,
68 app_secret: String,
69}
70
71#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::core::config::Config;
83 use std::time::Duration;
84
85 fn create_test_config() -> Config {
86 Config::builder()
87 .app_id("test_app_id")
88 .app_secret("test_app_secret")
89 .base_url("https://test.api.com")
90 .build()
91 }
92
93 #[test]
94 fn test_app_ticket_manager_creation() {
95 let manager = AppTicketManager::new();
96
97 assert!(format!("{:?}", manager).contains("AppTicketManager"));
99 }
100
101 #[test]
102 fn test_app_ticket_manager_default() {
103 let manager = AppTicketManager::default();
104
105 assert!(format!("{:?}", manager).contains("AppTicketManager"));
107 }
108
109 #[test]
110 fn test_app_ticket_key_generation() {
111 let app_id = "test_app_123";
112 let key = app_ticket_key(app_id);
113
114 assert!(key.contains("test_app_123"));
115 assert!(key.starts_with("app_ticket"));
116 }
117
118 #[test]
119 fn test_set_and_get_ticket() {
120 let mut manager = AppTicketManager::new();
121 let app_id = "test_app";
122 let ticket_value = "test_ticket_value";
123
124 manager.set(app_id, ticket_value, 60);
126
127 let key = app_ticket_key(app_id);
129 let cached_ticket = manager.cache.get(&key);
130 assert_eq!(cached_ticket, Some(ticket_value.to_string()));
131 }
132
133 #[test]
134 fn test_set_ticket_with_different_app_ids() {
135 let mut manager = AppTicketManager::new();
136
137 manager.set("app1", "ticket1", 60);
139 manager.set("app2", "ticket2", 60);
140
141 let key1 = app_ticket_key("app1");
143 let key2 = app_ticket_key("app2");
144
145 assert_eq!(manager.cache.get(&key1), Some("ticket1".to_string()));
146 assert_eq!(manager.cache.get(&key2), Some("ticket2".to_string()));
147 }
148
149 #[test]
150 fn test_ticket_expiration() {
151 let mut manager = AppTicketManager::new();
152 let app_id = "test_app";
153
154 manager.set(app_id, "short_lived_ticket", 1);
156
157 let key = app_ticket_key(app_id);
159 assert!(manager.cache.get(&key).is_some());
160
161 std::thread::sleep(Duration::from_secs(2));
163
164 assert!(manager.cache.get(&key).is_none());
166 }
167
168 #[test]
169 fn test_overwrite_existing_ticket() {
170 let mut manager = AppTicketManager::new();
171 let app_id = "test_app";
172
173 manager.set(app_id, "initial_ticket", 60);
175
176 manager.set(app_id, "updated_ticket", 60);
178
179 let key = app_ticket_key(app_id);
181 assert_eq!(manager.cache.get(&key), Some("updated_ticket".to_string()));
182 }
183
184 #[test]
185 fn test_empty_app_id() {
186 let mut manager = AppTicketManager::new();
187
188 manager.set("", "ticket", 60);
190
191 let key = app_ticket_key("");
192 assert!(manager.cache.get(&key).is_some());
193 }
194
195 #[test]
196 fn test_special_characters_in_app_id() {
197 let mut manager = AppTicketManager::new();
198 let special_app_id = "app-id_with.special@chars";
199
200 manager.set(special_app_id, "special_ticket", 60);
201
202 let key = app_ticket_key(special_app_id);
203 assert_eq!(manager.cache.get(&key), Some("special_ticket".to_string()));
204 }
205
206 #[test]
207 fn test_zero_expiration_time() {
208 let mut manager = AppTicketManager::new();
209 let app_id = "test_app";
210
211 manager.set(app_id, "zero_exp_ticket", 0);
213
214 std::thread::sleep(Duration::from_millis(10));
216
217 let key = app_ticket_key(app_id);
218 let _ = manager.cache.get(&key);
220 }
221
222 #[test]
223 fn test_very_long_ticket_value() {
224 let mut manager = AppTicketManager::new();
225 let app_id = "test_app";
226 let long_ticket = "a".repeat(10000); manager.set(app_id, &long_ticket, 60);
229
230 let key = app_ticket_key(app_id);
231 assert_eq!(manager.cache.get(&key), Some(long_ticket));
232 }
233
234 #[tokio::test]
235 async fn test_get_with_cached_ticket() {
236 let mut manager = AppTicketManager::new();
237 let config = create_test_config();
238
239 manager.set(&config.app_id, "cached_ticket", 60);
241
242 let result = manager.get(&config).await;
243 assert_eq!(result, Some("cached_ticket".to_string()));
244 }
245
246 #[tokio::test]
247 async fn test_get_with_empty_ticket() {
248 let mut manager = AppTicketManager::new();
249 let config = create_test_config();
250
251 manager.set(&config.app_id, "", 60);
253
254 let result = manager.get(&config).await;
256 assert_eq!(result, Some("".to_string()));
257 }
258
259 #[tokio::test]
260 async fn test_get_without_cached_ticket() {
261 let manager = AppTicketManager::new();
262 let config = create_test_config();
263
264 let result = manager.get(&config).await;
266 assert_eq!(result, None);
267 }
268
269 #[test]
270 fn test_resend_app_ticket_req_serialization() {
271 let req = ResendAppTicketReq {
272 app_id: "test_app".to_string(),
273 app_secret: "test_secret".to_string(),
274 };
275
276 let json = serde_json::to_string(&req).unwrap();
278 assert!(json.contains("test_app"));
279 assert!(json.contains("test_secret"));
280
281 let deserialized: ResendAppTicketReq = serde_json::from_str(&json).unwrap();
283 assert_eq!(deserialized.app_id, "test_app");
284 assert_eq!(deserialized.app_secret, "test_secret");
285 }
286
287 #[test]
288 fn test_multiple_managers_independence() {
289 let mut manager1 = AppTicketManager::new();
290 let mut manager2 = AppTicketManager::new();
291
292 manager1.set("app1", "ticket1", 60);
293 manager2.set("app2", "ticket2", 60);
294
295 let key1 = app_ticket_key("app1");
297 let key2 = app_ticket_key("app2");
298
299 assert_eq!(manager1.cache.get(&key1), Some("ticket1".to_string()));
300 assert_eq!(manager1.cache.get(&key2), None);
301
302 assert_eq!(manager2.cache.get(&key2), Some("ticket2".to_string()));
303 assert_eq!(manager2.cache.get(&key1), None);
304 }
305
306 #[test]
307 fn test_app_ticket_key_format() {
308 let test_cases = vec![
309 ("simple_app", "app_ticket-simple_app"),
310 ("app-with-dashes", "app_ticket-app-with-dashes"),
311 ("app_with_underscores", "app_ticket-app_with_underscores"),
312 ("123numeric", "app_ticket-123numeric"),
313 ];
314
315 for (app_id, _expected_suffix) in test_cases {
316 let key = app_ticket_key(app_id);
317 assert!(key.ends_with(&format!("-{}", app_id)));
318 assert!(key.starts_with("app_ticket"));
320 }
321 }
322}