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

gripmock / grpctestify-rust / 29348959266

14 Jul 2026 04:16PM UTC coverage: 70.34% (-2.0%) from 72.363%
29348959266

Pull #60

github

web-flow
Merge e2bc2de8f into 235ae7a5b
Pull Request #60: Add protocols

1565 of 3741 new or added lines in 42 files covered. (41.83%)

33 existing lines in 11 files now uncovered.

30408 of 43230 relevant lines covered (70.34%)

23522.65 hits per line

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

44.44
/src/execution/request_handler.rs
1
// Request Handler - handles request building and sending
2

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

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

25
/// Request Handler - builds and sends requests
26
pub struct RequestHandler {
27
    coverage_collector: Option<Arc<CoverageCollector>>,
28
}
29

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

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

61
    /// Send a single request
62
    pub async fn send_request(
×
63
        &self,
×
64
        tx: &Sender<Value>,
×
65
        request_value: Value,
×
66
        section_line: usize,
×
67
        _msg_type: Option<&MessageDescriptor>,
×
68
    ) -> RequestSendResult {
×
69
        // Coverage: record request fields (simplified - would need message type name)
70
        if let Some(_collector) = &self.coverage_collector {
×
71
            // collector.record_fields_from_json(msg_type_name, &request_value);
×
72
        }
×
73

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

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

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

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

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

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

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

198
            let import_paths = proto_map
×
199
                .get("import_paths")
×
200
                .map(|p| {
×
201
                    p.split(',')
×
202
                        .map(|s| {
×
203
                            FileUtils::resolve_relative_path(document_path, s.trim())
×
204
                                .to_string_lossy()
×
205
                                .to_string()
×
206
                        })
×
207
                        .collect()
×
208
                })
×
209
                .unwrap_or_default();
×
210

211
            let descriptor = proto_map.get("descriptor").map(|p| {
×
212
                FileUtils::resolve_relative_path(document_path, p)
×
213
                    .to_string_lossy()
×
214
                    .to_string()
×
215
            });
×
216

217
            crate::grpc::ProtoConfig {
×
218
                files,
×
219
                import_paths,
×
220
                descriptor,
×
221
            }
×
222
        })
×
223
    }
×
224

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

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

264
#[cfg(test)]
265
mod tests {
266
    use super::*;
267
    use serde_json::json;
268

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

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

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

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

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

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

307
    #[test]
308
    fn test_should_close_request_stream() {
1✔
309
        let handler = RequestHandler::new(false, false, None);
1✔
310
        let sections = vec![
1✔
311
            Section {
1✔
312
                section_type: SectionType::Request,
1✔
313
                content: SectionContent::Json(json!({})),
1✔
314
                inline_options: Default::default(),
1✔
315
                raw_content: "".to_string(),
1✔
316
                start_line: 0,
1✔
317
                end_line: 0,
1✔
318
                attributes: Vec::new(),
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
            },
1✔
329
        ];
330

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

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

350
        let result = handler.build_request(&section, &variables);
1✔
351
        assert!(result.is_some());
1✔
352
        assert_eq!(result.unwrap()["id"], "456");
1✔
353
    }
1✔
354
}
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