#![allow(missing_docs)]
#![allow(clippy::unwrap_used)]
#![cfg(feature = "rest")]
use async_trait::async_trait;
use force::api::RestOperation;
use force::auth::{AccessToken, Authenticator, TokenResponse};
use force::client::ForceClientBuilder;
use force::error::Result;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[derive(Debug, Clone)]
struct MockAuthenticator {
token: String,
instance_url: String,
}
impl MockAuthenticator {
fn new(token: impl Into<String>, instance_url: impl Into<String>) -> Self {
Self {
token: token.into(),
instance_url: instance_url.into(),
}
}
}
#[async_trait]
impl Authenticator for MockAuthenticator {
async fn authenticate(&self) -> Result<AccessToken> {
let response = TokenResponse {
access_token: self.token.clone(),
instance_url: self.instance_url.clone(),
token_type: "Bearer".to_string(),
issued_at: "1672531200000".to_string(), signature: "sig".to_string(),
expires_in: None,
refresh_token: None,
};
Ok(AccessToken::from_response(response))
}
async fn refresh(&self) -> Result<AccessToken> {
self.authenticate().await
}
}
#[tokio::test]
async fn test_upsert_path_injection() {
let mock_server = MockServer::start().await;
let auth = MockAuthenticator::new("test_token", mock_server.uri());
Mock::given(method("PATCH"))
.and(path(
"/services/data/v60.0/sobjects/Account/ExternalId__c/A%2FB",
))
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
"id": "001xx000003DHP0AAO",
"success": true,
"created": true,
"errors": []
})))
.mount(&mock_server)
.await;
Mock::given(method("PATCH"))
.and(path(
"/services/data/v60.0/sobjects/Account/ExternalId__c/A/B",
))
.respond_with(ResponseTemplate::new(400)) .mount(&mock_server)
.await;
let client = ForceClientBuilder::new()
.authenticate(auth)
.build()
.await
.unwrap();
let result = client
.rest()
.upsert(
"Account",
"ExternalId__c",
"A/B", &serde_json::json!({"Name": "Test"}),
)
.await;
assert!(result.is_ok(), "Upsert failed. Result: {result:?}");
}