1use bitwarden_core::Client;
2#[cfg(feature = "wasm")]
3use wasm_bindgen::prelude::*;
4
5use crate::send_access::{
6 SendAccessTokenError, SendAccessTokenRequest, SendAccessTokenResponse,
7 access_token_response::UnexpectedIdentityError,
8 api::{
9 SendAccessTokenApiErrorResponse, SendAccessTokenApiSuccessResponse,
10 SendAccessTokenRequestPayload,
11 },
12};
13
14#[derive(Clone)]
16#[cfg_attr(feature = "wasm", wasm_bindgen)]
17pub struct SendAccessClient {
18 pub(crate) client: Client,
19}
20
21impl SendAccessClient {
22 pub(crate) fn new(client: Client) -> Self {
23 Self { client }
24 }
25}
26
27#[cfg_attr(feature = "wasm", wasm_bindgen)]
28impl SendAccessClient {
29 pub async fn request_send_access_token(
31 &self,
32 request: SendAccessTokenRequest,
33 ) -> Result<SendAccessTokenResponse, SendAccessTokenError> {
34 let payload: SendAccessTokenRequestPayload = request.into();
36
37 let configurations = self.client.internal.get_api_configurations();
44
45 let url = format!("{}/connect/token", configurations.identity_config.base_path);
47
48 let request: reqwest_middleware::RequestBuilder = configurations
49 .identity_config
50 .client
51 .post(&url)
52 .header(reqwest::header::ACCEPT, "application/json")
53 .header(reqwest::header::CACHE_CONTROL, "no-store")
54 .form(&payload);
55
56 let response: reqwest::Response = request.send().await?;
61
62 let response_status = response.status();
63
64 if response_status.is_success() {
67 let send_access_token: SendAccessTokenApiSuccessResponse = response.json().await?;
68 return Ok(send_access_token.into());
69 }
70
71 let err_response = match response.json::<SendAccessTokenApiErrorResponse>().await {
72 Ok(err) => err,
76 Err(_) => {
77 let error_string = format!(
81 "Received response status {} against {}",
82 response_status, url
83 );
84
85 return Err(SendAccessTokenError::Unexpected(UnexpectedIdentityError(
86 error_string,
87 )));
88 }
89 };
90
91 Err(SendAccessTokenError::Expected(err_response))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use bitwarden_core::{Client as CoreClient, ClientSettings, DeviceType};
98 use bitwarden_test::start_api_mock;
99 use wiremock::{
100 Mock, MockServer, ResponseTemplate,
101 matchers::{self, body_string_contains},
102 };
103
104 use crate::{
105 AuthClientExt,
106 api::enums::{GrantType, Scope},
107 send_access::{
108 SendAccessClient, SendAccessCredentials, SendAccessTokenError, SendAccessTokenRequest,
109 SendAccessTokenResponse, SendEmailCredentials, SendEmailOtpCredentials,
110 SendPasswordCredentials, UnexpectedIdentityError,
111 api::{
112 SendAccessTokenApiErrorResponse, SendAccessTokenInvalidGrantError,
113 SendAccessTokenInvalidRequestError,
114 },
115 },
116 };
117
118 fn make_send_client(mock_server: &MockServer) -> SendAccessClient {
119 let settings = ClientSettings {
120 identity_url: format!("http://{}/identity", mock_server.address()),
121 api_url: format!("http://{}/api", mock_server.address()),
122 user_agent: "Bitwarden Rust-SDK [TEST]".into(),
123 device_type: DeviceType::SDK,
124 device_identifier: None,
125 bitwarden_client_version: None,
126 bitwarden_package_type: None,
127 };
128 let core_client = CoreClient::new(Some(settings));
129 core_client.auth_new().send_access()
130 }
131
132 mod request_send_access_token_success_tests {
133
134 use super::*;
135
136 #[tokio::test]
137 async fn request_send_access_token_anon_send_success() {
138 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
139 let scope_str = scope_value.as_str().unwrap();
140
141 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
142 let grant_type_str = grant_type_value.as_str().unwrap();
143
144 let raw_success = serde_json::json!({
146 "access_token": "token",
147 "token_type": "bearer",
148 "expires_in": 3600,
149 "scope": scope_str
150 });
151
152 let req = SendAccessTokenRequest {
154 send_id: "test_send_id".into(),
155 send_access_credentials: None, };
157
158 let mock = Mock::given(matchers::method("POST"))
159 .and(matchers::path("identity/connect/token"))
160 .and(matchers::header(
162 reqwest::header::CONTENT_TYPE.as_str(),
163 "application/x-www-form-urlencoded",
164 ))
165 .and(matchers::header(
166 reqwest::header::ACCEPT.as_str(),
167 "application/json",
168 ))
169 .and(matchers::header(
170 reqwest::header::CACHE_CONTROL.as_str(),
171 "no-store",
172 ))
173 .and(body_string_contains("client_id=send"))
175 .and(body_string_contains(format!(
176 "grant_type={}",
177 grant_type_str
178 )))
179 .and(body_string_contains(format!("scope={}", scope_str)))
180 .and(body_string_contains(format!("send_id={}", req.send_id)))
181 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
183
184 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
186
187 let send_access_client = make_send_client(&mock_server);
189
190 let token: SendAccessTokenResponse = send_access_client
191 .request_send_access_token(req)
192 .await
193 .unwrap();
194
195 assert_eq!(token.token, "token");
196 assert!(token.expires_at > 0);
197 }
198
199 #[tokio::test]
200 async fn request_send_access_token_password_protected_send_success() {
201 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
202 let scope_str = scope_value.as_str().unwrap();
203
204 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
205 let grant_type_str = grant_type_value.as_str().unwrap();
206
207 let raw_success = serde_json::json!({
209 "access_token": "token",
210 "token_type": "bearer",
211 "expires_in": 3600,
212 "scope": scope_str
213 });
214
215 let password_hash_b64 = "valid-hash";
216
217 let password_credentials = SendPasswordCredentials {
218 password_hash_b64: password_hash_b64.into(),
219 };
220
221 let req = SendAccessTokenRequest {
222 send_id: "valid-send-id".into(),
223 send_access_credentials: Some(SendAccessCredentials::Password(
224 password_credentials,
225 )),
226 };
227
228 let mock = Mock::given(matchers::method("POST"))
229 .and(matchers::path("identity/connect/token"))
230 .and(matchers::header(
232 reqwest::header::CONTENT_TYPE.as_str(),
233 "application/x-www-form-urlencoded",
234 ))
235 .and(matchers::header(
236 reqwest::header::ACCEPT.as_str(),
237 "application/json",
238 ))
239 .and(matchers::header(
240 reqwest::header::CACHE_CONTROL.as_str(),
241 "no-store",
242 ))
243 .and(body_string_contains("client_id=send"))
245 .and(body_string_contains(format!(
246 "grant_type={}",
247 grant_type_str
248 )))
249 .and(body_string_contains(format!("scope={}", scope_str)))
250 .and(body_string_contains(format!("send_id={}", req.send_id)))
251 .and(body_string_contains(format!(
252 "password_hash_b64={}",
253 password_hash_b64
254 )))
255 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
257
258 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
260
261 let send_access_client = make_send_client(&mock_server);
263
264 let token: SendAccessTokenResponse = send_access_client
265 .request_send_access_token(req)
266 .await
267 .unwrap();
268
269 assert_eq!(token.token, "token");
270 assert!(token.expires_at > 0);
271 }
272
273 #[tokio::test]
274 async fn request_send_access_token_email_otp_protected_send_success() {
275 let scope_value = serde_json::to_value(Scope::ApiSendAccess).unwrap();
276 let scope_str = scope_value.as_str().unwrap();
277
278 let grant_type_value = serde_json::to_value(GrantType::SendAccess).unwrap();
279 let grant_type_str = grant_type_value.as_str().unwrap();
280
281 let raw_success = serde_json::json!({
283 "access_token": "token",
284 "token_type": "bearer",
285 "expires_in": 3600,
286 "scope": scope_str
287 });
288
289 let email = "[email protected]";
290 let otp: &str = "valid_otp";
291
292 let email_otp_credentials = SendEmailOtpCredentials {
293 email: email.into(),
294 otp: otp.into(),
295 };
296
297 let req = SendAccessTokenRequest {
298 send_id: "valid-send-id".into(),
299 send_access_credentials: Some(SendAccessCredentials::EmailOtp(
300 email_otp_credentials,
301 )),
302 };
303
304 let mock = Mock::given(matchers::method("POST"))
305 .and(matchers::path("identity/connect/token"))
306 .and(matchers::header(
308 reqwest::header::CONTENT_TYPE.as_str(),
309 "application/x-www-form-urlencoded",
310 ))
311 .and(matchers::header(
312 reqwest::header::ACCEPT.as_str(),
313 "application/json",
314 ))
315 .and(matchers::header(
316 reqwest::header::CACHE_CONTROL.as_str(),
317 "no-store",
318 ))
319 .and(body_string_contains("client_id=send"))
321 .and(body_string_contains(format!(
322 "grant_type={}",
323 grant_type_str
324 )))
325 .and(body_string_contains(format!("scope={}", scope_str)))
326 .and(body_string_contains(format!("send_id={}", req.send_id)))
327 .and(body_string_contains("email=valid%40email.com"))
328 .and(body_string_contains(format!("otp={}", otp)))
329 .respond_with(ResponseTemplate::new(200).set_body_json(raw_success));
331
332 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
334
335 let send_access_client = make_send_client(&mock_server);
337
338 let token: SendAccessTokenResponse = send_access_client
339 .request_send_access_token(req)
340 .await
341 .unwrap();
342
343 assert_eq!(token.token, "token");
344 assert!(token.expires_at > 0);
345 }
346 }
347
348 mod request_send_access_token_invalid_request_tests {
349 use super::*;
350
351 #[tokio::test]
352 async fn request_send_access_token_invalid_request_send_id_required_error() {
353 let error_description = "send_id is required.".into();
355 let raw_error = serde_json::json!({
356 "error": "invalid_request",
357 "error_description": error_description,
358 "send_access_error_type": "send_id_required"
359 });
360
361 let mock = Mock::given(matchers::method("POST"))
363 .and(matchers::path("identity/connect/token"))
364 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
365
366 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
368
369 let send_access_client = make_send_client(&mock_server);
371
372 let req = SendAccessTokenRequest {
374 send_id: "".into(),
375 send_access_credentials: None, };
377
378 let result = send_access_client.request_send_access_token(req).await;
379
380 assert!(result.is_err());
381
382 let err = result.unwrap_err();
383 match err {
384 SendAccessTokenError::Expected(api_err) => {
385 assert_eq!(
386 api_err,
387 SendAccessTokenApiErrorResponse::InvalidRequest {
388 send_access_error_type: Some(
389 SendAccessTokenInvalidRequestError::SendIdRequired
390 ),
391 error_description: Some(error_description),
392 }
393 );
394 }
395 other => panic!("expected Response variant, got {:?}", other),
396 }
397 }
398
399 #[tokio::test]
400 async fn request_send_access_token_invalid_request_password_hash_required_error() {
401 let error_description = "password_hash_b64 is required.".into();
403 let raw_error = serde_json::json!({
404 "error": "invalid_request",
405 "error_description": error_description,
406 "send_access_error_type": "password_hash_b64_required"
407 });
408
409 let mock = Mock::given(matchers::method("POST"))
411 .and(matchers::path("identity/connect/token"))
412 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
413
414 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
416
417 let send_access_client = make_send_client(&mock_server);
419
420 let req = SendAccessTokenRequest {
422 send_id: "test_send_id".into(),
423 send_access_credentials: None, };
425
426 let result = send_access_client.request_send_access_token(req).await;
427
428 assert!(result.is_err());
429
430 let err = result.unwrap_err();
431 match err {
432 SendAccessTokenError::Expected(api_err) => {
433 assert_eq!(
434 api_err,
435 SendAccessTokenApiErrorResponse::InvalidRequest {
436 send_access_error_type: Some(
437 SendAccessTokenInvalidRequestError::PasswordHashB64Required
438 ),
439 error_description: Some(error_description),
440 }
441 );
442 }
443 other => panic!("expected Response variant, got {:?}", other),
444 }
445 }
446
447 #[tokio::test]
448 async fn request_send_access_token_invalid_request_email_required_error() {
449 let error_description = "email is required.".into();
451 let raw_error = serde_json::json!({
452 "error": "invalid_request",
453 "error_description": error_description,
454 "send_access_error_type": "email_required"
455 });
456
457 let mock = Mock::given(matchers::method("POST"))
459 .and(matchers::path("identity/connect/token"))
460 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
461
462 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
464
465 let send_access_client = make_send_client(&mock_server);
467
468 let req = SendAccessTokenRequest {
470 send_id: "test_send_id".into(),
471 send_access_credentials: None, };
473
474 let result = send_access_client.request_send_access_token(req).await;
475
476 assert!(result.is_err());
477
478 let err = result.unwrap_err();
479 match err {
480 SendAccessTokenError::Expected(api_err) => {
481 assert_eq!(
482 api_err,
483 SendAccessTokenApiErrorResponse::InvalidRequest {
484 send_access_error_type: Some(
485 SendAccessTokenInvalidRequestError::EmailRequired
486 ),
487 error_description: Some(error_description),
488 }
489 );
490 }
491 other => panic!("expected Response variant, got {:?}", other),
492 }
493 }
494
495 #[tokio::test]
496 async fn request_send_access_token_invalid_request_email_otp_required_error() {
497 let error_description =
499 "email and otp are required. An OTP has been sent to the email address provided."
500 .into();
501 let raw_error = serde_json::json!({
502 "error": "invalid_request",
503 "error_description": error_description,
504 "send_access_error_type": "email_and_otp_required"
505 });
506
507 let mock = Mock::given(matchers::method("POST"))
509 .and(matchers::path("identity/connect/token"))
510 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
511
512 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
514
515 let send_access_client = make_send_client(&mock_server);
517
518 let email_credentials = SendEmailCredentials {
520 email: "[email protected]".into(),
521 };
522
523 let req = SendAccessTokenRequest {
524 send_id: "test_send_id".into(),
525 send_access_credentials: Some(SendAccessCredentials::Email(email_credentials)),
526 };
527
528 let result = send_access_client.request_send_access_token(req).await;
529
530 assert!(result.is_err());
531
532 let err = result.unwrap_err();
533 match err {
534 SendAccessTokenError::Expected(api_err) => {
535 assert_eq!(
536 api_err,
537 SendAccessTokenApiErrorResponse::InvalidRequest {
538 send_access_error_type: Some(
539 SendAccessTokenInvalidRequestError::EmailAndOtpRequired
540 ),
541 error_description: Some(error_description),
542 }
543 );
544 }
545 other => panic!("expected Response variant, got {:?}", other),
546 }
547 }
548
549 #[tokio::test]
550 async fn request_send_access_token_invalid_request_email_credential_unrecognized_email_masked_as_otp_required()
551 {
552 let error_description = "email and otp are required.".into();
554 let raw_error = serde_json::json!({
555 "error": "invalid_request",
556 "error_description": error_description,
557 "send_access_error_type": "email_and_otp_required"
558 });
559
560 let mock = Mock::given(matchers::method("POST"))
562 .and(matchers::path("identity/connect/token"))
563 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
564
565 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
567
568 let send_access_client = make_send_client(&mock_server);
570
571 let email_credentials = SendEmailCredentials {
573 email: "invalid-email".into(),
574 };
575 let req = SendAccessTokenRequest {
576 send_id: "valid-send-id".into(),
577 send_access_credentials: Some(SendAccessCredentials::Email(email_credentials)),
578 };
579
580 let result = send_access_client.request_send_access_token(req).await;
581
582 assert!(result.is_err());
583
584 let err = result.unwrap_err();
585 match err {
586 SendAccessTokenError::Expected(api_err) => {
587 assert_eq!(
589 api_err,
590 SendAccessTokenApiErrorResponse::InvalidRequest {
591 send_access_error_type: Some(
592 SendAccessTokenInvalidRequestError::EmailAndOtpRequired
593 ),
594 error_description: Some(error_description),
595 }
596 );
597 }
598 other => panic!("expected Response variant, got {:?}", other),
599 }
600 }
601
602 #[tokio::test]
603 async fn request_send_access_token_invalid_request_email_otp_credential_invalid_otp_masked_as_otp_required()
604 {
605 let error_description = "email and otp are required.".into();
608 let raw_error = serde_json::json!({
609 "error": "invalid_request",
610 "error_description": error_description,
611 "send_access_error_type": "email_and_otp_required"
612 });
613
614 let mock = Mock::given(matchers::method("POST"))
616 .and(matchers::path("identity/connect/token"))
617 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
618
619 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
621
622 let send_access_client = make_send_client(&mock_server);
624
625 let email_otp_credentials = SendEmailOtpCredentials {
627 email: "[email protected]".into(),
628 otp: "invalid_otp".into(),
629 };
630 let req = SendAccessTokenRequest {
631 send_id: "valid-send-id".into(),
632 send_access_credentials: Some(SendAccessCredentials::EmailOtp(
633 email_otp_credentials,
634 )),
635 };
636
637 let result = send_access_client.request_send_access_token(req).await;
638
639 assert!(result.is_err());
640
641 let err = result.unwrap_err();
642 match err {
643 SendAccessTokenError::Expected(api_err) => {
644 assert_eq!(
645 api_err,
646 SendAccessTokenApiErrorResponse::InvalidRequest {
647 send_access_error_type: Some(
648 SendAccessTokenInvalidRequestError::EmailAndOtpRequired
649 ),
650 error_description: Some(error_description),
651 }
652 );
653 }
654 other => panic!("expected Response variant, got {:?}", other),
655 }
656 }
657
658 #[tokio::test]
659 async fn request_send_access_token_invalid_request_email_otp_credential_unrecognized_email_masked_as_otp_required()
660 {
661 let error_description = "email and otp are required.".into();
666 let raw_error = serde_json::json!({
667 "error": "invalid_request",
668 "error_description": error_description,
669 "send_access_error_type": "email_and_otp_required"
670 });
671
672 let mock = Mock::given(matchers::method("POST"))
674 .and(matchers::path("identity/connect/token"))
675 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
676
677 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
679
680 let send_access_client = make_send_client(&mock_server);
682
683 let email_otp_credentials = SendEmailOtpCredentials {
685 email: "[email protected]".into(),
686 otp: "any_otp".into(),
687 };
688 let req = SendAccessTokenRequest {
689 send_id: "valid-send-id".into(),
690 send_access_credentials: Some(SendAccessCredentials::EmailOtp(
691 email_otp_credentials,
692 )),
693 };
694
695 let result = send_access_client.request_send_access_token(req).await;
696
697 assert!(result.is_err());
698
699 let err = result.unwrap_err();
700 match err {
701 SendAccessTokenError::Expected(api_err) => {
702 assert_eq!(
703 api_err,
704 SendAccessTokenApiErrorResponse::InvalidRequest {
705 send_access_error_type: Some(
706 SendAccessTokenInvalidRequestError::EmailAndOtpRequired
707 ),
708 error_description: Some(error_description),
709 }
710 );
711 }
712 other => panic!("expected Response variant, got {:?}", other),
713 }
714 }
715 }
716
717 mod request_send_access_token_invalid_grant_tests {
718
719 use super::*;
720
721 #[tokio::test]
722 async fn request_send_access_token_invalid_grant_invalid_send_id_error() {
723 let error_description = "send_id is invalid.".into();
725 let raw_error = serde_json::json!({
726 "error": "invalid_grant",
727 "error_description": error_description,
728 "send_access_error_type": "send_id_invalid"
729 });
730
731 let mock = Mock::given(matchers::method("POST"))
733 .and(matchers::path("identity/connect/token"))
734 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
735
736 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
738
739 let send_access_client = make_send_client(&mock_server);
741
742 let req = SendAccessTokenRequest {
744 send_id: "invalid-send-id".into(),
745 send_access_credentials: None, };
747
748 let result = send_access_client.request_send_access_token(req).await;
749
750 assert!(result.is_err());
751
752 let err = result.unwrap_err();
753 match err {
754 SendAccessTokenError::Expected(api_err) => {
755 assert_eq!(
757 api_err,
758 SendAccessTokenApiErrorResponse::InvalidGrant {
759 send_access_error_type: Some(
760 SendAccessTokenInvalidGrantError::SendIdInvalid
761 ),
762 error_description: Some(error_description),
763 }
764 );
765 }
766 other => panic!("expected Response variant, got {:?}", other),
767 }
768 }
769
770 #[tokio::test]
771 async fn request_send_access_token_invalid_grant_invalid_password_hash_error() {
772 let error_description = "password_hash_b64 is invalid.".into();
774 let raw_error = serde_json::json!({
775 "error": "invalid_grant",
776 "error_description": error_description,
777 "send_access_error_type": "password_hash_b64_invalid"
778 });
779
780 let mock = Mock::given(matchers::method("POST"))
782 .and(matchers::path("identity/connect/token"))
783 .respond_with(ResponseTemplate::new(400).set_body_json(raw_error));
784
785 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
787
788 let send_access_client = make_send_client(&mock_server);
790
791 let password_credentials = SendPasswordCredentials {
793 password_hash_b64: "invalid-hash".into(),
794 };
795
796 let req = SendAccessTokenRequest {
797 send_id: "valid-send-id".into(),
798 send_access_credentials: Some(SendAccessCredentials::Password(
799 password_credentials,
800 )),
801 };
802
803 let result = send_access_client.request_send_access_token(req).await;
804
805 assert!(result.is_err());
806
807 let err = result.unwrap_err();
808 match err {
809 SendAccessTokenError::Expected(api_err) => {
810 assert_eq!(
812 api_err,
813 SendAccessTokenApiErrorResponse::InvalidGrant {
814 send_access_error_type: Some(
815 SendAccessTokenInvalidGrantError::PasswordHashB64Invalid
816 ),
817 error_description: Some(error_description),
818 }
819 );
820 }
821 other => panic!("expected Response variant, got {:?}", other),
822 }
823 }
824 }
825
826 mod request_send_access_token_unexpected_error_tests {
827
828 use super::*;
829
830 async fn run_case(status_code: u16, reason: &str) {
831 let mock = Mock::given(matchers::method("POST"))
832 .and(matchers::path("identity/connect/token"))
833 .respond_with(ResponseTemplate::new(status_code));
834
835 let (mock_server, _api_config) = start_api_mock(vec![mock]).await;
836 let send_access_client = make_send_client(&mock_server);
837
838 let req = SendAccessTokenRequest {
839 send_id: "test_send_id".into(),
840 send_access_credentials: None,
841 };
842
843 let result = send_access_client.request_send_access_token(req).await;
844
845 assert!(result.is_err());
846
847 let err = result.expect_err(&format!(
848 "expected Err for status {} {} against http://{}/identity/connect/token",
849 status_code,
850 reason,
851 mock_server.address()
852 ));
853
854 match err {
855 SendAccessTokenError::Unexpected(api_err) => {
856 let expected = UnexpectedIdentityError(format!(
857 "Received response status {} {} against http://{}/identity/connect/token",
858 status_code,
859 reason,
860 mock_server.address()
861 ));
862 assert_eq!(api_err, expected, "mismatch for status {}", status_code);
863 }
864 other => panic!("expected Unexpected variant, got {:?}", other),
865 }
866 }
867
868 #[tokio::test]
869 async fn request_send_access_token_unexpected_statuses() {
870 let cases = [
871 (401, "Unauthorized"),
874 (402, "Payment Required"),
875 (403, "Forbidden"),
876 (404, "Not Found"),
877 (405, "Method Not Allowed"),
878 (406, "Not Acceptable"),
879 (407, "Proxy Authentication Required"),
880 (408, "Request Timeout"),
881 (409, "Conflict"),
882 (410, "Gone"),
883 (411, "Length Required"),
884 (412, "Precondition Failed"),
885 (413, "Payload Too Large"),
886 (414, "URI Too Long"),
887 (415, "Unsupported Media Type"),
888 (416, "Range Not Satisfiable"),
889 (417, "Expectation Failed"),
890 (421, "Misdirected Request"),
891 (422, "Unprocessable Entity"),
892 (423, "Locked"),
893 (424, "Failed Dependency"),
894 (425, "Too Early"),
895 (426, "Upgrade Required"),
896 (428, "Precondition Required"),
897 (429, "Too Many Requests"),
898 (431, "Request Header Fields Too Large"),
899 (451, "Unavailable For Legal Reasons"),
900 (500, "Internal Server Error"),
902 (501, "Not Implemented"),
903 (502, "Bad Gateway"),
904 (503, "Service Unavailable"),
905 (504, "Gateway Timeout"),
906 (505, "HTTP Version Not Supported"),
907 (506, "Variant Also Negotiates"),
908 (507, "Insufficient Storage"),
909 (508, "Loop Detected"),
910 (510, "Not Extended"),
911 (511, "Network Authentication Required"),
912 ];
913
914 for (code, reason) in cases {
915 run_case(code, reason).await;
916 }
917 }
918 }
919}