Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions src/app/business_logic/request/http/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ pub async fn send_http_request(prepared_request: reqwest_middleware::RequestBuil

/* POST-REQUEST SCRIPT */

let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env)?;
let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env);

drop(request);

Expand All @@ -169,5 +169,66 @@ pub async fn send_http_request(prepared_request: reqwest_middleware::RequestBuil
request.cancellation_token = CancellationToken::new();
}

return Ok(modified_response);
}
return modified_response;
}

#[cfg(test)]
mod tests {
use std::io::{Read, Write};
use std::net::TcpListener;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::app::business_logic::request::http::send::send_http_request;
use crate::app::business_logic::request::send::RequestResponseError;
use crate::models::request::Request;

/// Answers a single request with a small 200 response, then closes the connection
fn one_response_server() -> String {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();

std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut buffer = [0; 4096];
let mut received = Vec::new();

while !received.windows(4).any(|window| window == b"\r\n\r\n") {
let read = stream.read(&mut buffer).unwrap();

if read == 0 {
break;
}

received.extend_from_slice(&buffer[..read]);
}

stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok").unwrap();
});

format!("http://{address}/")
}

#[tokio::test]
async fn failing_post_request_script_does_not_leave_the_request_pending() {
let url = one_response_server();

let mut request = Request {
url: url.clone(),
..Default::default()
};
request.scripts.post_request_script = Some(String::from(r#"throw new Error("post-request script failure");"#));

let local_request = Arc::new(RwLock::new(request));
let prepared_request = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build().get(&url);

let response = send_http_request(prepared_request, local_request.clone(), &None).await;

assert!(matches!(response, Err(RequestResponseError::PostRequestScript)));

let request = local_request.read();
assert!(!request.is_pending);

let console_output = request.console_output.post_request_output.as_deref().unwrap_or_default();
assert!(console_output.contains("post-request script failure"), "{console_output:?}");
}
}
32 changes: 28 additions & 4 deletions src/app/business_logic/request/mqtt/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ pub async fn send_mqtt_request(prepared_request: PreparedMqttRequest, local_requ
/* POST-REQUEST SCRIPT */

let request = local_request.read();
let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env)?;
let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env);
drop(request);

let mut request = local_request.write();
Expand All @@ -189,6 +189,8 @@ pub async fn send_mqtt_request(prepared_request: PreparedMqttRequest, local_requ
request.is_pending = false;
request.cancellation_token = CancellationToken::new();

let modified_response = modified_response?;

let mqtt_request = request.get_mqtt_request_mut().unwrap();
mqtt_request.messages = vec![];

Expand Down Expand Up @@ -385,11 +387,11 @@ fn error_response(error: String) -> RequestResponse {
mod tests {
use std::sync::Arc;
use indexmap::IndexMap;
use parking_lot::RwLock;
use parking_lot::{Mutex, RwLock};
use crate::app::app::App;
use crate::app::business_logic::request::mqtt::client::PreparedMqttRequest;
use crate::app::business_logic::request::mqtt::send::mqtt_publish;
use crate::app::business_logic::request::send::PrepareRequestError;
use crate::app::business_logic::request::mqtt::send::{mqtt_publish, send_mqtt_request};
use crate::app::business_logic::request::send::{PrepareRequestError, RequestResponseError};
use crate::models::auth::auth::Auth;
use crate::models::auth::basic::BasicAuth;
use crate::models::auth::bearer_token::BearerToken;
Expand Down Expand Up @@ -613,6 +615,28 @@ mod tests {
assert!(mqtt_request.messages.is_empty());
}

#[tokio::test]
async fn failing_post_request_script_does_not_leave_the_request_pending() {
// Nothing listens on this port, so the connection is refused
let port = std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port();

let mut failing = request(&format!("mqtt://127.0.0.1:{port}"), |_| {});
failing.scripts.post_request_script = Some(String::from(r#"throw new Error("post-request script failure");"#));

let prepared = prepare(&mut failing).unwrap();
let local_request = Arc::new(RwLock::new(failing));

let response = send_mqtt_request(prepared, local_request.clone(), &None, Arc::new(Mutex::new(false))).await;

assert!(matches!(response, Err(RequestResponseError::PostRequestScript)));

let request = local_request.read();
assert!(!request.is_pending);

let console_output = request.console_output.post_request_output.as_deref().unwrap_or_default();
assert!(console_output.contains("post-request script failure"), "{console_output:?}");
}

#[test]
fn publish_after_the_connection_ended_is_not_logged() {
let (mut mqtt_request, receiver) = connected(MqttVersion::V3_1_1, 100);
Expand Down
9 changes: 5 additions & 4 deletions src/app/business_logic/request/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,11 @@ impl App<'_> {
}
}

pub fn handle_post_request_script(request: &Request, response: RequestResponse, env: &Option<Arc<RwLock<Environment>>>) -> anyhow::Result<(RequestResponse, Option<String>), RequestResponseError> {
/// The console output is returned even when the script fails, so the error can be read in the console
pub fn handle_post_request_script(request: &Request, response: RequestResponse, env: &Option<Arc<RwLock<Environment>>>) -> (Result<RequestResponse, RequestResponseError>, Option<String>) {
match &request.scripts.post_request_script {
None => {
Ok((response, None))
(Ok(response), None)
},
Some(post_request_script) => {
let env_values = match &env {
Expand All @@ -386,8 +387,8 @@ impl App<'_> {
}

match result_response {
None => Err(PostRequestScript),
Some(result_response) => Ok((result_response, Some(result_console_output)))
None => (Err(PostRequestScript), Some(result_console_output)),
Some(result_response) => (Ok(result_response), Some(result_console_output))
}
}
}
Expand Down
51 changes: 47 additions & 4 deletions src/app/business_logic/request/ws/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,19 @@ pub async fn send_ws_request(prepared_request: reqwest_middleware::RequestBuilde

/* POST-REQUEST SCRIPT */

let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env)?;
let (modified_response, post_request_output) = App::handle_post_request_script(&request, response, env);

drop(request);

{
let modified_response = {
let mut request = local_request.write();

request.console_output.post_request_output = post_request_output;
request.is_pending = false;
request.cancellation_token = CancellationToken::new();

let modified_response = modified_response?;

let ws_request = request.get_ws_request_mut().unwrap();
ws_request.messages = vec![];

Expand All @@ -152,7 +154,9 @@ pub async fn send_ws_request(prepared_request: reqwest_middleware::RequestBuilde
else {
ws_request.is_connected = true;
}
}

modified_response
};

let local_request = local_request.clone();
let mut request = local_request.write();
Expand Down Expand Up @@ -217,4 +221,43 @@ pub async fn send_ws_request(prepared_request: reqwest_middleware::RequestBuilde
});

return Ok(modified_response);
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;
use parking_lot::{Mutex, RwLock};
use crate::app::business_logic::request::send::RequestResponseError;
use crate::app::business_logic::request::ws::send::send_ws_request;
use crate::models::protocol::protocol::Protocol;
use crate::models::protocol::ws::ws::WsRequest;
use crate::models::request::Request;

#[tokio::test]
async fn failing_post_request_script_does_not_leave_the_request_pending() {
// Nothing listens on this port, so the connection is refused
let port = std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port();
let url = format!("http://127.0.0.1:{port}/");

let mut request = Request {
url: url.clone(),
protocol: Protocol::WsRequest(WsRequest::default()),
..Default::default()
};
request.scripts.post_request_script = Some(String::from(r#"throw new Error("post-request script failure");"#));

let local_request = Arc::new(RwLock::new(request));
let prepared_request = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build().get(&url);

let response = send_ws_request(prepared_request, local_request.clone(), &None, Arc::new(Mutex::new(false))).await;

assert!(matches!(response, Err(RequestResponseError::PostRequestScript)));

let request = local_request.read();
assert!(!request.is_pending);
assert!(!request.get_ws_request().unwrap().is_connected);

let console_output = request.console_output.post_request_output.as_deref().unwrap_or_default();
assert!(console_output.contains("post-request script failure"), "{console_output:?}");
}
}
17 changes: 14 additions & 3 deletions src/cli/cli_logic/request/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,23 @@ impl App<'_> {

let local_env = self.get_selected_env_as_local();
let response = match (&protocol, prepared_request) {
(Protocol::HttpRequest(_), PreparedRequest::Reqwest(prepared_request)) => send_http_request(prepared_request, local_request.clone(), &local_env).await?,
(Protocol::WsRequest(_), PreparedRequest::Reqwest(prepared_request)) => send_ws_request(prepared_request, local_request.clone(), &local_env, self.received_response.clone()).await?,
(Protocol::MqttRequest(_), PreparedRequest::Mqtt(prepared_request)) => send_mqtt_request(prepared_request, local_request.clone(), &local_env, self.received_response.clone()).await?,
(Protocol::HttpRequest(_), PreparedRequest::Reqwest(prepared_request)) => send_http_request(prepared_request, local_request.clone(), &local_env).await,
(Protocol::WsRequest(_), PreparedRequest::Reqwest(prepared_request)) => send_ws_request(prepared_request, local_request.clone(), &local_env, self.received_response.clone()).await,
(Protocol::MqttRequest(_), PreparedRequest::Mqtt(prepared_request)) => send_mqtt_request(prepared_request, local_request.clone(), &local_env, self.received_response.clone()).await,
_ => unreachable!()
};

let response = match response {
Ok(response) => response,
Err(error) => {
if send_command.console && let Some(post_request_output) = &local_request.read().console_output.post_request_output {
println!("{}", post_request_output);
}

return Err(anyhow!(error));
}
};

let request = local_request.read();

if send_command.status_code {
Expand Down