Skip to main content

bitwarden_vault/cipher/attachment_client/
renew.rs

1use bitwarden_core::{ApiError, MissingFieldError};
2use bitwarden_error::bitwarden_error;
3use thiserror::Error;
4
5use crate::{AttachmentsClient, CipherId};
6
7#[allow(missing_docs)]
8#[bitwarden_error(flat)]
9#[derive(Debug, Error)]
10pub enum CipherRenewFileUploadUrlError {
11    #[error(transparent)]
12    Api(#[from] ApiError),
13    #[error(transparent)]
14    MissingField(#[from] MissingFieldError),
15}
16
17#[bitwarden_ffi::wasm_export]
18impl AttachmentsClient {
19    /// Returns a renewed upload URL for an attachment.
20    /// Does not modify the attachment slot.
21    pub async fn renew_file_upload_url(
22        &self,
23        cipher_id: CipherId,
24        attachment_id: String,
25    ) -> Result<String, CipherRenewFileUploadUrlError> {
26        let response = self
27            .api_configurations
28            .api_client
29            .ciphers_api()
30            .renew_file_upload_url(cipher_id.into(), &attachment_id)
31            .await?;
32
33        response.url.ok_or_else(|| MissingFieldError("url").into())
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use std::sync::Arc;
40
41    use bitwarden_api_api::{apis::ApiClient, models::AttachmentUploadDataResponseModel};
42    use bitwarden_core::{client::ApiConfigurations, key_management::KeySlotIds};
43    use bitwarden_crypto::KeyStore;
44    use reqwest::StatusCode;
45
46    use super::*;
47
48    const TEST_CIPHER_ID: &str = "5faa9684-c793-4a2d-8a12-b33900187097";
49    const TEST_ATTACHMENT_ID: &str = "uf7bkexzag04d3cw04jsbqqkbpbwhxs0";
50    const TEST_RENEW_URL: &str = "http://localhost:4000/attachments/test/renewed";
51
52    fn client_with_api(api_client: ApiClient) -> AttachmentsClient {
53        AttachmentsClient {
54            key_store: KeyStore::<KeySlotIds>::default(),
55            api_configurations: Arc::new(ApiConfigurations::from_api_client(api_client)),
56            repository: None,
57            http_client: reqwest::Client::new(),
58        }
59    }
60
61    #[tokio::test]
62    async fn returns_url_from_api_response() {
63        let api_client = ApiClient::new_mocked(|mock| {
64            mock.ciphers_api
65                .expect_renew_file_upload_url()
66                .returning(|id, attachment_id| {
67                    assert_eq!(&id.to_string(), TEST_CIPHER_ID);
68                    assert_eq!(attachment_id, TEST_ATTACHMENT_ID);
69                    Ok(AttachmentUploadDataResponseModel {
70                        url: Some(TEST_RENEW_URL.to_string()),
71                        ..Default::default()
72                    })
73                });
74        });
75
76        let client = client_with_api(api_client);
77        let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
78        let url = client
79            .renew_file_upload_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
80            .await
81            .unwrap();
82
83        assert_eq!(url, TEST_RENEW_URL);
84    }
85
86    #[tokio::test]
87    async fn returns_missing_field_when_response_has_no_url() {
88        let api_client = ApiClient::new_mocked(|mock| {
89            mock.ciphers_api
90                .expect_renew_file_upload_url()
91                .returning(|_id, _attachment_id| {
92                    Ok(AttachmentUploadDataResponseModel {
93                        url: None,
94                        ..Default::default()
95                    })
96                });
97        });
98
99        let client = client_with_api(api_client);
100        let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
101        let err = client
102            .renew_file_upload_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
103            .await
104            .unwrap_err();
105
106        assert!(matches!(
107            err,
108            CipherRenewFileUploadUrlError::MissingField(_)
109        ));
110    }
111
112    #[tokio::test]
113    async fn propagates_api_errors() {
114        let api_client = ApiClient::new_mocked(|mock| {
115            mock.ciphers_api
116                .expect_renew_file_upload_url()
117                .returning(|_id, _attachment_id| {
118                    Err(bitwarden_api_api::ApiError::Response(
119                        bitwarden_api_api::ResponseContent {
120                            status: StatusCode::INTERNAL_SERVER_ERROR,
121                            message: "boom".to_string(),
122                        },
123                    ))
124                });
125        });
126
127        let client = client_with_api(api_client);
128        let cipher_id: CipherId = TEST_CIPHER_ID.parse().unwrap();
129        let err = client
130            .renew_file_upload_url(cipher_id, TEST_ATTACHMENT_ID.to_string())
131            .await
132            .unwrap_err();
133
134        assert!(matches!(err, CipherRenewFileUploadUrlError::Api(_)));
135    }
136}