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