bitwarden_emergency_access/
list_trusted.rs1use bitwarden_core::{ApiError, MissingFieldError};
2use bitwarden_error::bitwarden_error;
3use thiserror::Error;
4
5use crate::{EmergencyAccessClient, GranteeEmergencyAccess};
6
7#[bitwarden_error(flat)]
9#[derive(Debug, Error)]
10pub enum EmergencyAccessListTrustedError {
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_trusted(
25 &self,
26 ) -> Result<Vec<GranteeEmergencyAccess>, EmergencyAccessListTrustedError> {
27 let response = self
28 .api_configurations
29 .api_client
30 .emergency_access_api()
31 .get_contacts()
32 .await?;
33
34 Ok(response
36 .data
37 .unwrap_or_default()
38 .into_iter()
39 .map(GranteeEmergencyAccess::try_from)
40 .collect::<Result<_, _>>()?)
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use bitwarden_api_api::models::{
47 EmergencyAccessGranteeDetailsResponseModel,
48 EmergencyAccessGranteeDetailsResponseModelListResponseModel, EmergencyAccessStatusType,
49 EmergencyAccessType as ApiEmergencyAccessType,
50 };
51 use bitwarden_core::client::test_accounts::test_bitwarden_com_account;
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_GRANTEE_ID: &str = "060000fb-0922-4dd3-b170-6e15cb5df8c8";
60
61 fn invited() -> EmergencyAccessGranteeDetailsResponseModel {
62 EmergencyAccessGranteeDetailsResponseModel {
63 id: Some(TEST_EMERGENCY_ACCESS_ID.parse().unwrap()),
64 status: Some(EmergencyAccessStatusType::Invited),
65 r#type: Some(ApiEmergencyAccessType::View),
66 wait_time_days: Some(7),
67 email: Some("[email protected]".to_owned()),
68 ..Default::default()
69 }
70 }
71
72 fn confirmed() -> EmergencyAccessGranteeDetailsResponseModel {
73 EmergencyAccessGranteeDetailsResponseModel {
74 id: Some(TEST_EMERGENCY_ACCESS_ID.parse().unwrap()),
75 status: Some(EmergencyAccessStatusType::Confirmed),
76 r#type: Some(ApiEmergencyAccessType::Takeover),
77 wait_time_days: Some(1),
78 grantee_id: Some(TEST_GRANTEE_ID.parse().unwrap()),
79 name: Some("Grantee".to_owned()),
80 email: Some("[email protected]".to_owned()),
81 avatar_color: Some("#175ddc".to_owned()),
82 ..Default::default()
83 }
84 }
85
86 async fn list(
87 data: Option<Vec<EmergencyAccessGranteeDetailsResponseModel>>,
88 ) -> Result<Vec<GranteeEmergencyAccess>, EmergencyAccessListTrustedError> {
89 let response = EmergencyAccessGranteeDetailsResponseModelListResponseModel {
90 data,
91 ..Default::default()
92 };
93
94 let client = test_client(test_bitwarden_com_account(), move |mock| {
95 mock.emergency_access_api
96 .expect_get_contacts()
97 .returning(move || Ok(response.clone()))
98 .once();
99 })
100 .await;
101
102 client.emergency_access().list_trusted().await
103 }
104
105 #[tokio::test]
106 async fn maps_trusted_contacts() {
107 let result = list(Some(vec![invited(), confirmed()])).await.unwrap();
108
109 assert_eq!(
110 result,
111 [
112 GranteeEmergencyAccess {
113 id: test_id(),
114 grantee_id: None,
115 name: None,
116 email: Some("[email protected]".to_owned()),
117 r#type: EmergencyAccessType::View,
118 status: EmergencyAccessStatus::Invited,
119 wait_time_days: 7,
120 avatar_color: None,
121 },
122 GranteeEmergencyAccess {
123 id: test_id(),
124 grantee_id: Some(TEST_GRANTEE_ID.parse().unwrap()),
125 name: Some("Grantee".to_owned()),
126 email: Some("[email protected]".to_owned()),
127 r#type: EmergencyAccessType::Takeover,
128 status: EmergencyAccessStatus::Confirmed,
129 wait_time_days: 1,
130 avatar_color: Some("#175ddc".to_owned()),
131 },
132 ]
133 );
134 }
135
136 #[tokio::test]
137 async fn returns_empty_list_when_data_is_missing() {
138 assert!(list(None).await.unwrap().is_empty());
139 }
140
141 #[tokio::test]
142 async fn fails_when_status_is_unknown() {
143 let unknown = EmergencyAccessGranteeDetailsResponseModel {
144 status: Some(EmergencyAccessStatusType::__Unknown(42)),
145 ..confirmed()
146 };
147
148 let result = list(Some(vec![unknown])).await;
149
150 assert!(matches!(
151 result,
152 Err(EmergencyAccessListTrustedError::MissingField(_))
153 ));
154 }
155
156 #[tokio::test]
157 async fn fails_when_request_fails() {
158 let client = test_client(test_bitwarden_com_account(), |mock| {
159 mock.emergency_access_api
160 .expect_get_contacts()
161 .returning(api_error)
162 .once();
163 })
164 .await;
165
166 let result = client.emergency_access().list_trusted().await;
167
168 assert!(matches!(
169 result,
170 Err(EmergencyAccessListTrustedError::Api(_))
171 ));
172 }
173}