busbar_sf_rest/client/
consent.rs1use tracing::instrument;
2
3use busbar_sf_client::security::soql;
4
5use crate::consent::{ConsentResponse, ConsentWriteRequest};
6use crate::error::{Error, ErrorKind, Result};
7
8impl super::SalesforceRestClient {
9 #[instrument(skip(self))]
11 pub async fn read_consent(&self, action: &str, ids: &[&str]) -> Result<ConsentResponse> {
12 if !soql::is_safe_field_name(action) {
13 return Err(Error::new(ErrorKind::Salesforce {
14 error_code: "INVALID_ACTION".to_string(),
15 message: "Invalid consent action name".to_string(),
16 }));
17 }
18 let ids_param = ids.join(",");
19 let path = format!("consent/action/{}?ids={}", action, ids_param);
20 self.client.rest_get(&path).await.map_err(Into::into)
21 }
22
23 #[instrument(skip(self, request))]
25 pub async fn write_consent(&self, action: &str, request: &ConsentWriteRequest) -> Result<()> {
26 if !soql::is_safe_field_name(action) {
27 return Err(Error::new(ErrorKind::Salesforce {
28 error_code: "INVALID_ACTION".to_string(),
29 message: "Invalid consent action name".to_string(),
30 }));
31 }
32 let path = format!("consent/action/{}", action);
33 self.client
34 .rest_patch(&path, request)
35 .await
36 .map_err(Into::into)
37 }
38
39 #[instrument(skip(self))]
41 pub async fn read_multi_consent(
42 &self,
43 actions: &[&str],
44 ids: &[&str],
45 ) -> Result<serde_json::Value> {
46 let actions_param = actions.join(",");
47 let ids_param = ids.join(",");
48 let path = format!(
49 "consent/multiaction?actions={}&ids={}",
50 actions_param, ids_param
51 );
52 self.client.rest_get(&path).await.map_err(Into::into)
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::super::SalesforceRestClient;
59
60 #[tokio::test]
61 async fn test_read_consent_invalid_action() {
62 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
63 let result = client.read_consent("Bad'; DROP--", &["001xx"]).await;
64 assert!(result.is_err());
65 assert!(result.unwrap_err().to_string().contains("INVALID_ACTION"));
66 }
67
68 #[tokio::test]
69 async fn test_write_consent_invalid_action() {
70 let client = SalesforceRestClient::new("https://test.salesforce.com", "token").unwrap();
71 let request = crate::consent::ConsentWriteRequest { records: vec![] };
72 let result = client.write_consent("Bad'; DROP--", &request).await;
73 assert!(result.is_err());
74 assert!(result.unwrap_err().to_string().contains("INVALID_ACTION"));
75 }
76
77 #[tokio::test]
78 async fn test_read_consent_wiremock() {
79 use wiremock::matchers::{method, path_regex};
80 use wiremock::{Mock, MockServer, ResponseTemplate};
81
82 let mock_server = MockServer::start().await;
83
84 let body = serde_json::json!({
85 "results": [{
86 "result": "OptIn",
87 "status": "Active",
88 "objectConsulted": "ContactPointEmail"
89 }]
90 });
91
92 Mock::given(method("GET"))
93 .and(path_regex(".*/consent/action/email.*"))
94 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
95 .mount(&mock_server)
96 .await;
97
98 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
99 let result = client
100 .read_consent("email", &["001xx000003DgAAAS"])
101 .await
102 .expect("read_consent should succeed");
103 assert_eq!(result.results.len(), 1);
104 assert_eq!(result.results[0].result, "OptIn");
105 }
106
107 #[tokio::test]
108 async fn test_write_consent_wiremock() {
109 use wiremock::matchers::{method, path_regex};
110 use wiremock::{Mock, MockServer, ResponseTemplate};
111
112 let mock_server = MockServer::start().await;
113
114 Mock::given(method("PATCH"))
115 .and(path_regex(".*/consent/action/email$"))
116 .respond_with(ResponseTemplate::new(204))
117 .mount(&mock_server)
118 .await;
119
120 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
121 let request = crate::consent::ConsentWriteRequest {
122 records: vec![crate::consent::ConsentWriteRecord {
123 id: "001xx000003DgAAAS".to_string(),
124 result: "OptIn".to_string(),
125 }],
126 };
127 client
128 .write_consent("email", &request)
129 .await
130 .expect("write_consent should succeed");
131 }
132
133 #[tokio::test]
134 async fn test_read_multi_consent_wiremock() {
135 use wiremock::matchers::{method, path_regex};
136 use wiremock::{Mock, MockServer, ResponseTemplate};
137
138 let mock_server = MockServer::start().await;
139
140 let body = serde_json::json!({
141 "results": [
142 {"action": "email", "status": "OptIn"},
143 {"action": "sms", "status": "OptOut"}
144 ]
145 });
146
147 Mock::given(method("GET"))
148 .and(path_regex(".*/consent/multiaction.*"))
149 .respond_with(ResponseTemplate::new(200).set_body_json(&body))
150 .mount(&mock_server)
151 .await;
152
153 let client = SalesforceRestClient::new(mock_server.uri(), "test-token").unwrap();
154 let result = client
155 .read_multi_consent(&["email", "sms"], &["001xx000003DgAAAS"])
156 .await
157 .expect("read_multi_consent should succeed");
158 assert!(result["results"].is_array());
159 }
160}