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