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