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