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            data: None,
81            max_access_count: None,
82            access_count: 0,
83            disabled: false,
84            hide_email: false,
85            revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
86            deletion_date: "2025-01-10T00:00:00Z".parse().unwrap(),
87            expiration_date: None,
88            emails: Vec::new(),
89            auth_type: AuthType::None,
90        };
91        let mut send = store.encrypt(send_view).unwrap();
92        send.id = Some(SendId::new(send_id));
93        repository.set(SendId::new(send_id), send).await.unwrap();
94
95        (store, repository)
96    }
97
98    #[tokio::test]
99    async fn test_delete_send() {
100        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
101        let (_store, repository) = make_store_with_send(send_id).await;
102
103        let api_client = ApiClient::new_mocked(move |mock| {
104            mock.sends_api
105                .expect_delete()
106                .returning(move |_id| Ok(()))
107                .once();
108        });
109
110        let result = delete_send(&api_client, &repository, SendId::new(send_id)).await;
111
112        assert!(result.is_ok());
113        assert!(
114            repository
115                .get(SendId::new(send_id))
116                .await
117                .unwrap()
118                .is_none()
119        );
120    }
121
122    #[tokio::test]
123    async fn test_delete_send_http_error() {
124        let send_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
125        let (_store, repository) = make_store_with_send(send_id).await;
126
127        let api_client = ApiClient::new_mocked(move |mock| {
128            mock.sends_api
129                .expect_delete()
130                .returning(move |_id| {
131                    Err(bitwarden_api_api::ApiError::Io(std::io::Error::other(
132                        "Simulated error",
133                    )))
134                })
135                .once();
136        });
137
138        let result = delete_send(&api_client, &repository, SendId::new(send_id)).await;
139
140        assert!(result.is_err());
141        assert!(matches!(result.unwrap_err(), DeleteSendError::Api(_)));
142        // Send should still be in the repository since API call failed
143        assert!(
144            repository
145                .get(SendId::new(send_id))
146                .await
147                .unwrap()
148                .is_some()
149        );
150    }
151}