Skip to main content

bitwarden_send/
delete.rs

1use bitwarden_core::ApiError;
2use bitwarden_error::bitwarden_error;
3use bitwarden_state::repository::{Repository, RepositoryError};
4use thiserror::Error;
5#[cfg(feature = "wasm")]
6use wasm_bindgen::prelude::*;
7
8use crate::{Send, SendId, send_client::SendClient};
9
10#[allow(missing_docs)]
11#[bitwarden_error(flat)]
12#[derive(Debug, Error)]
13pub enum DeleteSendError {
14    #[error(transparent)]
15    Api(#[from] ApiError),
16    #[error(transparent)]
17    Repository(#[from] RepositoryError),
18}
19
20async fn delete_send<R: Repository<Send> + ?Sized>(
21    api_client: &bitwarden_api_api::apis::ApiClient,
22    repository: &R,
23    send_id: SendId,
24) -> Result<(), DeleteSendError> {
25    api_client
26        .sends_api()
27        .delete(&send_id.to_string())
28        .await
29        .map_err(ApiError::from)?;
30
31    repository.remove(send_id).await?;
32
33    Ok(())
34}
35
36#[cfg_attr(feature = "wasm", wasm_bindgen)]
37impl SendClient {
38    /// Delete a [Send] from the server and remove it from local state.
39    pub async fn delete(&self, send_id: SendId) -> Result<(), DeleteSendError> {
40        let config = self.client.internal.get_api_configurations();
41        let repository = self.get_repository()?;
42
43        delete_send(&config.api_client, repository.as_ref(), send_id).await
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use bitwarden_api_api::apis::ApiClient;
50    use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
51    use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
52    use bitwarden_test::MemoryRepository;
53    use uuid::uuid;
54
55    use super::*;
56    use crate::{AuthType, Send, SendId, SendTextView, SendType, SendView};
57
58    async fn make_store_with_send(
59        send_id: uuid::Uuid,
60    ) -> (KeyStore<KeySlotIds>, MemoryRepository<Send>) {
61        let store: KeyStore<KeySlotIds> = KeyStore::default();
62        {
63            let mut ctx = store.context_mut();
64            let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
65            ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
66                .unwrap();
67        }
68
69        let repository = MemoryRepository::<Send>::default();
70        let send_view = SendView {
71            id: None,
72            access_id: None,
73            name: "Test Send".to_string(),
74            notes: None,
75            key: None,
76            new_password: None,
77            has_password: false,
78            r#type: SendType::Text,
79            file: None,
80            text: Some(SendTextView {
81                text: Some("Secret text".to_string()),
82                hidden: false,
83            }),
84            max_access_count: None,
85            access_count: 0,
86            disabled: false,
87            hide_email: false,
88            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
89            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
90            expiration_date: None,
91            emails: Vec::new(),
92            auth_type: AuthType::None,
93        };
94        let mut send = store.encrypt(send_view).unwrap();
95        send.id = Some(SendId::new(send_id));
96        repository.set(SendId::new(send_id), send).await.unwrap();
97
98        (store, repository)
99    }
100
101    #[tokio::test]
102    async fn test_delete_send() {
103        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
104        let (_store, repository) = make_store_with_send(send_id).await;
105
106        let api_client = ApiClient::new_mocked(move |mock| {
107            mock.sends_api
108                .expect_delete()
109                .returning(move |_id| Ok(()))
110                .once();
111        });
112
113        let result = delete_send(&api_client, &repository, SendId::new(send_id)).await;
114
115        assert!(result.is_ok());
116        assert!(
117            repository
118                .get(SendId::new(send_id))
119                .await
120                .unwrap()
121                .is_none()
122        );
123    }
124
125    #[tokio::test]
126    async fn test_delete_send_http_error() {
127        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
128        let (_store, repository) = make_store_with_send(send_id).await;
129
130        let api_client = ApiClient::new_mocked(move |mock| {
131            mock.sends_api
132                .expect_delete()
133                .returning(move |_id| {
134                    Err(bitwarden_api_api::apis::Error::Io(std::io::Error::other(
135                        "Simulated error",
136                    )))
137                })
138                .once();
139        });
140
141        let result = delete_send(&api_client, &repository, SendId::new(send_id)).await;
142
143        assert!(result.is_err());
144        assert!(matches!(result.unwrap_err(), DeleteSendError::Api(_)));
145        // Send should still be in the repository since API call failed
146        assert!(
147            repository
148                .get(SendId::new(send_id))
149                .await
150                .unwrap()
151                .is_some()
152        );
153    }
154}