1use std::{collections::HashSet, marker::PhantomData};
2
3use reqwest::RequestBuilder;
4use tracing::debug;
5use tracing::{info_span, Instrument};
6
7use crate::{
8 api::ApiResponseTrait,
9 api::{ApiRequest, Response},
10 auth::app_ticket::apply_app_ticket,
11 config::Config,
12 constants::*,
13 error::CoreError,
14 req_option::RequestOption,
15 req_translator::ReqTranslator,
16 response_handler::ImprovedResponseHandler,
17 SDKResult,
18};
19
20pub struct Transport<T> {
21 phantom_data: PhantomData<T>,
22}
23
24impl<T> Default for Transport<T> {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl<T> Transport<T> {
31 pub fn new() -> Self {
32 Self {
33 phantom_data: PhantomData,
34 }
35 }
36}
37
38impl<T: ApiResponseTrait + std::fmt::Debug + for<'de> serde::Deserialize<'de>> Transport<T> {
39 pub async fn request<R: Send>(
40 req: ApiRequest<R>,
41 config: &Config,
42 option: Option<RequestOption>,
43 ) -> Result<Response<T>, CoreError> {
44 let span = info_span!(
46 "http_request",
47 method = %req.method(),
48 path = %req.api_path(),
49 app_id = %config.app_id,
50 duration_ms = tracing::field::Empty,
51 status = tracing::field::Empty,
52 );
53
54 async move {
55 let start_time = std::time::Instant::now();
56 let option = option.unwrap_or_default();
57
58 let mut token_types = req.supported_access_token_types();
59 if token_types.is_empty() {
60 token_types = vec![AccessTokenType::None];
61 }
62
63 let result: Result<_, _> = async {
64 validate_token_type(&token_types, &option)?;
65 let access_token_type =
66 determine_token_type(&token_types, &option, config.enable_token_cache);
67 validate(config, &option, access_token_type)?;
68
69 Self::do_request(req, access_token_type, config, option).await
70 }
71 .await;
72
73 let current_span = tracing::Span::current();
75 let duration_ms = start_time.elapsed().as_millis() as u64;
76 current_span.record("duration_ms", duration_ms);
77
78 match &result {
79 Ok(response) => {
80 current_span.record(
81 "status",
82 if response.is_success() {
83 "success"
84 } else {
85 "api_error"
86 },
87 );
88 }
89 Err(_) => {
90 current_span.record("status", "error");
91 }
92 }
93
94 result
95 }
96 .instrument(span)
97 .await
98 }
99
100 async fn do_request<R: Send>(
101 mut http_req: ApiRequest<R>,
102 access_token_type: AccessTokenType,
103 config: &Config,
104 option: RequestOption,
105 ) -> SDKResult<Response<T>> {
106 let req =
107 ReqTranslator::translate(&mut http_req, access_token_type, config, &option).await?;
108 debug!(
109 method = %http_req.method(),
110 path = %http_req.api_path(),
111 "Sending request"
112 );
113 let resp = Self::do_send(req, http_req.to_bytes(), !http_req.file().is_empty()).await?;
114 debug!(
115 success = resp.is_success(),
116 code = resp.raw_response.code,
117 msg = %resp.raw_response.msg,
118 "Received response"
119 );
120
121 if !resp.is_success() && resp.raw_response.code == ERR_CODE_APP_TICKET_INVALID {
122 apply_app_ticket(config).await?;
123 }
124
125 Ok(resp)
126 }
127
128 pub async fn do_send(
129 raw_request: RequestBuilder,
130 body: Vec<u8>,
131 multi_part: bool,
132 ) -> SDKResult<Response<T>> {
133 let span = info_span!(
135 "http_send",
136 multi_part = multi_part,
137 body_size = body.len(),
138 response_code = tracing::field::Empty,
139 response_size = tracing::field::Empty,
140 );
141
142 async move {
143 let future = if multi_part {
144 raw_request.send()
145 } else {
146 raw_request.body(body).send()
147 };
148
149 match future.await {
150 Ok(response) => {
151 let status_code = response.status();
152 tracing::Span::current().record("response_code", status_code.as_u16());
153
154 ImprovedResponseHandler::handle_response(response).await
156 }
157 Err(err) => {
158 debug!("Request error: {err:?}");
159 tracing::Span::current().record("response_code", 0_u16); Err(err.into())
161 }
162 }
163 }
164 .instrument(span)
165 .await
166 }
167}
168
169fn validate_token_type(
170 access_token_types: &[AccessTokenType],
171 option: &RequestOption,
172) -> Result<(), CoreError> {
173 if access_token_types.is_empty() {
176 return Ok(());
177 }
178
179 let access_token_type = access_token_types[0];
180
181 if access_token_type == AccessTokenType::Tenant && option.user_access_token.is_some() {
182 return Err(crate::error::validation_error(
183 "access_token_type",
184 "tenant token type not match user access token",
185 ));
186 }
187
188 if access_token_type == AccessTokenType::App && option.tenant_access_token.is_some() {
189 return Err(crate::error::validation_error(
190 "access_token_type",
191 "user token type not match tenant access token",
192 ));
193 }
194
195 Ok(())
196}
197
198fn determine_token_type(
199 access_token_types: &[AccessTokenType],
200 option: &RequestOption,
201 enable_token_cache: bool,
202) -> AccessTokenType {
203 if !enable_token_cache {
204 if option.user_access_token.is_some() {
205 return AccessTokenType::User;
206 }
207 if option.tenant_access_token.is_some() {
208 return AccessTokenType::Tenant;
209 }
210 if option.app_access_token.is_some() {
211 return AccessTokenType::App;
212 }
213
214 return AccessTokenType::None;
215 }
216
217 if access_token_types.is_empty() {
219 if option.user_access_token.is_some() {
220 return AccessTokenType::User;
221 }
222 if option.tenant_access_token.is_some() || option.tenant_key.is_some() {
223 return AccessTokenType::Tenant;
224 }
225 if option.app_access_token.is_some() {
226 return AccessTokenType::App;
227 }
228 return AccessTokenType::None;
229 }
230
231 let mut accessible_token_type_set: HashSet<AccessTokenType> = HashSet::new();
232 let mut access_token_type = access_token_types[0];
233
234 for t in access_token_types {
235 if *t == AccessTokenType::Tenant {
236 access_token_type = *t; }
238 accessible_token_type_set.insert(*t);
239 }
240
241 if option.tenant_key.is_some() && accessible_token_type_set.contains(&AccessTokenType::Tenant) {
242 access_token_type = AccessTokenType::Tenant;
243 }
244
245 if option.user_access_token.is_some()
246 && accessible_token_type_set.contains(&AccessTokenType::User)
247 {
248 access_token_type = AccessTokenType::User;
249 }
250
251 access_token_type
252}
253
254fn validate(
255 config: &Config,
256 option: &RequestOption,
257 access_token_type: AccessTokenType,
258) -> Result<(), CoreError> {
259 if config.app_id.is_empty() {
260 return Err(crate::error::validation_error("app_id", "AppId is empty"));
261 }
262
263 if config.app_secret.is_empty() {
264 return Err(crate::error::validation_error(
265 "app_secret",
266 "AppSecret is empty",
267 ));
268 }
269
270 if !config.enable_token_cache {
271 if access_token_type == AccessTokenType::None {
272 return Ok(());
273 }
274 if option.user_access_token.is_none()
275 && option.tenant_access_token.is_none()
276 && option.app_access_token.is_none()
277 {
278 return Err(crate::error::validation_error(
279 "access_token",
280 "accessToken is empty",
281 ));
282 }
283 }
284
285 if config.app_type == AppType::Marketplace
286 && access_token_type == AccessTokenType::Tenant
287 && option.tenant_key.is_none()
288 {
289 return Err(crate::error::validation_error(
290 "access_token",
291 "accessToken is empty",
292 ));
293 }
294
295 if access_token_type == AccessTokenType::User && option.user_access_token.is_none() {
296 return Err(crate::error::validation_error(
297 "user_access_token",
298 "user access token is empty",
299 ));
300 }
301
302 if option.header.contains_key(HTTP_HEADER_KEY_REQUEST_ID) {
303 return Err(crate::error::validation_error(
304 "header",
305 format!("use {HTTP_HEADER_KEY_REQUEST_ID} as header key is not allowed"),
306 ));
307 }
308 if option.header.contains_key(HTTP_HEADER_REQUEST_ID) {
309 return Err(crate::error::validation_error(
310 "header",
311 format!("use {HTTP_HEADER_REQUEST_ID} as header key is not allowed"),
312 ));
313 }
314
315 Ok(())
316}
317
318#[cfg(test)]
319#[allow(clippy::field_reassign_with_default)]
320mod test {
321 use std::collections::HashMap;
322
323 use crate::{
324 config::Config,
325 constants::{AccessTokenType, AppType, HTTP_HEADER_KEY_REQUEST_ID, HTTP_HEADER_REQUEST_ID},
326 http::{determine_token_type, validate, validate_token_type},
327 req_option::RequestOption,
328 };
329
330 fn create_test_config() -> Config {
331 Config::builder()
332 .app_id("test_app_id")
333 .app_secret("test_app_secret")
334 .build()
335 }
336
337 fn create_test_config_marketplace() -> Config {
338 Config::builder()
339 .app_id("test_app_id")
340 .app_secret("test_app_secret")
341 .app_type(AppType::Marketplace)
342 .build()
343 }
344
345 #[test]
346 fn test_validate_token_type_empty_list_no_panic() {
347 let empty_types: Vec<AccessTokenType> = vec![];
348 let option = RequestOption::default();
349
350 let result = validate_token_type(&empty_types, &option);
352 assert!(result.is_ok());
353 }
354
355 #[test]
356 fn test_validate_token_type_non_empty_list_returns_ok() {
357 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
358 let option = RequestOption::default();
359
360 let result = validate_token_type(&types, &option);
362 assert!(result.is_ok());
363 }
364
365 #[test]
366 fn test_validate_token_type_tenant_with_user_token() {
367 let types = vec![AccessTokenType::Tenant];
368 let option = RequestOption {
369 user_access_token: Some("user_token".to_string()),
370 ..Default::default()
371 };
372
373 let result = validate_token_type(&types, &option);
374 assert!(result.is_err());
375 }
376
377 #[test]
378 fn test_validate_token_type_app_with_tenant_token() {
379 let types = vec![AccessTokenType::App];
380 let option = RequestOption {
381 tenant_access_token: Some("tenant_token".to_string()),
382 ..Default::default()
383 };
384
385 let result = validate_token_type(&types, &option);
386 assert!(result.is_err());
387 }
388
389 #[test]
390 fn test_validate_token_type_valid_combinations() {
391 let types = vec![AccessTokenType::User];
392 let mut option = RequestOption::default();
393 option.user_access_token = Some("user_token".to_string());
394
395 let result = validate_token_type(&types, &option);
396 assert!(result.is_ok());
397 }
398
399 #[test]
400 fn test_determine_token_type_no_cache_user() {
401 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
402 let mut option = RequestOption::default();
403 option.user_access_token = Some("user_token".to_string());
404
405 let token_type = determine_token_type(&types, &option, false);
406 assert_eq!(token_type, AccessTokenType::User);
407 }
408
409 #[test]
410 fn test_determine_token_type_no_cache_tenant() {
411 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
412 let option = RequestOption {
413 tenant_access_token: Some("tenant_token".to_string()),
414 ..Default::default()
415 };
416
417 let token_type = determine_token_type(&types, &option, false);
418 assert_eq!(token_type, AccessTokenType::Tenant);
419 }
420
421 #[test]
422 fn test_determine_token_type_no_cache_app() {
423 let types = vec![AccessTokenType::App, AccessTokenType::Tenant];
424 let option = RequestOption {
425 app_access_token: Some("app_token".to_string()),
426 ..Default::default()
427 };
428
429 let token_type = determine_token_type(&types, &option, false);
430 assert_eq!(token_type, AccessTokenType::App);
431 }
432
433 #[test]
434 fn test_determine_token_type_no_cache_none() {
435 let types = vec![AccessTokenType::None];
436 let option = RequestOption::default();
437
438 let token_type = determine_token_type(&types, &option, false);
439 assert_eq!(token_type, AccessTokenType::None);
440 }
441
442 #[test]
443 fn test_determine_token_type_with_cache_defaults_to_tenant() {
444 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
445 let option = RequestOption::default();
446
447 let token_type = determine_token_type(&types, &option, true);
448 assert_eq!(token_type, AccessTokenType::Tenant);
449 }
450
451 #[test]
452 fn test_determine_token_type_with_cache_tenant_key() {
453 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
454 let mut option = RequestOption::default();
455 option.tenant_key = Some("tenant_key".to_string());
456
457 let token_type = determine_token_type(&types, &option, true);
458 assert_eq!(token_type, AccessTokenType::Tenant);
459 }
460
461 #[test]
462 fn test_determine_token_type_with_cache_user_access_token() {
463 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
464 let mut option = RequestOption::default();
465 option.user_access_token = Some("user_token".to_string());
466
467 let token_type = determine_token_type(&types, &option, true);
468 assert_eq!(token_type, AccessTokenType::User);
469 }
470
471 #[test]
472 fn test_validate_empty_app_id() {
473 let config = Config::builder()
474 .app_id("")
475 .app_secret("test_secret")
476 .build();
477 let option = RequestOption::default();
478
479 let result = validate(&config, &option, AccessTokenType::None);
480 assert!(matches!(
481 result,
482 Err(crate::error::CoreError::Validation { .. })
483 ));
484 }
485
486 #[test]
487 fn test_validate_empty_app_secret() {
488 let config = Config::builder().app_id("test_id").app_secret("").build();
489 let option = RequestOption::default();
490
491 let result = validate(&config, &option, AccessTokenType::None);
492 assert!(matches!(
493 result,
494 Err(crate::error::CoreError::Validation { .. })
495 ));
496 }
497
498 #[test]
499 fn test_validate_no_cache_missing_access_tokens() {
500 let config = Config::builder()
501 .app_id("test_app_id")
502 .app_secret("test_app_secret")
503 .enable_token_cache(false)
504 .build();
505 let option = RequestOption::default();
506
507 let result = validate(&config, &option, AccessTokenType::User);
508 assert!(matches!(
509 result,
510 Err(crate::error::CoreError::Validation { .. })
511 ));
512 }
513
514 #[test]
515 fn test_validate_no_cache_with_tokens() {
516 let config = Config::builder()
517 .app_id("test_app_id")
518 .app_secret("test_app_secret")
519 .enable_token_cache(false)
520 .build();
521 let mut option = RequestOption::default();
522 option.user_access_token = Some("token".to_string());
523
524 let result = validate(&config, &option, AccessTokenType::User);
525 assert!(result.is_ok());
526 }
527
528 #[test]
529 fn test_validate_marketplace_tenant_no_key() {
530 let config = create_test_config_marketplace();
531 let option = RequestOption::default();
532
533 let result = validate(&config, &option, AccessTokenType::Tenant);
534 assert!(matches!(
535 result,
536 Err(crate::error::CoreError::Validation { .. })
537 ));
538 }
539
540 #[test]
541 fn test_validate_marketplace_tenant_with_key() {
542 let config = create_test_config_marketplace();
543 let mut option = RequestOption::default();
544 option.tenant_key = Some("tenant_key".to_string());
545
546 let result = validate(&config, &option, AccessTokenType::Tenant);
547 assert!(result.is_ok());
548 }
549
550 #[test]
551 fn test_validate_user_token_empty() {
552 let config = create_test_config();
553 let option = RequestOption::default();
554
555 let result = validate(&config, &option, AccessTokenType::User);
556 assert!(matches!(
557 result,
558 Err(crate::error::CoreError::Validation { .. })
559 ));
560 }
561
562 #[test]
563 fn test_validate_user_token_present() {
564 let config = create_test_config();
565 let mut option = RequestOption::default();
566 option.user_access_token = Some("user_token".to_string());
567
568 let result = validate(&config, &option, AccessTokenType::User);
569 assert!(result.is_ok());
570 }
571
572 #[test]
573 fn test_validate_forbidden_header_key_request_id() {
574 let config = create_test_config();
575 let mut option = RequestOption::default();
576 let mut header = HashMap::new();
577 header.insert(HTTP_HEADER_KEY_REQUEST_ID.to_string(), "test".to_string());
578 option.header = header;
579
580 let result = validate(&config, &option, AccessTokenType::None);
581 assert!(matches!(
582 result,
583 Err(crate::error::CoreError::Validation { .. })
584 ));
585 }
586
587 #[test]
588 fn test_validate_forbidden_header_request_id() {
589 let config = create_test_config();
590 let mut option = RequestOption::default();
591 let mut header = HashMap::new();
592 header.insert(HTTP_HEADER_REQUEST_ID.to_string(), "test".to_string());
593 option.header = header;
594
595 let result = validate(&config, &option, AccessTokenType::None);
596 assert!(matches!(
597 result,
598 Err(crate::error::CoreError::Validation { .. })
599 ));
600 }
601
602 #[test]
603 fn test_validate_valid_config() {
604 let config = create_test_config();
605 let option = RequestOption::default();
606
607 let result = validate(&config, &option, AccessTokenType::None);
608 assert!(result.is_ok());
609 }
610
611 #[test]
612 fn test_validate_no_cache_none_token_type() {
613 let config = Config::builder()
614 .app_id("test_app_id")
615 .app_secret("test_app_secret")
616 .enable_token_cache(false)
617 .build();
618 let option = RequestOption::default();
619
620 let result = validate(&config, &option, AccessTokenType::None);
621 assert!(result.is_ok());
622 }
623
624 #[test]
625 fn test_determine_token_type_first_is_tenant() {
626 let types = vec![AccessTokenType::Tenant, AccessTokenType::User];
627 let option = RequestOption::default();
628
629 let token_type = determine_token_type(&types, &option, true);
630 assert_eq!(token_type, AccessTokenType::Tenant);
631 }
632
633 #[test]
634 fn test_determine_token_type_no_tenant_in_list() {
635 let types = vec![AccessTokenType::User, AccessTokenType::App];
636 let option = RequestOption::default();
637
638 let token_type = determine_token_type(&types, &option, true);
639 assert_eq!(token_type, AccessTokenType::User);
641 }
642
643 #[test]
644 fn test_validate_token_type_edge_case_single_element() {
645 let types = vec![AccessTokenType::None];
646 let mut option = RequestOption::default();
647 option.user_access_token = Some("user_token".to_string());
648
649 let result = validate_token_type(&types, &option);
650 assert!(result.is_ok());
651 }
652
653 #[test]
654 fn test_decode_file_name_whitespace_handling() {
655 let raw = " attachment ; filename=\"test.txt\" ; filename*=UTF-8''spaced%20file.txt ";
656 let file_name = crate::content_disposition::extract_filename(raw).unwrap();
657 assert_eq!(file_name, "spaced%20file.txt");
658 }
659
660 #[test]
661 fn test_decode_file_name_no_equals() {
662 let raw = "attachment; filename*UTF-8''invalid.txt";
663 let file_name = crate::content_disposition::extract_filename(raw);
664 assert!(file_name.is_none());
665 }
666
667 #[test]
670 fn test_validate_token_type_non_empty_list_ok() {
671 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
672 let option = RequestOption::default();
673
674 let result = validate_token_type(&types, &option);
675 assert!(result.is_ok());
676 }
677
678 #[test]
679 fn test_determine_token_type_priority_with_multiple_tokens() {
680 let types = vec![
681 AccessTokenType::User,
682 AccessTokenType::Tenant,
683 AccessTokenType::App,
684 ];
685 let mut option = RequestOption::default();
686 option.user_access_token = Some("user_token".to_string());
687 option.tenant_key = Some("tenant_key".to_string());
688
689 let token_type = determine_token_type(&types, &option, true);
691 assert_eq!(token_type, AccessTokenType::User);
692 }
693
694 #[test]
695 fn test_determine_token_type_tenant_key_without_tenant_type() {
696 let types = vec![AccessTokenType::User, AccessTokenType::App];
697 let mut option = RequestOption::default();
698 option.tenant_key = Some("tenant_key".to_string());
699
700 let token_type = determine_token_type(&types, &option, true);
702 assert_eq!(token_type, AccessTokenType::User);
703 }
704
705 #[test]
706 fn test_determine_token_type_user_token_without_user_type() {
707 let types = vec![AccessTokenType::Tenant, AccessTokenType::App];
708 let mut option = RequestOption::default();
709 option.user_access_token = Some("user_token".to_string());
710
711 let token_type = determine_token_type(&types, &option, true);
713 assert_eq!(token_type, AccessTokenType::Tenant);
714 }
715
716 #[test]
717 fn test_determine_token_type_cache_disabled_fallback_priority() {
718 let types = vec![
719 AccessTokenType::User,
720 AccessTokenType::Tenant,
721 AccessTokenType::App,
722 ];
723 let mut option = RequestOption::default();
724 option.tenant_access_token = Some("tenant_token".to_string());
725 option.app_access_token = Some("app_token".to_string());
726
727 let token_type = determine_token_type(&types, &option, false);
729 assert_eq!(token_type, AccessTokenType::Tenant);
730 }
731
732 #[test]
733 fn test_determine_token_type_cache_disabled_all_empty() {
734 let types = vec![AccessTokenType::User, AccessTokenType::Tenant];
735 let option = RequestOption::default();
736
737 let token_type = determine_token_type(&types, &option, false);
739 assert_eq!(token_type, AccessTokenType::None);
740 }
741
742 #[test]
743 fn test_validate_config_with_all_required_fields() {
744 let config = Config::builder()
745 .app_id("valid_app_id")
746 .app_secret("valid_app_secret")
747 .enable_token_cache(true)
748 .build();
749 let option = RequestOption::default();
750
751 let result = validate(&config, &option, AccessTokenType::Tenant);
752 assert!(result.is_ok());
753 }
754
755 #[test]
756 fn test_validate_marketplace_app_with_valid_tenant_key() {
757 let config = Config::builder()
758 .app_id("marketplace_app")
759 .app_secret("marketplace_secret")
760 .app_type(AppType::Marketplace)
761 .build();
762 let mut option = RequestOption::default();
763 option.tenant_key = Some("valid_tenant_key".to_string());
764
765 let result = validate(&config, &option, AccessTokenType::Tenant);
766 assert!(result.is_ok());
767 }
768
769 #[test]
770 fn test_validate_marketplace_app_type_with_non_tenant_token() {
771 let config = Config::builder()
772 .app_id("marketplace_app")
773 .app_secret("marketplace_secret")
774 .app_type(AppType::Marketplace)
775 .build();
776 let mut option = RequestOption::default();
777 option.user_access_token = Some("user_token".to_string());
778
779 let result = validate(&config, &option, AccessTokenType::User);
781 assert!(result.is_ok());
782
783 let result = validate(&config, &RequestOption::default(), AccessTokenType::App);
785 assert!(result.is_ok());
786
787 let result = validate(&config, &RequestOption::default(), AccessTokenType::None);
789 assert!(result.is_ok());
790 }
791
792 #[test]
793 fn test_validate_no_cache_with_multiple_token_types() {
794 let config = Config::builder()
795 .app_id("test_app")
796 .app_secret("test_secret")
797 .enable_token_cache(false)
798 .build();
799 let mut option = RequestOption::default();
800 option.user_access_token = Some("user_token".to_string());
801 option.tenant_access_token = Some("tenant_token".to_string());
802 option.app_access_token = Some("app_token".to_string());
803
804 let result = validate(&config, &option, AccessTokenType::User);
806 assert!(result.is_ok());
807 }
808
809 #[test]
810 fn test_validate_user_token_type_with_empty_user_token() {
811 let config = create_test_config();
812 let mut option = RequestOption::default();
813 option.tenant_access_token = Some("tenant_token".to_string()); let result = validate(&config, &option, AccessTokenType::User);
817 assert!(matches!(
818 result,
819 Err(crate::error::CoreError::Validation { .. })
820 ));
821 if let Err(crate::error::CoreError::Validation { message, .. }) = result {
822 assert!(message.contains("user access token is empty"));
823 }
824 }
825
826 #[test]
827 fn test_validate_forbidden_headers_custom_values() {
828 let config = create_test_config();
829 let mut option = RequestOption::default();
830 let mut header = HashMap::new();
831 header.insert("X-Request-Id".to_string(), "custom_id".to_string());
832 header.insert("Custom-Header".to_string(), "value".to_string());
833 option.header = header;
834
835 let result = validate(&config, &option, AccessTokenType::None);
836 assert!(matches!(
837 result,
838 Err(crate::error::CoreError::Validation { .. })
839 ));
840 }
841
842 #[test]
843 fn test_validate_forbidden_headers_request_id_variation() {
844 let config = create_test_config();
845 let mut option = RequestOption::default();
846 let mut header = HashMap::new();
847 header.insert("Request-Id".to_string(), "another_id".to_string());
848 option.header = header;
849
850 let result = validate(&config, &option, AccessTokenType::None);
851 assert!(matches!(
852 result,
853 Err(crate::error::CoreError::Validation { .. })
854 ));
855 }
856
857 #[test]
858 fn test_validate_allowed_custom_headers() {
859 let config = create_test_config();
860 let mut option = RequestOption::default();
861 let mut header = HashMap::new();
862 header.insert("Authorization".to_string(), "Bearer token".to_string());
863 header.insert("Content-Type".to_string(), "application/json".to_string());
864 header.insert("Custom-App-Header".to_string(), "value".to_string());
865 option.header = header;
866
867 let result = validate(&config, &option, AccessTokenType::None);
868 assert!(result.is_ok());
869 }
870
871 #[test]
872 fn test_decode_file_name_missing_utf8_prefix() {
873 let raw = "attachment; filename*=''missing_utf8.txt";
874 let file_name = crate::content_disposition::extract_filename(raw);
875 assert_eq!(file_name, Some("missing_utf8.txt".to_string()));
876 }
877
878 #[test]
879 fn test_decode_file_name_malformed_filename_star() {
880 let raw = "attachment; filename*=UTF-8";
881 let file_name = crate::content_disposition::extract_filename(raw);
882 assert_eq!(file_name, None);
883 }
884
885 #[test]
886 fn test_decode_file_name_multiple_filename_star_entries() {
887 let raw = "attachment; filename*=UTF-8''first.txt; filename*=UTF-8''second.txt";
888 let file_name = crate::content_disposition::extract_filename(raw).unwrap();
889 assert_eq!(file_name, "first.txt");
891 }
892
893 #[test]
894 fn test_decode_file_name_special_characters() {
895 let raw = "attachment; filename*=UTF-8''special%20%21%40%23.txt";
896 let file_name = crate::content_disposition::extract_filename(raw).unwrap();
897 assert_eq!(file_name, "special%20%21%40%23.txt");
898 }
899
900 #[test]
901 fn test_decode_file_name_empty_filename() {
902 let raw = "attachment; filename*=UTF-8''";
903 let file_name = crate::content_disposition::extract_filename(raw).unwrap();
904 assert_eq!(file_name, "");
905 }
906
907 #[test]
908 fn test_determine_token_type_empty_types_list_no_panic() {
909 let types: Vec<AccessTokenType> = vec![];
910 let option = RequestOption::default();
911
912 let token_type = determine_token_type(&types, &option, true);
913 assert_eq!(token_type, AccessTokenType::None);
914 }
915
916 #[test]
917 fn test_determine_token_type_empty_types_list_no_cache() {
918 let types: Vec<AccessTokenType> = vec![];
920 let option = RequestOption::default();
921
922 let token_type = determine_token_type(&types, &option, false);
924 assert_eq!(token_type, AccessTokenType::None);
925 }
926
927 #[test]
928 fn test_determine_token_type_single_app_type() {
929 let types = vec![AccessTokenType::App];
930 let option = RequestOption::default();
931
932 let token_type = determine_token_type(&types, &option, true);
933 assert_eq!(token_type, AccessTokenType::App);
934 }
935
936 #[test]
937 fn test_determine_token_type_single_none_type() {
938 let types = vec![AccessTokenType::None];
939 let option = RequestOption::default();
940
941 let token_type = determine_token_type(&types, &option, true);
942 assert_eq!(token_type, AccessTokenType::None);
943 }
944
945 #[test]
946 fn test_validate_with_cache_enabled_various_token_types() {
947 let config = Config::builder()
948 .app_id("test_app")
949 .app_secret("test_secret")
950 .enable_token_cache(true)
951 .build();
952 let option = RequestOption::default();
953
954 assert!(validate(&config, &option, AccessTokenType::None).is_ok());
956 assert!(validate(&config, &option, AccessTokenType::App).is_ok());
957 assert!(validate(&config, &option, AccessTokenType::Tenant).is_ok());
958 }
959
960 #[test]
961 fn test_validate_self_build_app_type_with_tenant_token() {
962 let config = Config::builder()
963 .app_id("self_build_app")
964 .app_secret("self_build_secret")
965 .app_type(AppType::SelfBuild)
966 .build();
967 let option = RequestOption::default();
968
969 let result = validate(&config, &option, AccessTokenType::Tenant);
971 assert!(result.is_ok());
972 }
973
974 #[test]
977 fn test_validate_token_type_with_mismatched_tokens_simulation() {
978 let types = vec![AccessTokenType::Tenant];
980 let mut option = RequestOption::default();
981 option.user_access_token = Some("user_token".to_string()); let result = validate_token_type(&types, &option);
984 assert!(result.is_err());
985 }
986
987 #[test]
988 fn test_validate_comprehensive_error_messages() {
989 let config_empty_id = Config::builder().app_id("").app_secret("secret").build();
990
991 let config_empty_secret = Config::builder().app_id("app_id").app_secret("").build();
992
993 let option = RequestOption::default();
994
995 if let Err(crate::error::CoreError::Validation { message, .. }) =
997 validate(&config_empty_id, &option, AccessTokenType::None)
998 {
999 assert_eq!(message, "AppId is empty");
1000 } else {
1001 panic!("Expected IllegalParamError for empty app_id");
1002 }
1003
1004 if let Err(crate::error::CoreError::Validation { message, .. }) =
1005 validate(&config_empty_secret, &option, AccessTokenType::None)
1006 {
1007 assert_eq!(message, "AppSecret is empty");
1008 } else {
1009 panic!("Expected IllegalParamError for empty app_secret");
1010 }
1011 }
1012}