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