• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gripmock / grpctestify-rust / 30248040547

27 Jul 2026 07:57AM UTC coverage: 80.574% (+6.4%) from 74.202%
30248040547

Pull #81

github

web-flow
Merge 55b8756ce into 67c08fb25
Pull Request #81: rhai

47513 of 58968 relevant lines covered (80.57%)

23229.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.78
/src/execution/request_handler.rs
1
#[cfg(test)]
2
use crate::grpc::GrpcClientConfig;
3
#[cfg(test)]
4
use crate::parser::ast::SectionType;
5
use crate::parser::ast::{Section, SectionContent};
6
use crate::report::CoverageCollector;
7
#[cfg(test)]
8
use crate::utils::file::FileUtils;
9
use prost_reflect::MessageDescriptor;
10
use serde_json::Value;
11
#[cfg(test)]
12
use std::path::Path;
13
use std::sync::Arc;
14
use tokio::sync::mpsc::Sender;
15

16
/// Request building and sending result
17
#[derive(Debug, Clone)]
18
pub struct RequestSendResult {
19
    pub success: bool,
20
    pub error_message: Option<String>,
21
}
22

23
pub struct RequestHandler {
24
    coverage_collector: Option<Arc<CoverageCollector>>,
25
}
26

27
impl RequestHandler {
28
    /// Create new request handler
29
    pub fn new(
109✔
30
        _no_assert: bool,
109✔
31
        _verbose: bool,
109✔
32
        coverage_collector: Option<Arc<CoverageCollector>>,
109✔
33
    ) -> Self {
109✔
34
        Self { coverage_collector }
109✔
35
    }
109✔
36

37
    /// Build request value from section
38
    pub fn build_request(
2✔
39
        &self,
2✔
40
        section: &Section,
2✔
41
        variables: &std::collections::HashMap<String, Value>,
2✔
42
    ) -> Option<Value> {
2✔
43
        match &section.content {
2✔
44
            SectionContent::Json(value) => {
2✔
45
                let mut request = value.clone();
2✔
46
                self.substitute_variables(&mut request, variables);
2✔
47
                Some(request)
2✔
48
            }
49
            SectionContent::JsonLines(_) => {
50
                // For JSON lines, each line is a separate request
51
                // This is handled by send_requests_batch
52
                None
×
53
            }
54
            _ => None,
×
55
        }
56
    }
2✔
57

58
    /// Send a single request
59
    pub async fn send_request(
77✔
60
        &self,
77✔
61
        tx: &Sender<Value>,
77✔
62
        request_value: Value,
77✔
63
        section_line: usize,
77✔
64
        _msg_type: Option<&MessageDescriptor>,
77✔
65
    ) -> RequestSendResult {
77✔
66
        // Coverage: record request fields (simplified - would need message type name)
67
        if let Some(_collector) = &self.coverage_collector {}
77✔
68

69
        match tx.send(request_value).await {
77✔
70
            Ok(_) => RequestSendResult {
77✔
71
                success: true,
77✔
72
                error_message: None,
77✔
73
            },
77✔
74
            Err(e) => RequestSendResult {
×
75
                success: false,
×
76
                error_message: Some(format!(
×
77
                    "Failed to send request at line {}: {}",
×
78
                    section_line, e
×
79
                )),
×
80
            },
×
81
        }
82
    }
77✔
83

84
    /// Send implicit empty request (for unary/server-stream when no REQUEST section)
85
    pub async fn send_implicit_empty_request(&self, tx: &Sender<Value>) -> RequestSendResult {
×
86
        let empty_request = Value::Object(serde_json::Map::new());
×
87

88
        match tx.send(empty_request).await {
×
89
            Ok(_) => RequestSendResult {
×
90
                success: true,
×
91
                error_message: None,
×
92
            },
×
93
            Err(e) => RequestSendResult {
×
94
                success: false,
×
95
                error_message: Some(format!("Failed to send implicit empty request: {}", e)),
×
96
            },
×
97
        }
98
    }
×
99

100
    /// Check if request stream should be closed (test-only)
101
    #[cfg(test)]
102
    pub fn should_close_request_stream(&self, sections: &[Section], current_index: usize) -> bool {
1✔
103
        // Close stream if no more REQUEST sections follow
104
        sections[current_index + 1..]
1✔
105
            .iter()
1✔
106
            .all(|s| s.section_type != SectionType::Request)
1✔
107
    }
1✔
108

109
    /// Substitute variables in request value
110
    pub fn substitute_variables(
7✔
111
        &self,
7✔
112
        value: &mut Value,
7✔
113
        variables: &std::collections::HashMap<String, Value>,
7✔
114
    ) {
7✔
115
        match value {
7✔
116
            Value::String(s) => {
3✔
117
                for (var_name, var_value) in variables {
3✔
118
                    let pattern = format!("{{{{ {} }}}}", var_name);
3✔
119
                    if s.contains(&pattern) {
3✔
120
                        if let Value::String(replacement) = var_value {
2✔
121
                            *s = s.replace(&pattern, replacement);
2✔
122
                        } else {
2✔
123
                            *s = s.replace(&pattern, &var_value.to_string());
×
124
                        }
×
125
                    }
1✔
126
                }
127
            }
128
            Value::Array(arr) => {
×
129
                for item in arr {
×
130
                    self.substitute_variables(item, variables);
×
131
                }
×
132
            }
133
            Value::Object(map) => {
3✔
134
                for (_, val) in map {
4✔
135
                    self.substitute_variables(val, variables);
4✔
136
                }
4✔
137
            }
138
            _ => {}
1✔
139
        }
140
    }
7✔
141

142
    /// Build TLS config from document (test-only)
143
    #[cfg(test)]
144
    pub fn build_tls_config(
×
145
        document: &crate::parser::ast::GctfDocument,
×
146
        document_path: &Path,
×
147
    ) -> Option<crate::grpc::TlsConfig> {
×
148
        document
×
149
            .get_tls_config()
×
150
            .map(|tls_map| crate::grpc::TlsConfig {
×
151
                ca_cert_path: tls_map.get("ca_cert").map(|p| {
×
152
                    FileUtils::resolve_relative_path(document_path, p)
×
153
                        .to_string_lossy()
×
154
                        .to_string()
×
155
                }),
×
156
                client_cert_path: tls_map.get("client_cert").map(|p| {
×
157
                    FileUtils::resolve_relative_path(document_path, p)
×
158
                        .to_string_lossy()
×
159
                        .to_string()
×
160
                }),
×
161
                client_key_path: tls_map.get("client_key").map(|p| {
×
162
                    FileUtils::resolve_relative_path(document_path, p)
×
163
                        .to_string_lossy()
×
164
                        .to_string()
×
165
                }),
×
166
                server_name: tls_map.get("server_name").cloned(),
×
167
                insecure_skip_verify: tls_map
×
168
                    .get("insecure")
×
169
                    .is_some_and(|v| v == "true" || v == "1"),
×
170
            })
×
171
    }
×
172

173
    /// Build proto config from document (test-only)
174
    #[cfg(test)]
175
    pub fn build_proto_config(
×
176
        document: &crate::parser::ast::GctfDocument,
×
177
        document_path: &Path,
×
178
    ) -> Option<crate::grpc::ProtoConfig> {
×
179
        document.get_proto_config().map(|proto_map| {
×
180
            let files = proto_map
×
181
                .get("files")
×
182
                .map(|f| {
×
183
                    f.split(',')
×
184
                        .map(|s| {
×
185
                            FileUtils::resolve_relative_path(document_path, s.trim())
×
186
                                .to_string_lossy()
×
187
                                .to_string()
×
188
                        })
×
189
                        .collect()
×
190
                })
×
191
                .unwrap_or_default();
×
192

193
            let import_paths = proto_map
×
194
                .get("import_paths")
×
195
                .map(|p| {
×
196
                    p.split(',')
×
197
                        .map(|s| {
×
198
                            FileUtils::resolve_relative_path(document_path, s.trim())
×
199
                                .to_string_lossy()
×
200
                                .to_string()
×
201
                        })
×
202
                        .collect()
×
203
                })
×
204
                .unwrap_or_default();
×
205

206
            let descriptor = proto_map.get("descriptor").map(|p| {
×
207
                FileUtils::resolve_relative_path(document_path, p)
×
208
                    .to_string_lossy()
×
209
                    .to_string()
×
210
            });
×
211

212
            crate::grpc::ProtoConfig {
×
213
                files,
×
214
                import_paths,
×
215
                descriptor,
×
216
            }
×
217
        })
×
218
    }
×
219

220
    /// Build gRPC client config (test-only)
221
    #[cfg(test)]
222
    pub fn build_client_config(
×
223
        document: &crate::parser::ast::GctfDocument,
×
224
        document_path: &Path,
×
225
        address: &str,
×
226
    ) -> GrpcClientConfig {
×
227
        let tls_config = Self::build_tls_config(document, document_path);
×
228
        let proto_config = Self::build_proto_config(document, document_path);
×
229

230
        GrpcClientConfig {
231
            address: address.to_string(),
×
232
            timeout_seconds: 30,
233
            tls_config,
×
234
            proto_config,
×
235
            metadata: document
×
236
                .get_request_headers()
×
237
                .map(|m| m.into_iter().collect()),
×
238
            compression: crate::config::compression_from_env(),
×
239
            connection_id: 0,
240
            protocol: document
×
241
                .get_options()
×
242
                .and_then(|o| {
×
243
                    o.get("protocol").map(|s| {
×
244
                        s.parse::<crate::grpc::WireProtocol>()
×
245
                            .unwrap_or(crate::grpc::WireProtocol::Grpc)
×
246
                    })
×
247
                })
×
248
                .unwrap_or(crate::grpc::WireProtocol::Grpc),
×
249
            version: env!("CARGO_PKG_VERSION").to_string(),
×
250
            target_service: document.parse_endpoint().map(|(p, s, m)| {
×
251
                if p.is_empty() {
×
252
                    format!("{}/{}", s, m)
×
253
                } else {
254
                    format!("{}.{}/{}", p, s, m)
×
255
                }
256
            }),
×
257
        }
258
    }
×
259
}
260

261
#[cfg(test)]
262
mod tests {
263
    use super::*;
264
    use crate::parser::ast::SectionSpan;
265
    use serde_json::json;
266

267
    #[test]
268
    fn test_request_handler_new() {
1✔
269
        let handler = RequestHandler::new(false, false, None);
1✔
270
        assert!(handler.coverage_collector.is_none());
1✔
271
    }
1✔
272

273
    #[test]
274
    fn test_build_request_json() {
1✔
275
        let handler = RequestHandler::new(false, false, None);
1✔
276
        let section = Section {
1✔
277
            section_type: SectionType::Request,
1✔
278
            content: SectionContent::Json(json!({"id": 123})),
1✔
279
            inline_options: Default::default(),
1✔
280
            raw_content: "".to_string(),
1✔
281
            start_line: 0,
1✔
282
            end_line: 0,
1✔
283
            attributes: Vec::new(),
1✔
284
            span: SectionSpan::default(),
1✔
285
        };
1✔
286
        let variables = std::collections::HashMap::new();
1✔
287

288
        let result = handler.build_request(&section, &variables);
1✔
289
        assert!(result.is_some());
1✔
290
        assert_eq!(result.unwrap(), json!({"id": 123}));
1✔
291
    }
1✔
292

293
    #[test]
294
    fn test_substitute_variables() {
1✔
295
        let handler = RequestHandler::new(false, false, None);
1✔
296
        let mut value = json!({"id": "{{ user_id }}", "name": "test"});
1✔
297
        let mut variables = std::collections::HashMap::new();
1✔
298
        variables.insert("user_id".to_string(), json!("123"));
1✔
299

300
        handler.substitute_variables(&mut value, &variables);
1✔
301

302
        assert_eq!(value["id"], "123");
1✔
303
        assert_eq!(value["name"], "test");
1✔
304
    }
1✔
305

306
    #[test]
307
    fn test_should_close_request_stream() {
1✔
308
        let handler = RequestHandler::new(false, false, None);
1✔
309
        let sections = vec![
1✔
310
            Section {
1✔
311
                section_type: SectionType::Request,
1✔
312
                content: SectionContent::Json(json!({})),
1✔
313
                inline_options: Default::default(),
1✔
314
                raw_content: "".to_string(),
1✔
315
                start_line: 0,
1✔
316
                end_line: 0,
1✔
317
                attributes: Vec::new(),
1✔
318
                span: SectionSpan::default(),
1✔
319
            },
1✔
320
            Section {
1✔
321
                section_type: SectionType::Response,
1✔
322
                content: SectionContent::Json(json!({})),
1✔
323
                inline_options: Default::default(),
1✔
324
                raw_content: "".to_string(),
1✔
325
                start_line: 0,
1✔
326
                end_line: 0,
1✔
327
                attributes: Vec::new(),
1✔
328
                span: SectionSpan::default(),
1✔
329
            },
1✔
330
        ];
331

332
        // After first section (index 0), there are no more REQUEST sections
333
        assert!(handler.should_close_request_stream(&sections, 0));
1✔
334
    }
1✔
335

336
    #[test]
337
    fn test_build_request_with_variables() {
1✔
338
        let handler = RequestHandler::new(false, false, None);
1✔
339
        let section = Section {
1✔
340
            section_type: SectionType::Request,
1✔
341
            content: SectionContent::Json(json!({"id": "{{ user_id }}"})),
1✔
342
            inline_options: Default::default(),
1✔
343
            raw_content: "".to_string(),
1✔
344
            start_line: 0,
1✔
345
            end_line: 0,
1✔
346
            attributes: Vec::new(),
1✔
347
            span: SectionSpan::default(),
1✔
348
        };
1✔
349
        let mut variables = std::collections::HashMap::new();
1✔
350
        variables.insert("user_id".to_string(), json!("456"));
1✔
351

352
        let result = handler.build_request(&section, &variables);
1✔
353
        assert!(result.is_some());
1✔
354
        assert_eq!(result.unwrap()["id"], "456");
1✔
355
    }
1✔
356
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc