Skip to main content

busbar_sf_rest/client/
user_password.rs

1use tracing::instrument;
2
3use busbar_sf_client::security::url as url_security;
4
5use crate::error::{Error, ErrorKind, Result};
6use crate::user_password::{SetPasswordRequest, SetPasswordResponse, UserPasswordStatus};
7
8impl super::SalesforceRestClient {
9    /// Get the password expiration status for a user.
10    #[instrument(skip(self))]
11    pub async fn get_user_password_status(&self, user_id: &str) -> Result<UserPasswordStatus> {
12        if !url_security::is_valid_salesforce_id(user_id) {
13            return Err(Error::new(ErrorKind::Salesforce {
14                error_code: "INVALID_ID".to_string(),
15                message: "Invalid Salesforce ID format".to_string(),
16            }));
17        }
18        let path = format!("sobjects/User/{}/password", user_id);
19        self.client.rest_get(&path).await.map_err(Into::into)
20    }
21
22    /// Set a user's password.
23    #[instrument(skip(self, request))]
24    pub async fn set_user_password(
25        &self,
26        user_id: &str,
27        request: &SetPasswordRequest,
28    ) -> Result<()> {
29        if !url_security::is_valid_salesforce_id(user_id) {
30            return Err(Error::new(ErrorKind::Salesforce {
31                error_code: "INVALID_ID".to_string(),
32                message: "Invalid Salesforce ID format".to_string(),
33            }));
34        }
35        let path = format!("sobjects/User/{}/password", user_id);
36        let _: serde_json::Value = self.client.rest_post(&path, request).await?;
37        Ok(())
38    }
39
40    /// Reset a user's password.
41    ///
42    /// Salesforce generates a new password and returns it in the response.
43    /// This uses DELETE to trigger the reset.
44    #[instrument(skip(self))]
45    pub async fn reset_user_password(&self, user_id: &str) -> Result<SetPasswordResponse> {
46        if !url_security::is_valid_salesforce_id(user_id) {
47            return Err(Error::new(ErrorKind::Salesforce {
48                error_code: "INVALID_ID".to_string(),
49                message: "Invalid Salesforce ID format".to_string(),
50            }));
51        }
52        let path = format!("sobjects/User/{}/password", user_id);
53        let url = self.client.rest_url(&path);
54        let request = self.client.delete(&url);
55        let response = self.client.execute(request).await?;
56        response.json().await.map_err(Into::into)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::super::SalesforceRestClient;
63
64    #[tokio::test]
65    async fn test_get_user_password_status_invalid_id() {
66        let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
67        let result = client.get_user_password_status("bad-id").await;
68        assert!(result.is_err());
69        assert!(result.unwrap_err().to_string().contains("INVALID_ID"));
70    }
71
72    #[tokio::test]
73    async fn test_set_user_password_invalid_id() {
74        let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
75        let request = crate::user_password::SetPasswordRequest {
76            new_password: "NewPass123!".to_string(),
77        };
78        let result = client.set_user_password("bad-id", &request).await;
79        assert!(result.is_err());
80        assert!(result.unwrap_err().to_string().contains("INVALID_ID"));
81    }
82
83    #[tokio::test]
84    async fn test_reset_user_password_invalid_id() {
85        let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
86        let result = client.reset_user_password("bad-id").await;
87        assert!(result.is_err());
88        assert!(result.unwrap_err().to_string().contains("INVALID_ID"));
89    }
90
91    #[tokio::test]
92    async fn test_get_user_password_status_wiremock() {
93        use wiremock::matchers::{method, path_regex};
94        use wiremock::{Mock, MockServer, ResponseTemplate};
95
96        let mock_server = MockServer::start().await;
97
98        let body = serde_json::json!({"isExpired": false});
99
100        Mock::given(method("GET"))
101            .and(path_regex(".*/sobjects/User/005xx000001Svf0AAC/password$"))
102            .respond_with(ResponseTemplate::new(200).set_body_json(&body))
103            .mount(&mock_server)
104            .await;
105
106        let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
107        let result = client
108            .get_user_password_status("005xx000001Svf0AAC")
109            .await
110            .expect("get_user_password_status should succeed");
111        assert!(!result.is_expired);
112    }
113
114    #[tokio::test]
115    async fn test_reset_user_password_wiremock() {
116        use wiremock::matchers::{method, path_regex};
117        use wiremock::{Mock, MockServer, ResponseTemplate};
118
119        let mock_server = MockServer::start().await;
120
121        let body = serde_json::json!({"NewPassword": "AutoGenerated123!"});
122
123        Mock::given(method("DELETE"))
124            .and(path_regex(".*/sobjects/User/005xx000001Svf0AAC/password$"))
125            .respond_with(ResponseTemplate::new(200).set_body_json(&body))
126            .mount(&mock_server)
127            .await;
128
129        let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
130        let result = client
131            .reset_user_password("005xx000001Svf0AAC")
132            .await
133            .expect("reset_user_password should succeed");
134        assert_eq!(result.new_password, "AutoGenerated123!");
135    }
136}