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