open_lark/core/request_builder/
auth_handler.rs

1use crate::core::{
2    config::Config, constants::AccessTokenType, error::LarkAPIError, req_option::RequestOption,
3};
4use reqwest::RequestBuilder;
5
6/// 处理不同类型的 AccessToken 认证
7pub struct AuthHandler;
8
9impl AuthHandler {
10    /// 根据认证类型为请求添加相应的认证头
11    pub async fn apply_auth(
12        req_builder: RequestBuilder,
13        access_token_type: AccessTokenType,
14        config: &Config,
15        option: &RequestOption,
16    ) -> Result<RequestBuilder, LarkAPIError> {
17        match access_token_type {
18            AccessTokenType::None => Ok(req_builder),
19            AccessTokenType::App => Self::apply_app_auth(req_builder, config, option).await,
20            AccessTokenType::Tenant => Self::apply_tenant_auth(req_builder, config, option).await,
21            AccessTokenType::User => Ok(Self::apply_user_auth(req_builder, option)),
22        }
23    }
24
25    /// 应用应用级认证
26    async fn apply_app_auth(
27        req_builder: RequestBuilder,
28        config: &Config,
29        option: &RequestOption,
30    ) -> Result<RequestBuilder, LarkAPIError> {
31        let app_access_token = if !option.app_access_token.is_empty() {
32            option.app_access_token.clone()
33        } else if config.enable_token_cache {
34            let token_manager = config.token_manager.lock().await;
35            token_manager
36                .get_app_access_token(config, &option.app_ticket, &config.app_ticket_manager)
37                .await?
38        } else {
39            return Err(LarkAPIError::MissingAccessToken);
40        };
41
42        Ok(Self::add_auth_header(req_builder, &app_access_token))
43    }
44
45    /// 应用租户级认证
46    async fn apply_tenant_auth(
47        req_builder: RequestBuilder,
48        config: &Config,
49        option: &RequestOption,
50    ) -> Result<RequestBuilder, LarkAPIError> {
51        let tenant_access_token = if !option.tenant_access_token.is_empty() {
52            option.tenant_access_token.clone()
53        } else if config.enable_token_cache {
54            let token_manager = config.token_manager.lock().await;
55            token_manager
56                .get_tenant_access_token(
57                    config,
58                    &option.tenant_key,
59                    &option.app_ticket,
60                    &config.app_ticket_manager,
61                )
62                .await?
63        } else {
64            return Err(LarkAPIError::MissingAccessToken);
65        };
66
67        Ok(Self::add_auth_header(req_builder, &tenant_access_token))
68    }
69
70    /// 应用用户级认证
71    fn apply_user_auth(req_builder: RequestBuilder, option: &RequestOption) -> RequestBuilder {
72        Self::add_auth_header(req_builder, &option.user_access_token)
73    }
74
75    /// 添加 Authorization 头
76    fn add_auth_header(req_builder: RequestBuilder, token: &str) -> RequestBuilder {
77        req_builder.header("Authorization", format!("Bearer {token}"))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::core::constants::AppType;
85    use reqwest::Client;
86
87    fn create_test_config() -> Config {
88        Config::builder()
89            .app_id("test_app_id")
90            .app_secret("test_app_secret")
91            .app_type(AppType::SelfBuild)
92            .enable_token_cache(false)
93            .build()
94    }
95
96    fn create_test_request_builder() -> RequestBuilder {
97        Client::new().get("https://test.api.example.com/test")
98    }
99
100    #[test]
101    fn test_auth_handler_struct_creation() {
102        let _handler = AuthHandler;
103    }
104
105    #[tokio::test]
106    async fn test_apply_auth_none_type() {
107        let req_builder = create_test_request_builder();
108        let config = create_test_config();
109        let option = RequestOption::default();
110
111        let result =
112            AuthHandler::apply_auth(req_builder, AccessTokenType::None, &config, &option).await;
113
114        assert!(result.is_ok());
115    }
116
117    #[tokio::test]
118    async fn test_apply_auth_user_type() {
119        let req_builder = create_test_request_builder();
120        let config = create_test_config();
121        let option = RequestOption {
122            user_access_token: "user_token_123".to_string(),
123            ..Default::default()
124        };
125
126        let result =
127            AuthHandler::apply_auth(req_builder, AccessTokenType::User, &config, &option).await;
128
129        assert!(result.is_ok());
130    }
131
132    #[tokio::test]
133    async fn test_apply_app_auth_with_token_in_option() {
134        let req_builder = create_test_request_builder();
135        let config = create_test_config();
136        let option = RequestOption {
137            app_access_token: "app_token_123".to_string(),
138            ..Default::default()
139        };
140
141        let result = AuthHandler::apply_app_auth(req_builder, &config, &option).await;
142        assert!(result.is_ok());
143    }
144
145    #[tokio::test]
146    async fn test_apply_app_auth_no_cache_no_token() {
147        let req_builder = create_test_request_builder();
148        let config = create_test_config(); // enable_token_cache is false
149        let option = RequestOption::default(); // no app_access_token
150
151        let result = AuthHandler::apply_app_auth(req_builder, &config, &option).await;
152        assert!(result.is_err());
153
154        match result {
155            Err(LarkAPIError::MissingAccessToken) => (),
156            _ => panic!("Expected MissingAccessToken error"),
157        }
158    }
159
160    #[tokio::test]
161    async fn test_apply_tenant_auth_with_token_in_option() {
162        let req_builder = create_test_request_builder();
163        let config = create_test_config();
164        let option = RequestOption {
165            tenant_access_token: "tenant_token_123".to_string(),
166            ..Default::default()
167        };
168
169        let result = AuthHandler::apply_tenant_auth(req_builder, &config, &option).await;
170        assert!(result.is_ok());
171    }
172
173    #[tokio::test]
174    async fn test_apply_tenant_auth_no_cache_no_token() {
175        let req_builder = create_test_request_builder();
176        let config = create_test_config(); // enable_token_cache is false
177        let option = RequestOption::default(); // no tenant_access_token
178
179        let result = AuthHandler::apply_tenant_auth(req_builder, &config, &option).await;
180        assert!(result.is_err());
181
182        match result {
183            Err(LarkAPIError::MissingAccessToken) => (),
184            _ => panic!("Expected MissingAccessToken error"),
185        }
186    }
187
188    #[test]
189    fn test_apply_user_auth() {
190        let req_builder = create_test_request_builder();
191        let option = RequestOption {
192            user_access_token: "user_token_456".to_string(),
193            ..Default::default()
194        };
195
196        let result = AuthHandler::apply_user_auth(req_builder, &option);
197
198        // Can't easily test the actual header, but should not panic
199        // and should return a RequestBuilder
200        assert!(format!("{:?}", result).contains("RequestBuilder"));
201    }
202
203    #[test]
204    fn test_add_auth_header_with_token() {
205        let req_builder = create_test_request_builder();
206        let token = "test_token_789";
207
208        let result = AuthHandler::add_auth_header(req_builder, token);
209
210        // Can't easily inspect headers without building request
211        // but should not panic and should return RequestBuilder
212        assert!(format!("{:?}", result).contains("RequestBuilder"));
213    }
214
215    #[test]
216    fn test_add_auth_header_with_empty_token() {
217        let req_builder = create_test_request_builder();
218        let token = "";
219
220        let result = AuthHandler::add_auth_header(req_builder, token);
221
222        // Should handle empty token without panicking
223        assert!(format!("{:?}", result).contains("RequestBuilder"));
224    }
225
226    #[tokio::test]
227    async fn test_apply_auth_all_types() {
228        let config = create_test_config();
229
230        let test_cases = vec![
231            (AccessTokenType::None, RequestOption::default()),
232            (
233                AccessTokenType::User,
234                RequestOption {
235                    user_access_token: "user_token".to_string(),
236                    ..Default::default()
237                },
238            ),
239            (
240                AccessTokenType::App,
241                RequestOption {
242                    app_access_token: "app_token".to_string(),
243                    ..Default::default()
244                },
245            ),
246            (
247                AccessTokenType::Tenant,
248                RequestOption {
249                    tenant_access_token: "tenant_token".to_string(),
250                    ..Default::default()
251                },
252            ),
253        ];
254
255        for (token_type, option) in test_cases {
256            let req_builder = create_test_request_builder();
257
258            let result = AuthHandler::apply_auth(req_builder, token_type, &config, &option).await;
259
260            // All cases with tokens should succeed
261            // None type always succeeds
262            match token_type {
263                AccessTokenType::None | AccessTokenType::User => {
264                    assert!(result.is_ok());
265                }
266                AccessTokenType::App | AccessTokenType::Tenant => {
267                    // These should succeed when token is provided in option
268                    assert!(result.is_ok());
269                }
270            }
271        }
272    }
273
274    #[tokio::test]
275    async fn test_apply_auth_with_cache_enabled() {
276        let config = Config::builder()
277            .app_id("test_app_id")
278            .app_secret("test_app_secret")
279            .app_type(AppType::SelfBuild)
280            .enable_token_cache(true)
281            .build();
282
283        let option = RequestOption::default();
284        let req_builder = create_test_request_builder();
285
286        // This will likely fail because token_manager needs proper setup
287        // but we test that it doesn't panic
288        let result =
289            AuthHandler::apply_auth(req_builder, AccessTokenType::App, &config, &option).await;
290
291        // Result depends on token_manager implementation
292        assert!(result.is_ok() || result.is_err());
293    }
294
295    #[test]
296    fn test_auth_handler_trait_implementations() {
297        // Test that AuthHandler can be used in Send/Sync contexts
298        fn assert_send<T: Send>() {}
299        fn assert_sync<T: Sync>() {}
300
301        assert_send::<AuthHandler>();
302        assert_sync::<AuthHandler>();
303    }
304
305    #[test]
306    fn test_add_auth_header_format() {
307        let req_builder = create_test_request_builder();
308        let token = "test123";
309
310        let _result = AuthHandler::add_auth_header(req_builder, token);
311
312        // The header should be formatted as "Bearer {token}"
313        // We can't easily test this without building the request
314        // but we verify the method doesn't panic
315    }
316}