bitwarden_sm/projects/
update.rs1use bitwarden_api_api::models::ProjectUpdateRequestModel;
2use bitwarden_core::{OrganizationId, key_management::SymmetricKeyId};
3use bitwarden_crypto::PrimitiveEncryptable;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7use validator::Validate;
8
9use crate::{
10 SecretsManagerClient,
11 error::{SecretsManagerError, validate_only_whitespaces},
12 projects::ProjectResponse,
13};
14
15#[allow(missing_docs)]
16#[derive(Serialize, Deserialize, Debug, JsonSchema, Validate)]
17#[serde(rename_all = "camelCase", deny_unknown_fields)]
18pub struct ProjectPutRequest {
19 pub id: Uuid,
21 pub organization_id: Uuid,
23 #[validate(length(min = 1, max = 500), custom(function = validate_only_whitespaces))]
24 pub name: String,
25}
26
27pub(crate) async fn update_project(
28 client: &SecretsManagerClient,
29 input: &ProjectPutRequest,
30) -> Result<ProjectResponse, SecretsManagerError> {
31 let client = client.client();
32 input.validate()?;
33
34 let key_store = client.internal.get_key_store();
35 let key = SymmetricKeyId::Organization(OrganizationId::new(input.organization_id));
36
37 let project = Some(ProjectUpdateRequestModel {
38 name: input
39 .name
40 .clone()
41 .trim()
42 .encrypt(&mut key_store.context(), key)?
43 .to_string(),
44 });
45
46 let config = client.internal.get_api_configurations();
47 let res = config
48 .api_client
49 .projects_api()
50 .update(input.id, project)
51 .await?;
52
53 ProjectResponse::process_response(res, &mut key_store.context())
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 async fn update_project(name: String) -> Result<ProjectResponse, SecretsManagerError> {
61 let input = ProjectPutRequest {
62 id: Uuid::new_v4(),
63 organization_id: Uuid::new_v4(),
64 name,
65 };
66
67 super::update_project(&SecretsManagerClient::new(None), &input).await
68 }
69
70 #[tokio::test]
71 async fn test_update_project_request_name_empty_string() {
72 let response = update_project("".into()).await;
73 assert!(response.is_err());
74 assert_eq!(
75 response.err().unwrap().to_string(),
76 "name must not be empty"
77 );
78 }
79
80 #[tokio::test]
81 async fn test_update_project_request_name_all_whitespaces_space() {
82 let response = update_project(" ".into()).await;
83 assert!(response.is_err());
84 assert_eq!(
85 response.err().unwrap().to_string(),
86 "name must not contain only whitespaces"
87 );
88 }
89
90 #[tokio::test]
91 async fn test_update_project_request_name_all_whitespaces_tab() {
92 let response = update_project("\t".into()).await;
93 assert!(response.is_err());
94 assert_eq!(
95 response.err().unwrap().to_string(),
96 "name must not contain only whitespaces"
97 );
98 }
99
100 #[tokio::test]
101 async fn test_update_project_request_name_all_whitespaces_newline() {
102 let response = update_project("\n".into()).await;
103 assert!(response.is_err());
104 assert_eq!(
105 response.err().unwrap().to_string(),
106 "name must not contain only whitespaces"
107 );
108 }
109
110 #[tokio::test]
111 async fn test_update_project_request_name_all_whitespaces_combined() {
112 let response = update_project(" \t\n".into()).await;
113 assert!(response.is_err());
114 assert_eq!(
115 response.err().unwrap().to_string(),
116 "name must not contain only whitespaces"
117 );
118 }
119
120 #[tokio::test]
121 async fn test_update_project_request_name_501_character_length() {
122 let response = update_project("a".repeat(501)).await;
123 assert!(response.is_err());
124 assert_eq!(
125 response.err().unwrap().to_string(),
126 "name must not exceed 500 characters in length"
127 );
128 }
129}