bitwarden_emergency_access/
list_granted.rs1use bitwarden_core::{ApiError, MissingFieldError};
2use bitwarden_error::bitwarden_error;
3use thiserror::Error;
4
5use crate::{EmergencyAccessClient, GrantorEmergencyAccess};
6
7#[bitwarden_error(flat)]
9#[derive(Debug, Error)]
10pub enum EmergencyAccessListGrantedError {
11 #[error(transparent)]
13 Api(#[from] ApiError),
14 #[error(transparent)]
16 MissingField(#[from] MissingFieldError),
17}
18
19#[bitwarden_ffi::wasm_export]
20impl EmergencyAccessClient {
21 pub async fn list_granted(
25 &self,
26 ) -> Result<Vec<GrantorEmergencyAccess>, EmergencyAccessListGrantedError> {
27 let response = self
28 .api_configurations
29 .api_client
30 .emergency_access_api()
31 .get_grantees()
32 .await?;
33
34 Ok(response
36 .data
37 .unwrap_or_default()
38 .into_iter()
39 .map(GrantorEmergencyAccess::try_from)
40 .collect::<Result<_, _>>()?)
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use bitwarden_api_api::models::{
47 EmergencyAccessGrantorDetailsResponseModel,
48 EmergencyAccessGrantorDetailsResponseModelListResponseModel, EmergencyAccessStatusType,
49 EmergencyAccessType as ApiEmergencyAccessType,
50 };
51 use bitwarden_core::client::test_accounts::test_bitwarden_com_account_v2;
52
53 use super::*;
54 use crate::{
55 EmergencyAccessClientExt, EmergencyAccessStatus, EmergencyAccessType,
56 test_support::{TEST_EMERGENCY_ACCESS_ID, api_error, test_client, test_id},
57 };
58
59 const TEST_GRANTOR_ID: &str = "3ed3b8a2-6a1e-4e8b-9b6f-1c2d3e4f5a6b";
60
61 fn approved() -> EmergencyAccessGrantorDetailsResponseModel {
62 EmergencyAccessGrantorDetailsResponseModel {
63 id: Some(TEST_EMERGENCY_ACCESS_ID.parse().unwrap()),
64 status: Some(EmergencyAccessStatusType::RecoveryApproved),
65 r#type: Some(ApiEmergencyAccessType::Takeover),
66 wait_time_days: Some(2),
67 grantor_id: Some(TEST_GRANTOR_ID.parse().unwrap()),
68 name: Some("Grantor".to_owned()),
69 email: Some("[email protected]".to_owned()),
70 avatar_color: None,
71 ..Default::default()
72 }
73 }
74
75 async fn list(
76 data: Option<Vec<EmergencyAccessGrantorDetailsResponseModel>>,
77 ) -> Result<Vec<GrantorEmergencyAccess>, EmergencyAccessListGrantedError> {
78 let response = EmergencyAccessGrantorDetailsResponseModelListResponseModel {
79 data,
80 ..Default::default()
81 };
82
83 let client = test_client(test_bitwarden_com_account_v2(), move |mock| {
84 mock.emergency_access_api
85 .expect_get_grantees()
86 .returning(move || Ok(response.clone()))
87 .once();
88 })
89 .await;
90
91 client.emergency_access().list_granted().await
92 }
93
94 #[tokio::test]
95 async fn maps_grantors() {
96 let result = list(Some(vec![approved()])).await.unwrap();
97
98 assert_eq!(
99 result,
100 [GrantorEmergencyAccess {
101 id: test_id(),
102 grantor_id: TEST_GRANTOR_ID.parse().unwrap(),
103 name: Some("Grantor".to_owned()),
104 email: Some("[email protected]".to_owned()),
105 r#type: EmergencyAccessType::Takeover,
106 status: EmergencyAccessStatus::RecoveryApproved,
107 wait_time_days: 2,
108 avatar_color: None,
109 }]
110 );
111 }
112
113 #[tokio::test]
114 async fn returns_empty_list_when_data_is_missing() {
115 assert!(list(None).await.unwrap().is_empty());
116 }
117
118 #[tokio::test]
119 async fn fails_when_grantor_id_is_missing() {
120 let missing = EmergencyAccessGrantorDetailsResponseModel {
121 grantor_id: None,
122 ..approved()
123 };
124
125 let result = list(Some(vec![missing])).await;
126
127 assert!(matches!(
128 result,
129 Err(EmergencyAccessListGrantedError::MissingField(_))
130 ));
131 }
132
133 #[tokio::test]
134 async fn fails_when_request_fails() {
135 let client = test_client(test_bitwarden_com_account_v2(), |mock| {
136 mock.emergency_access_api
137 .expect_get_grantees()
138 .returning(api_error)
139 .once();
140 })
141 .await;
142
143 let result = client.emergency_access().list_granted().await;
144
145 assert!(matches!(
146 result,
147 Err(EmergencyAccessListGrantedError::Api(_))
148 ));
149 }
150}