1use serde::de::DeserializeOwned;
2use tracing::instrument;
3
4use busbar_sf_client::security::{soql, url as url_security};
5
6use crate::error::{Error, ErrorKind, Result};
7use crate::list_views::{ListView, ListViewCollection, ListViewDescribe, ListViewResult};
8
9impl super::SalesforceRestClient {
10 #[instrument(skip(self))]
12 pub async fn list_views(&self, sobject: &str) -> Result<ListViewCollection> {
13 if !soql::is_safe_sobject_name(sobject) {
14 return Err(Error::new(ErrorKind::Salesforce {
15 error_code: "INVALID_SOBJECT".to_string(),
16 message: "Invalid SObject name".to_string(),
17 }));
18 }
19 let path = format!("sobjects/{}/listviews", sobject);
20 self.client.rest_get(&path).await.map_err(Into::into)
21 }
22
23 #[instrument(skip(self))]
25 pub async fn get_list_view(&self, sobject: &str, list_view_id: &str) -> Result<ListView> {
26 if !soql::is_safe_sobject_name(sobject) {
27 return Err(Error::new(ErrorKind::Salesforce {
28 error_code: "INVALID_SOBJECT".to_string(),
29 message: "Invalid SObject name".to_string(),
30 }));
31 }
32 if !url_security::is_valid_salesforce_id(list_view_id) {
33 return Err(Error::new(ErrorKind::Salesforce {
34 error_code: "INVALID_ID".to_string(),
35 message: "Invalid Salesforce ID format".to_string(),
36 }));
37 }
38 let path = format!("sobjects/{}/listviews/{}", sobject, list_view_id);
39 self.client.rest_get(&path).await.map_err(Into::into)
40 }
41
42 #[instrument(skip(self))]
44 pub async fn describe_list_view(
45 &self,
46 sobject: &str,
47 list_view_id: &str,
48 ) -> Result<ListViewDescribe> {
49 if !soql::is_safe_sobject_name(sobject) {
50 return Err(Error::new(ErrorKind::Salesforce {
51 error_code: "INVALID_SOBJECT".to_string(),
52 message: "Invalid SObject name".to_string(),
53 }));
54 }
55 if !url_security::is_valid_salesforce_id(list_view_id) {
56 return Err(Error::new(ErrorKind::Salesforce {
57 error_code: "INVALID_ID".to_string(),
58 message: "Invalid Salesforce ID format".to_string(),
59 }));
60 }
61 let path = format!("sobjects/{}/listviews/{}/describe", sobject, list_view_id);
62 self.client.rest_get(&path).await.map_err(Into::into)
63 }
64
65 #[instrument(skip(self))]
67 pub async fn execute_list_view<T: DeserializeOwned>(
68 &self,
69 sobject: &str,
70 list_view_id: &str,
71 ) -> Result<ListViewResult<T>> {
72 if !soql::is_safe_sobject_name(sobject) {
73 return Err(Error::new(ErrorKind::Salesforce {
74 error_code: "INVALID_SOBJECT".to_string(),
75 message: "Invalid SObject name".to_string(),
76 }));
77 }
78 if !url_security::is_valid_salesforce_id(list_view_id) {
79 return Err(Error::new(ErrorKind::Salesforce {
80 error_code: "INVALID_ID".to_string(),
81 message: "Invalid Salesforce ID format".to_string(),
82 }));
83 }
84 let path = format!("sobjects/{}/listviews/{}/results", sobject, list_view_id);
85 self.client.rest_get(&path).await.map_err(Into::into)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::super::SalesforceRestClient;
92
93 #[tokio::test]
94 async fn test_list_views_invalid_sobject() {
95 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
96 let result = client.list_views("Bad'; DROP--").await;
97 assert!(result.is_err());
98 assert!(result.unwrap_err().to_string().contains("INVALID_SOBJECT"));
99 }
100
101 #[tokio::test]
102 async fn test_get_list_view_invalid_id() {
103 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
104 let result = client.get_list_view("Account", "bad-id").await;
105 assert!(result.is_err());
106 assert!(result.unwrap_err().to_string().contains("INVALID_ID"));
107 }
108
109 #[tokio::test]
110 async fn test_describe_list_view_invalid_sobject() {
111 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
112 let result = client
113 .describe_list_view("Bad'; DROP--", "00Bxx0000000001AAA")
114 .await;
115 assert!(result.is_err());
116 assert!(result.unwrap_err().to_string().contains("INVALID_SOBJECT"));
117 }
118
119 #[tokio::test]
120 async fn test_execute_list_view_invalid_id() {
121 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
122 let result = client
123 .execute_list_view::<serde_json::Value>("Account", "bad-id")
124 .await;
125 assert!(result.is_err());
126 assert!(result.unwrap_err().to_string().contains("INVALID_ID"));
127 }
128
129 #[tokio::test]
130 async fn test_list_views_wiremock() {
131 use wiremock::matchers::{method, path_regex};
132 use wiremock::{Mock, MockServer, ResponseTemplate};
133
134 let mock_server = MockServer::start().await;
135
136 let body = serde_json::json!({
137 "done": true,
138 "nextRecordsUrl": null,
139 "listviews": [{
140 "id": "00Bxx0000000001AAA",
141 "developerName": "AllAccounts",
142 "label": "All Accounts",
143 "describeUrl": "/describe",
144 "resultsUrl": "/results",
145 "sobjectType": "Account"
146 }]
147 });
148
149 Mock::given(method("GET"))
150 .and(path_regex(".*/sobjects/Account/listviews$"))
151 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
152 .mount(&mock_server)
153 .await;
154
155 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
156 let result = client
157 .list_views("Account")
158 .await
159 .expect("list_views should succeed");
160 assert!(result.done);
161 assert_eq!(result.listviews.len(), 1);
162 assert_eq!(result.listviews[0].developer_name, "AllAccounts");
163 }
164
165 #[tokio::test]
166 async fn test_get_list_view_wiremock() {
167 use wiremock::matchers::{method, path_regex};
168 use wiremock::{Mock, MockServer, ResponseTemplate};
169
170 let mock_server = MockServer::start().await;
171
172 let body = serde_json::json!({
173 "id": "00Bxx0000000001AAA",
174 "developerName": "AllAccounts",
175 "label": "All Accounts",
176 "describeUrl": "/describe",
177 "resultsUrl": "/results",
178 "sobjectType": "Account"
179 });
180
181 Mock::given(method("GET"))
182 .and(path_regex(
183 ".*/sobjects/Account/listviews/00Bxx0000000001AAA$",
184 ))
185 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
186 .mount(&mock_server)
187 .await;
188
189 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
190 let result = client
191 .get_list_view("Account", "00Bxx0000000001AAA")
192 .await
193 .expect("get_list_view should succeed");
194 assert_eq!(result.id, "00Bxx0000000001AAA");
195 }
196
197 #[tokio::test]
198 async fn test_describe_list_view_wiremock() {
199 use wiremock::matchers::{method, path_regex};
200 use wiremock::{Mock, MockServer, ResponseTemplate};
201
202 let mock_server = MockServer::start().await;
203
204 let body = serde_json::json!({
205 "id": "00Bxx0000000001AAA",
206 "developerName": "AllAccounts",
207 "label": "All Accounts",
208 "sobjectType": "Account",
209 "query": "SELECT Id, Name FROM Account",
210 "columns": [{
211 "fieldNameOrPath": "Name",
212 "label": "Account Name",
213 "sortable": true,
214 "type": "string"
215 }],
216 "orderBy": [],
217 "whereCondition": null
218 });
219
220 Mock::given(method("GET"))
221 .and(path_regex(
222 ".*/sobjects/Account/listviews/00Bxx0000000001AAA/describe$",
223 ))
224 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
225 .mount(&mock_server)
226 .await;
227
228 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
229 let result = client
230 .describe_list_view("Account", "00Bxx0000000001AAA")
231 .await
232 .expect("describe_list_view should succeed");
233 assert_eq!(result.columns.len(), 1);
234 }
235
236 #[tokio::test]
237 async fn test_execute_list_view_wiremock() {
238 use wiremock::matchers::{method, path_regex};
239 use wiremock::{Mock, MockServer, ResponseTemplate};
240
241 let mock_server = MockServer::start().await;
242
243 let body = serde_json::json!({
244 "done": true,
245 "id": "00Bxx0000000001AAA",
246 "label": "All Accounts",
247 "records": [{"Id": "001xx", "Name": "Acme"}],
248 "size": 1,
249 "developerName": "AllAccounts",
250 "nextRecordsUrl": null
251 });
252
253 Mock::given(method("GET"))
254 .and(path_regex(
255 ".*/sobjects/Account/listviews/00Bxx0000000001AAA/results$",
256 ))
257 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
258 .mount(&mock_server)
259 .await;
260
261 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
262 let result = client
263 .execute_list_view::<serde_json::Value>("Account", "00Bxx0000000001AAA")
264 .await
265 .expect("execute_list_view should succeed");
266 assert!(result.done);
267 assert_eq!(result.size, 1);
268 }
269}