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

gripmock / grpctestify-rust / 30306709509

27 Jul 2026 09:24PM UTC coverage: 80.72% (+6.5%) from 74.202%
30306709509

Pull #81

github

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

47620 of 58994 relevant lines covered (80.72%)

23219.35 hits per line

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

97.23
/src/execution/assertion_handler.rs
1
use crate::assert::AssertionEngine;
2
#[cfg(test)]
3
use crate::parser::ast::{Section, SectionContent, SectionSpan, SectionType};
4
use crate::plugins::AssertionTiming;
5
use crate::utils::section_content_line;
6
use serde_json::Value;
7
use std::collections::HashMap;
8
use std::sync::Arc;
9
use std::sync::LazyLock;
10

11
/// Assertion evaluation result
12
#[derive(Debug, Clone)]
13
pub struct AssertionResult {
14
    pub passed: bool,
15
    pub failure_messages: Vec<String>,
16
    /// Per-assertion outcome + timing, in source order. Populated by
17
    /// [`AssertionHandler::evaluate_assertions_for_section`]; empty for the
18
    /// `#[cfg(test)]`-only helper methods below.
19
    pub records: Vec<apif_state::AssertionRecord>,
20
}
21

22
pub struct AssertionHandler {
23
    engine: AssertionEngine,
24
}
25

26
/// Global plugin registry for assertion evaluation.
27
static PLUGIN_REGISTRY: LazyLock<Arc<dyn apif_assert::registry::PluginRegistry>> =
28
    LazyLock::new(|| Arc::new(crate::execution::plugin_dir::build_plugin_manager()));
62✔
29

30
impl AssertionHandler {
31
    /// Create new assertion handler
32
    pub fn new(_verbose: bool) -> Self {
111✔
33
        Self {
111✔
34
            engine: AssertionEngine::with_registry(PLUGIN_REGISTRY.clone()),
111✔
35
        }
111✔
36
    }
111✔
37

38
    /// Evaluate assertions for a document (test-only)
39
    #[cfg(test)]
40
    pub fn evaluate_assertions(
2✔
41
        &self,
2✔
42
        sections: &[Section],
2✔
43
        target_value: &Value,
2✔
44
        headers: &HashMap<String, String>,
2✔
45
        trailers: &HashMap<String, String>,
2✔
46
    ) -> AssertionResult {
2✔
47
        let mut failure_messages = Vec::new();
2✔
48

49
        for section in sections {
2✔
50
            if section.section_type == SectionType::Asserts
2✔
51
                && let SectionContent::Assertions(lines) = &section.content
2✔
52
            {
53
                let results =
2✔
54
                    self.engine
2✔
55
                        .evaluate_all(lines, target_value, Some(headers), Some(trailers));
2✔
56

57
                for (idx, result) in results.iter().enumerate() {
2✔
58
                    let line_num = section_content_line(section.start_line, idx);
2✔
59
                    let context = format!("at line {}", line_num);
2✔
60
                    append_single_failure(result, &context, &mut failure_messages);
2✔
61
                }
2✔
62
            }
×
63
        }
64

65
        AssertionResult {
2✔
66
            passed: failure_messages.is_empty(),
2✔
67
            failure_messages,
2✔
68
            records: Vec::new(),
2✔
69
        }
2✔
70
    }
2✔
71

72
    /// Evaluate assertions for a specific section (test-only)
73
    #[cfg(test)]
74
    pub fn evaluate_section_assertions(
1✔
75
        &self,
1✔
76
        section: &Section,
1✔
77
        target_value: &Value,
1✔
78
        headers: &HashMap<String, String>,
1✔
79
        trailers: &HashMap<String, String>,
1✔
80
    ) -> AssertionResult {
1✔
81
        let mut failure_messages = Vec::new();
1✔
82

83
        if section.section_type == SectionType::Asserts
1✔
84
            && let SectionContent::Assertions(lines) = &section.content
1✔
85
        {
86
            let results =
1✔
87
                self.engine
1✔
88
                    .evaluate_all(lines, target_value, Some(headers), Some(trailers));
1✔
89

90
            for (idx, result) in results.iter().enumerate() {
1✔
91
                let line_num = section_content_line(section.start_line, idx);
×
92
                let context = format!("at line {}", line_num);
×
93
                append_single_failure(result, &context, &mut failure_messages);
×
94
            }
×
95
        }
×
96

97
        AssertionResult {
1✔
98
            passed: failure_messages.is_empty(),
1✔
99
            failure_messages,
1✔
100
            records: Vec::new(),
1✔
101
        }
1✔
102
    }
1✔
103

104
    /// Check if section has assertions (test-only)
105
    #[cfg(test)]
106
    pub fn has_assertions(&self, section: &Section) -> bool {
2✔
107
        section.section_type == SectionType::Asserts
2✔
108
    }
2✔
109

110
    /// Get assertion lines from section (test-only)
111
    #[cfg(test)]
112
    pub fn get_assertion_lines<'a>(&self, section: &'a Section) -> Vec<&'a String> {
1✔
113
        if let SectionContent::Assertions(lines) = &section.content {
1✔
114
            lines.iter().collect()
1✔
115
        } else {
116
            Vec::new()
×
117
        }
118
    }
1✔
119

120
    /// Evaluate a single assertion (test-only)
121
    #[cfg(test)]
122
    pub fn evaluate_single_assertion(
1✔
123
        &self,
1✔
124
        assertion: &str,
1✔
125
        target_value: &Value,
1✔
126
        headers: Option<&HashMap<String, String>>,
1✔
127
        trailers: Option<&HashMap<String, String>>,
1✔
128
    ) -> Result<crate::assert::AssertionResult, String> {
1✔
129
        self.engine
1✔
130
            .evaluate(assertion, target_value, headers, trailers)
1✔
131
            .map_err(|e| e.to_string())
1✔
132
    }
1✔
133

134
    /// Evaluate assertions for a section (convenience method for runner.rs)
135
    #[expect(clippy::too_many_arguments)]
136
    pub fn evaluate_assertions_for_section(
52✔
137
        &self,
52✔
138
        lines: &[String],
52✔
139
        target_value: &Value,
52✔
140
        headers: &HashMap<String, String>,
52✔
141
        trailers: &HashMap<String, String>,
52✔
142
        section_context: &str,
52✔
143
        start_line: usize,
52✔
144
        timing: Option<&AssertionTiming>,
52✔
145
        variables: &HashMap<String, Value>,
52✔
146
        protocol: &str,
52✔
147
    ) -> AssertionResult {
52✔
148
        let mut failure_messages = Vec::new();
52✔
149

150
        let evaluated = self.engine.evaluate_all_with_records(
52✔
151
            lines,
52✔
152
            target_value,
52✔
153
            Some(headers),
52✔
154
            Some(trailers),
52✔
155
            timing,
52✔
156
            variables,
52✔
157
            Some(protocol),
52✔
158
        );
159

160
        let mut records = Vec::with_capacity(evaluated.len());
52✔
161
        for (idx, (result, elapsed_ms)) in evaluated.iter().enumerate() {
66✔
162
            let line_num = section_content_line(start_line, idx);
66✔
163
            let context = format!("{} (assertion at line {})", section_context, line_num);
66✔
164
            append_single_failure(result, &context, &mut failure_messages);
66✔
165

166
            let (message, expected, actual) = match result {
66✔
167
                crate::assert::AssertionResult::Fail {
168
                    message,
15✔
169
                    expected,
15✔
170
                    actual,
15✔
171
                } => (Some(message.clone()), expected.clone(), actual.clone()),
15✔
172
                crate::assert::AssertionResult::Error(msg) => (Some(msg.clone()), None, None),
2✔
173
                crate::assert::AssertionResult::Pass => (None, None, None),
49✔
174
            };
175
            records.push(apif_state::AssertionRecord {
66✔
176
                line: line_num,
66✔
177
                expression: lines[idx].clone(),
66✔
178
                passed: matches!(result, crate::assert::AssertionResult::Pass),
66✔
179
                elapsed_ms: *elapsed_ms,
66✔
180
                message,
66✔
181
                endpoint: None,
66✔
182
                expected,
66✔
183
                actual,
66✔
184
            });
185
        }
186

187
        AssertionResult {
52✔
188
            passed: failure_messages.is_empty(),
52✔
189
            failure_messages,
52✔
190
            records,
52✔
191
        }
52✔
192
    }
52✔
193
}
194

195
fn append_single_failure(
68✔
196
    result: &crate::assert::AssertionResult,
68✔
197
    context: &str,
68✔
198
    failure_messages: &mut Vec<String>,
68✔
199
) {
68✔
200
    match result {
68✔
201
        crate::assert::AssertionResult::Fail {
202
            message,
16✔
203
            expected,
16✔
204
            actual,
16✔
205
        } => {
206
            failure_messages.push(format!("Assertion failed {}: {}", context, message));
16✔
207
            if let (Some(exp), Some(act)) = (expected, actual) {
16✔
208
                failure_messages.push(format!("    Expected: {}\n    Actual:   {}", exp, act));
11✔
209
            }
11✔
210
        }
211
        crate::assert::AssertionResult::Error(msg) => {
2✔
212
            failure_messages.push(format!("Assertion error {}: {}", context, msg));
2✔
213
        }
2✔
214
        _ => {}
50✔
215
    }
216
}
68✔
217

218
#[cfg(test)]
219
mod tests {
220
    use super::*;
221
    use serde_json::json;
222

223
    #[test]
224
    fn test_evaluate_assertions_pass() {
1✔
225
        let handler = AssertionHandler::new(false);
1✔
226
        let sections = vec![Section {
1✔
227
            section_type: SectionType::Asserts,
1✔
228
            content: SectionContent::Assertions(vec![".id == 123".to_string()]),
1✔
229
            inline_options: Default::default(),
1✔
230
            raw_content: "".to_string(),
1✔
231
            start_line: 0,
1✔
232
            end_line: 0,
1✔
233
            attributes: Vec::new(),
1✔
234
            span: SectionSpan::default(),
1✔
235
        }];
1✔
236

237
        let target = json!({"id": 123, "name": "test"});
1✔
238
        let headers = HashMap::new();
1✔
239
        let trailers = HashMap::new();
1✔
240

241
        let result = handler.evaluate_assertions(&sections, &target, &headers, &trailers);
1✔
242
        assert!(result.passed);
1✔
243
        assert!(result.failure_messages.is_empty());
1✔
244
    }
1✔
245

246
    #[test]
247
    fn test_evaluate_assertions_fail() {
1✔
248
        let handler = AssertionHandler::new(false);
1✔
249
        let sections = vec![Section {
1✔
250
            section_type: SectionType::Asserts,
1✔
251
            content: SectionContent::Assertions(vec![".id == 456".to_string()]),
1✔
252
            inline_options: Default::default(),
1✔
253
            raw_content: "".to_string(),
1✔
254
            start_line: 0,
1✔
255
            end_line: 0,
1✔
256
            attributes: Vec::new(),
1✔
257
            span: SectionSpan::default(),
1✔
258
        }];
1✔
259

260
        let target = json!({"id": 123, "name": "test"});
1✔
261
        let headers = HashMap::new();
1✔
262
        let trailers = HashMap::new();
1✔
263

264
        let result = handler.evaluate_assertions(&sections, &target, &headers, &trailers);
1✔
265
        assert!(!result.passed);
1✔
266
        assert!(!result.failure_messages.is_empty());
1✔
267
    }
1✔
268

269
    #[test]
270
    fn test_has_assertions() {
1✔
271
        let handler = AssertionHandler::new(false);
1✔
272
        let asserts_section = Section {
1✔
273
            section_type: SectionType::Asserts,
1✔
274
            content: SectionContent::Assertions(vec![]),
1✔
275
            inline_options: Default::default(),
1✔
276
            raw_content: "".to_string(),
1✔
277
            start_line: 0,
1✔
278
            end_line: 0,
1✔
279
            attributes: Vec::new(),
1✔
280
            span: SectionSpan::default(),
1✔
281
        };
1✔
282

283
        let other_section = Section {
1✔
284
            section_type: SectionType::Response,
1✔
285
            content: SectionContent::Json(json!({})),
1✔
286
            inline_options: Default::default(),
1✔
287
            raw_content: "".to_string(),
1✔
288
            start_line: 0,
1✔
289
            end_line: 0,
1✔
290
            attributes: Vec::new(),
1✔
291
            span: SectionSpan::default(),
1✔
292
        };
1✔
293

294
        assert!(handler.has_assertions(&asserts_section));
1✔
295
        assert!(!handler.has_assertions(&other_section));
1✔
296
    }
1✔
297

298
    #[test]
299
    fn test_get_assertion_lines() {
1✔
300
        let handler = AssertionHandler::new(false);
1✔
301
        let section = Section {
1✔
302
            section_type: SectionType::Asserts,
1✔
303
            content: SectionContent::Assertions(vec![
1✔
304
                ".id == 123".to_string(),
1✔
305
                ".name == \"test\"".to_string(),
1✔
306
            ]),
1✔
307
            inline_options: Default::default(),
1✔
308
            raw_content: "".to_string(),
1✔
309
            start_line: 0,
1✔
310
            end_line: 0,
1✔
311
            attributes: Vec::new(),
1✔
312
            span: SectionSpan::default(),
1✔
313
        };
1✔
314

315
        let lines = handler.get_assertion_lines(&section);
1✔
316
        assert_eq!(lines.len(), 2);
1✔
317
    }
1✔
318

319
    #[test]
320
    fn test_evaluate_single_assertion() {
1✔
321
        let handler = AssertionHandler::new(false);
1✔
322
        let target = json!({"id": 123, "name": "test"});
1✔
323
        let headers = HashMap::new();
1✔
324
        let trailers = HashMap::new();
1✔
325

326
        let result = handler.evaluate_single_assertion(
1✔
327
            ".id == 123",
1✔
328
            &target,
1✔
329
            Some(&headers),
1✔
330
            Some(&trailers),
1✔
331
        );
332
        assert!(result.is_ok());
1✔
333
    }
1✔
334

335
    #[test]
336
    fn test_evaluate_section_assertions_empty() {
1✔
337
        let handler = AssertionHandler::new(false);
1✔
338
        let section = Section {
1✔
339
            section_type: SectionType::Asserts,
1✔
340
            content: SectionContent::Assertions(vec![]),
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
            span: SectionSpan::default(),
1✔
347
        };
1✔
348

349
        let target = json!({"id": 123});
1✔
350
        let headers = HashMap::new();
1✔
351
        let trailers = HashMap::new();
1✔
352

353
        let result = handler.evaluate_section_assertions(&section, &target, &headers, &trailers);
1✔
354
        assert!(result.passed);
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