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

gripmock / grpctestify-rust / 30380034481

28 Jul 2026 04:49PM UTC coverage: 80.734% (+6.5%) from 74.202%
30380034481

Pull #81

github

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

47628 of 58994 relevant lines covered (80.73%)

23219.34 hits per line

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

49.37
/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(
110✔
30
        _no_assert: bool,
110✔
31
        _verbose: bool,
110✔
32
        coverage_collector: Option<Arc<CoverageCollector>>,
110✔
33
    ) -> Self {
110✔
34
        Self { coverage_collector }
110✔
35
    }
110✔
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(
78✔
60
        &self,
78✔
61
        tx: &Sender<Value>,
78✔
62
        request_value: Value,
78✔
63
        section_line: usize,
78✔
64
        _msg_type: Option<&MessageDescriptor>,
78✔
65
    ) -> RequestSendResult {
78✔
66
        // Coverage: record request fields (simplified - would need message type name)
67
        if let Some(_collector) = &self.coverage_collector {}
78✔
68

69
        match tx.send(request_value).await {
78✔
70
            Ok(_) => RequestSendResult {
78✔
71
                success: true,
78✔
72
                error_message: None,
78✔
73
            },
78✔
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
    }
78✔
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(
3✔
111
        &self,
3✔
112
        value: &mut Value,
3✔
113
        variables: &std::collections::HashMap<String, Value>,
3✔
114
    ) {
3✔
115
        crate::execution::runner_helpers::substitute_variables(value, variables);
3✔
116
    }
3✔
117

118
    /// Build TLS config from document (test-only)
119
    #[cfg(test)]
120
    pub fn build_tls_config(
×
121
        document: &crate::parser::ast::GctfDocument,
×
122
        document_path: &Path,
×
123
    ) -> Option<crate::grpc::TlsConfig> {
×
124
        document
×
125
            .get_tls_config()
×
126
            .map(|tls_map| crate::grpc::TlsConfig {
×
127
                ca_cert_path: tls_map.get("ca_cert").map(|p| {
×
128
                    FileUtils::resolve_relative_path(document_path, p)
×
129
                        .to_string_lossy()
×
130
                        .to_string()
×
131
                }),
×
132
                client_cert_path: tls_map.get("client_cert").map(|p| {
×
133
                    FileUtils::resolve_relative_path(document_path, p)
×
134
                        .to_string_lossy()
×
135
                        .to_string()
×
136
                }),
×
137
                client_key_path: tls_map.get("client_key").map(|p| {
×
138
                    FileUtils::resolve_relative_path(document_path, p)
×
139
                        .to_string_lossy()
×
140
                        .to_string()
×
141
                }),
×
142
                server_name: tls_map.get("server_name").cloned(),
×
143
                insecure_skip_verify: tls_map
×
144
                    .get("insecure")
×
145
                    .is_some_and(|v| v == "true" || v == "1"),
×
146
            })
×
147
    }
×
148

149
    /// Build proto config from document (test-only)
150
    #[cfg(test)]
151
    pub fn build_proto_config(
×
152
        document: &crate::parser::ast::GctfDocument,
×
153
        document_path: &Path,
×
154
    ) -> Option<crate::grpc::ProtoConfig> {
×
155
        document.get_proto_config().map(|proto_map| {
×
156
            let files = proto_map
×
157
                .get("files")
×
158
                .map(|f| {
×
159
                    f.split(',')
×
160
                        .map(|s| {
×
161
                            FileUtils::resolve_relative_path(document_path, s.trim())
×
162
                                .to_string_lossy()
×
163
                                .to_string()
×
164
                        })
×
165
                        .collect()
×
166
                })
×
167
                .unwrap_or_default();
×
168

169
            let import_paths = proto_map
×
170
                .get("import_paths")
×
171
                .map(|p| {
×
172
                    p.split(',')
×
173
                        .map(|s| {
×
174
                            FileUtils::resolve_relative_path(document_path, s.trim())
×
175
                                .to_string_lossy()
×
176
                                .to_string()
×
177
                        })
×
178
                        .collect()
×
179
                })
×
180
                .unwrap_or_default();
×
181

182
            let descriptor = proto_map.get("descriptor").map(|p| {
×
183
                FileUtils::resolve_relative_path(document_path, p)
×
184
                    .to_string_lossy()
×
185
                    .to_string()
×
186
            });
×
187

188
            crate::grpc::ProtoConfig {
×
189
                files,
×
190
                import_paths,
×
191
                descriptor,
×
192
            }
×
193
        })
×
194
    }
×
195

196
    /// Build gRPC client config (test-only)
197
    #[cfg(test)]
198
    pub fn build_client_config(
×
199
        document: &crate::parser::ast::GctfDocument,
×
200
        document_path: &Path,
×
201
        address: &str,
×
202
    ) -> GrpcClientConfig {
×
203
        let tls_config = Self::build_tls_config(document, document_path);
×
204
        let proto_config = Self::build_proto_config(document, document_path);
×
205

206
        GrpcClientConfig {
207
            address: address.to_string(),
×
208
            timeout_seconds: 30,
209
            tls_config,
×
210
            proto_config,
×
211
            metadata: document
×
212
                .get_request_headers()
×
213
                .map(|m| m.into_iter().collect()),
×
214
            compression: crate::config::compression_from_env(),
×
215
            connection_id: 0,
216
            protocol: document
×
217
                .get_options()
×
218
                .and_then(|o| {
×
219
                    o.get("protocol").map(|s| {
×
220
                        s.parse::<crate::grpc::WireProtocol>()
×
221
                            .unwrap_or(crate::grpc::WireProtocol::Grpc)
×
222
                    })
×
223
                })
×
224
                .unwrap_or(crate::grpc::WireProtocol::Grpc),
×
225
            version: env!("CARGO_PKG_VERSION").to_string(),
×
226
            target_service: document.parse_endpoint().map(|(p, s, m)| {
×
227
                if p.is_empty() {
×
228
                    format!("{}/{}", s, m)
×
229
                } else {
230
                    format!("{}.{}/{}", p, s, m)
×
231
                }
232
            }),
×
233
        }
234
    }
×
235
}
236

237
#[cfg(test)]
238
mod tests {
239
    use super::*;
240
    use crate::parser::ast::SectionSpan;
241
    use serde_json::json;
242

243
    #[test]
244
    fn test_request_handler_new() {
1✔
245
        let handler = RequestHandler::new(false, false, None);
1✔
246
        assert!(handler.coverage_collector.is_none());
1✔
247
    }
1✔
248

249
    #[test]
250
    fn test_build_request_json() {
1✔
251
        let handler = RequestHandler::new(false, false, None);
1✔
252
        let section = Section {
1✔
253
            section_type: SectionType::Request,
1✔
254
            content: SectionContent::Json(json!({"id": 123})),
1✔
255
            inline_options: Default::default(),
1✔
256
            raw_content: "".to_string(),
1✔
257
            start_line: 0,
1✔
258
            end_line: 0,
1✔
259
            attributes: Vec::new(),
1✔
260
            span: SectionSpan::default(),
1✔
261
        };
1✔
262
        let variables = std::collections::HashMap::new();
1✔
263

264
        let result = handler.build_request(&section, &variables);
1✔
265
        assert!(result.is_some());
1✔
266
        assert_eq!(result.unwrap(), json!({"id": 123}));
1✔
267
    }
1✔
268

269
    #[test]
270
    fn test_substitute_variables() {
1✔
271
        let handler = RequestHandler::new(false, false, None);
1✔
272
        let mut value = json!({"id": "{{ user_id }}", "name": "test"});
1✔
273
        let mut variables = std::collections::HashMap::new();
1✔
274
        variables.insert("user_id".to_string(), json!("123"));
1✔
275

276
        handler.substitute_variables(&mut value, &variables);
1✔
277

278
        assert_eq!(value["id"], "123");
1✔
279
        assert_eq!(value["name"], "test");
1✔
280
    }
1✔
281

282
    #[test]
283
    fn test_should_close_request_stream() {
1✔
284
        let handler = RequestHandler::new(false, false, None);
1✔
285
        let sections = vec![
1✔
286
            Section {
1✔
287
                section_type: SectionType::Request,
1✔
288
                content: SectionContent::Json(json!({})),
1✔
289
                inline_options: Default::default(),
1✔
290
                raw_content: "".to_string(),
1✔
291
                start_line: 0,
1✔
292
                end_line: 0,
1✔
293
                attributes: Vec::new(),
1✔
294
                span: SectionSpan::default(),
1✔
295
            },
1✔
296
            Section {
1✔
297
                section_type: SectionType::Response,
1✔
298
                content: SectionContent::Json(json!({})),
1✔
299
                inline_options: Default::default(),
1✔
300
                raw_content: "".to_string(),
1✔
301
                start_line: 0,
1✔
302
                end_line: 0,
1✔
303
                attributes: Vec::new(),
1✔
304
                span: SectionSpan::default(),
1✔
305
            },
1✔
306
        ];
307

308
        // After first section (index 0), there are no more REQUEST sections
309
        assert!(handler.should_close_request_stream(&sections, 0));
1✔
310
    }
1✔
311

312
    #[test]
313
    fn test_build_request_with_variables() {
1✔
314
        let handler = RequestHandler::new(false, false, None);
1✔
315
        let section = Section {
1✔
316
            section_type: SectionType::Request,
1✔
317
            content: SectionContent::Json(json!({"id": "{{ user_id }}"})),
1✔
318
            inline_options: Default::default(),
1✔
319
            raw_content: "".to_string(),
1✔
320
            start_line: 0,
1✔
321
            end_line: 0,
1✔
322
            attributes: Vec::new(),
1✔
323
            span: SectionSpan::default(),
1✔
324
        };
1✔
325
        let mut variables = std::collections::HashMap::new();
1✔
326
        variables.insert("user_id".to_string(), json!("456"));
1✔
327

328
        let result = handler.build_request(&section, &variables);
1✔
329
        assert!(result.is_some());
1✔
330
        assert_eq!(result.unwrap()["id"], "456");
1✔
331
    }
1✔
332
}
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