#[cfg(test)]
use crate::grpc::GrpcClientConfig;
#[cfg(test)]
use crate::parser::ast::SectionType;
use crate::parser::ast::{Section, SectionContent};
use crate::report::CoverageCollector;
#[cfg(test)]
use crate::utils::file::FileUtils;
use prost_reflect::MessageDescriptor;
use serde_json::Value;
#[cfg(test)]
use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc::Sender;
#[derive(Debug, Clone)]
pub struct RequestSendResult {
pub success: bool,
pub error_message: Option<String>,
}
pub struct RequestHandler {
coverage_collector: Option<Arc<CoverageCollector>>,
}
impl RequestHandler {
pub fn new(
_no_assert: bool,
_verbose: bool,
coverage_collector: Option<Arc<CoverageCollector>>,
) -> Self {
Self { coverage_collector }
}
pub fn build_request(
&self,
section: &Section,
variables: &std::collections::HashMap<String, Value>,
) -> Option<Value> {
match §ion.content {
SectionContent::Json(value) => {
let mut request = value.clone();
self.substitute_variables(&mut request, variables);
Some(request)
}
SectionContent::JsonLines(_) => {
None
}
_ => None,
}
}
pub async fn send_request(
&self,
tx: &Sender<Value>,
request_value: Value,
section_line: usize,
_msg_type: Option<&MessageDescriptor>,
) -> RequestSendResult {
if let Some(_collector) = &self.coverage_collector {
}
match tx.send(request_value).await {
Ok(_) => RequestSendResult {
success: true,
error_message: None,
},
Err(e) => RequestSendResult {
success: false,
error_message: Some(format!(
"Failed to send request at line {}: {}",
section_line, e
)),
},
}
}
pub async fn send_implicit_empty_request(&self, tx: &Sender<Value>) -> RequestSendResult {
let empty_request = Value::Object(serde_json::Map::new());
match tx.send(empty_request).await {
Ok(_) => RequestSendResult {
success: true,
error_message: None,
},
Err(e) => RequestSendResult {
success: false,
error_message: Some(format!("Failed to send implicit empty request: {}", e)),
},
}
}
#[cfg(test)]
pub fn should_close_request_stream(&self, sections: &[Section], current_index: usize) -> bool {
sections[current_index + 1..]
.iter()
.all(|s| s.section_type != SectionType::Request)
}
pub fn substitute_variables(
&self,
value: &mut Value,
variables: &std::collections::HashMap<String, Value>,
) {
match value {
Value::String(s) => {
for (var_name, var_value) in variables {
let pattern = format!("{{{{ {} }}}}", var_name);
if s.contains(&pattern) {
if let Value::String(replacement) = var_value {
*s = s.replace(&pattern, replacement);
} else {
*s = s.replace(&pattern, &var_value.to_string());
}
}
}
}
Value::Array(arr) => {
for item in arr {
self.substitute_variables(item, variables);
}
}
Value::Object(map) => {
for (_, val) in map {
self.substitute_variables(val, variables);
}
}
_ => {}
}
}
#[cfg(test)]
pub fn build_tls_config(
document: &crate::parser::ast::GctfDocument,
document_path: &Path,
) -> Option<crate::grpc::TlsConfig> {
document
.get_tls_config()
.map(|tls_map| crate::grpc::TlsConfig {
ca_cert_path: tls_map.get("ca_cert").map(|p| {
FileUtils::resolve_relative_path(document_path, p)
.to_string_lossy()
.to_string()
}),
client_cert_path: tls_map.get("client_cert").map(|p| {
FileUtils::resolve_relative_path(document_path, p)
.to_string_lossy()
.to_string()
}),
client_key_path: tls_map.get("client_key").map(|p| {
FileUtils::resolve_relative_path(document_path, p)
.to_string_lossy()
.to_string()
}),
server_name: tls_map.get("server_name").cloned(),
insecure_skip_verify: tls_map
.get("insecure")
.is_some_and(|v| v == "true" || v == "1"),
})
}
#[cfg(test)]
pub fn build_proto_config(
document: &crate::parser::ast::GctfDocument,
document_path: &Path,
) -> Option<crate::grpc::ProtoConfig> {
document.get_proto_config().map(|proto_map| {
let files = proto_map
.get("files")
.map(|f| {
f.split(',')
.map(|s| {
FileUtils::resolve_relative_path(document_path, s.trim())
.to_string_lossy()
.to_string()
})
.collect()
})
.unwrap_or_default();
let import_paths = proto_map
.get("import_paths")
.map(|p| {
p.split(',')
.map(|s| {
FileUtils::resolve_relative_path(document_path, s.trim())
.to_string_lossy()
.to_string()
})
.collect()
})
.unwrap_or_default();
let descriptor = proto_map.get("descriptor").map(|p| {
FileUtils::resolve_relative_path(document_path, p)
.to_string_lossy()
.to_string()
});
crate::grpc::ProtoConfig {
files,
import_paths,
descriptor,
}
})
}
#[cfg(test)]
pub fn build_client_config(
document: &crate::parser::ast::GctfDocument,
document_path: &Path,
address: &str,
) -> GrpcClientConfig {
let tls_config = Self::build_tls_config(document, document_path);
let proto_config = Self::build_proto_config(document, document_path);
GrpcClientConfig {
address: address.to_string(),
timeout_seconds: 30, tls_config,
proto_config,
metadata: document.get_request_headers(),
compression: crate::grpc::CompressionMode::from_env(),
connection_id: 0,
protocol: crate::grpc::WireProtocol::Grpc,
target_service: document.parse_endpoint().map(|(p, s, m)| {
if p.is_empty() {
format!("{}/{}", s, m)
} else {
format!("{}.{}/{}", p, s, m)
}
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_request_handler_new() {
let handler = RequestHandler::new(false, false, None);
assert!(handler.coverage_collector.is_none());
}
#[test]
fn test_build_request_json() {
let handler = RequestHandler::new(false, false, None);
let section = Section {
section_type: SectionType::Request,
content: SectionContent::Json(json!({"id": 123})),
inline_options: Default::default(),
raw_content: "".to_string(),
start_line: 0,
end_line: 0,
attributes: Vec::new(),
};
let variables = std::collections::HashMap::new();
let result = handler.build_request(§ion, &variables);
assert!(result.is_some());
assert_eq!(result.unwrap(), json!({"id": 123}));
}
#[test]
fn test_substitute_variables() {
let handler = RequestHandler::new(false, false, None);
let mut value = json!({"id": "{{ user_id }}", "name": "test"});
let mut variables = std::collections::HashMap::new();
variables.insert("user_id".to_string(), json!("123"));
handler.substitute_variables(&mut value, &variables);
assert_eq!(value["id"], "123");
assert_eq!(value["name"], "test");
}
#[test]
fn test_should_close_request_stream() {
let handler = RequestHandler::new(false, false, None);
let sections = vec![
Section {
section_type: SectionType::Request,
content: SectionContent::Json(json!({})),
inline_options: Default::default(),
raw_content: "".to_string(),
start_line: 0,
end_line: 0,
attributes: Vec::new(),
},
Section {
section_type: SectionType::Response,
content: SectionContent::Json(json!({})),
inline_options: Default::default(),
raw_content: "".to_string(),
start_line: 0,
end_line: 0,
attributes: Vec::new(),
},
];
assert!(handler.should_close_request_stream(§ions, 0));
}
#[test]
fn test_build_request_with_variables() {
let handler = RequestHandler::new(false, false, None);
let section = Section {
section_type: SectionType::Request,
content: SectionContent::Json(json!({"id": "{{ user_id }}"})),
inline_options: Default::default(),
raw_content: "".to_string(),
start_line: 0,
end_line: 0,
attributes: Vec::new(),
};
let mut variables = std::collections::HashMap::new();
variables.insert("user_id".to_string(), json!("456"));
let result = handler.build_request(§ion, &variables);
assert!(result.is_some());
assert_eq!(result.unwrap()["id"], "456");
}
}