busbar_sf_rest/client/
sync.rs1use tracing::instrument;
2
3use busbar_sf_client::security::soql;
4
5use crate::error::{Error, ErrorKind, Result};
6
7impl super::SalesforceRestClient {
8 #[instrument(skip(self))]
13 pub async fn get_deleted(
14 &self,
15 sobject: &str,
16 start: &str,
17 end: &str,
18 ) -> Result<super::GetDeletedResult> {
19 if !soql::is_safe_sobject_name(sobject) {
20 return Err(Error::new(ErrorKind::Salesforce {
21 error_code: "INVALID_SOBJECT".to_string(),
22 message: "Invalid SObject name".to_string(),
23 }));
24 }
25 let path = format!(
26 "sobjects/{}/deleted/?start={}&end={}",
27 sobject,
28 urlencoding::encode(start),
29 urlencoding::encode(end)
30 );
31 self.client.rest_get(&path).await.map_err(Into::into)
32 }
33
34 #[instrument(skip(self))]
39 pub async fn get_updated(
40 &self,
41 sobject: &str,
42 start: &str,
43 end: &str,
44 ) -> Result<super::GetUpdatedResult> {
45 if !soql::is_safe_sobject_name(sobject) {
46 return Err(Error::new(ErrorKind::Salesforce {
47 error_code: "INVALID_SOBJECT".to_string(),
48 message: "Invalid SObject name".to_string(),
49 }));
50 }
51 let path = format!(
52 "sobjects/{}/updated/?start={}&end={}",
53 sobject,
54 urlencoding::encode(start),
55 urlencoding::encode(end)
56 );
57 self.client.rest_get(&path).await.map_err(Into::into)
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::super::SalesforceRestClient;
64
65 #[tokio::test]
66 async fn test_get_deleted_invalid_sobject() {
67 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
68 let result = client
69 .get_deleted(
70 "Bad'; DROP--",
71 "2024-01-01T00:00:00Z",
72 "2024-01-15T00:00:00Z",
73 )
74 .await;
75 assert!(result.is_err());
76 assert!(result.unwrap_err().to_string().contains("INVALID_SOBJECT"));
77 }
78
79 #[tokio::test]
80 async fn test_get_updated_invalid_sobject() {
81 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
82 let result = client
83 .get_updated(
84 "Bad'; DROP--",
85 "2024-01-01T00:00:00Z",
86 "2024-01-15T00:00:00Z",
87 )
88 .await;
89 assert!(result.is_err());
90 assert!(result.unwrap_err().to_string().contains("INVALID_SOBJECT"));
91 }
92
93 #[tokio::test]
94 async fn test_get_deleted_wiremock() {
95 use wiremock::matchers::{method, path_regex};
96 use wiremock::{Mock, MockServer, ResponseTemplate};
97
98 let mock_server = MockServer::start().await;
99
100 let body = serde_json::json!({
101 "deletedRecords": [
102 {"id": "001xx000003DgAAAS", "deletedDate": "2024-01-15T10:30:00.000Z"}
103 ],
104 "earliestDateAvailable": "2024-01-01T00:00:00.000Z",
105 "latestDateCovered": "2024-01-15T23:59:59.000Z"
106 });
107
108 Mock::given(method("GET"))
109 .and(path_regex(".*/sobjects/Account/deleted/.*"))
110 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
111 .mount(&mock_server)
112 .await;
113
114 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
115 let result = client
116 .get_deleted("Account", "2024-01-01T00:00:00Z", "2024-01-15T00:00:00Z")
117 .await
118 .expect("get_deleted should succeed");
119 assert_eq!(result.deleted_records.len(), 1);
120 assert_eq!(result.deleted_records[0].id, "001xx000003DgAAAS");
121 }
122
123 #[tokio::test]
124 async fn test_get_updated_wiremock() {
125 use wiremock::matchers::{method, path_regex};
126 use wiremock::{Mock, MockServer, ResponseTemplate};
127
128 let mock_server = MockServer::start().await;
129
130 let body = serde_json::json!({
131 "ids": ["001xx000003DgAAAS", "001xx000003DgBBAS"],
132 "latestDateCovered": "2024-01-15T23:59:59.000Z"
133 });
134
135 Mock::given(method("GET"))
136 .and(path_regex(".*/sobjects/Account/updated/.*"))
137 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
138 .mount(&mock_server)
139 .await;
140
141 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
142 let result = client
143 .get_updated("Account", "2024-01-01T00:00:00Z", "2024-01-15T00:00:00Z")
144 .await
145 .expect("get_updated should succeed");
146 assert_eq!(result.ids.len(), 2);
147 }
148}