1use crate::schema::{
4 Baseline, GateResult, MetricsSummary, QualityReport, ReportSummary, Violation,
5};
6
7#[derive(Debug, Clone, Default)]
10pub struct GateConfig {
11 pub max_cyclomatic_per_function: Option<u32>,
12 pub max_nesting_depth: Option<u32>,
13 pub max_lines_per_function: Option<u32>,
14 pub max_lines_per_file: Option<u32>,
15 pub max_code_lines_per_file: Option<u32>,
16 pub max_parameters_per_function: Option<u32>,
17 pub min_coverage_percent: Option<f64>,
18 pub max_duplicate_lines: Option<u32>,
19 pub max_clippy_warnings: Option<u32>,
20 pub max_line_length: Option<usize>,
21}
22
23pub struct Gate;
24
25impl Gate {
26 pub fn run(summary: &MetricsSummary, baseline: &Baseline) -> QualityReport {
28 Self::run_with_config(summary, baseline, None)
29 }
30
31 pub fn run_with_config(
34 summary: &MetricsSummary,
35 baseline: &Baseline,
36 config: Option<&GateConfig>,
37 ) -> QualityReport {
38 let mut violations = Vec::new();
39 let mut collectors_passed = 0u32;
40 let mut collectors_failed = 0u32;
41 let mut collectors_skipped = 0u32;
42
43 let thresholds = &baseline.thresholds;
44 let default_cfg = GateConfig::default();
45 let cfg = config.unwrap_or(&default_cfg);
46
47 macro_rules! check_pass {
48 ($pass:expr, $collector:expr, $metric:expr, $baseline_val:expr, $current_val:expr, $msg:expr) => {
49 if $pass {
50 collectors_passed += 1;
51 } else {
52 collectors_failed += 1;
53 violations.push(Violation {
54 collector: $collector.to_string(),
55 metric: $metric.to_string(),
56 baseline_value: $baseline_val,
57 current_value: $current_val,
58 message: $msg,
59 });
60 }
61 };
62 }
63
64 macro_rules! check_status {
65 ($status:expr, $collector:expr, $must_pass:expr) => {
66 match $status {
67 crate::schema::CollectorStatus::Pass => collectors_passed += 1,
68 crate::schema::CollectorStatus::Fail => {
69 collectors_failed += 1;
70 if $must_pass {
71 violations.push(Violation {
72 collector: $collector.to_string(),
73 metric: "status".to_string(),
74 baseline_value: serde_json::json!(true),
75 current_value: serde_json::json!("fail"),
76 message: format!("{} check failed", $collector),
77 });
78 }
79 }
80 crate::schema::CollectorStatus::Skipped => collectors_skipped += 1,
81 crate::schema::CollectorStatus::Error => collectors_skipped += 1,
82 }
83 };
84 }
85
86 check_status!(summary.collectors.fmt.status, "fmt", thresholds.fmt.must_pass);
88
89 let clippy_max = cfg.max_clippy_warnings.unwrap_or(thresholds.clippy.max_warnings);
91 check_pass!(
92 summary.collectors.clippy.warning_count <= clippy_max,
93 "clippy", "warning_count",
94 serde_json::json!(clippy_max),
95 serde_json::json!(summary.collectors.clippy.warning_count),
96 format!("clippy warnings ({}) exceed max allowed ({})", summary.collectors.clippy.warning_count, clippy_max)
97 );
98
99 check_pass!(
101 summary.collectors.tests.failed <= thresholds.tests.max_failures,
102 "tests", "failed",
103 serde_json::json!(thresholds.tests.max_failures),
104 serde_json::json!(summary.collectors.tests.failed),
105 format!("test failures ({}) exceed max allowed ({})", summary.collectors.tests.failed, thresholds.tests.max_failures)
106 );
107
108 let coverage_min = cfg.min_coverage_percent.unwrap_or(thresholds.coverage.min_line_percent);
110 check_pass!(
111 summary.collectors.coverage.line_percent >= coverage_min,
112 "coverage", "line_percent",
113 serde_json::json!(coverage_min),
114 serde_json::json!(summary.collectors.coverage.line_percent),
115 format!("coverage ({:.1}%) below minimum ({:.1}%)", summary.collectors.coverage.line_percent, coverage_min)
116 );
117
118 check_pass!(
120 summary.collectors.deny.banned_count <= thresholds.deny.max_banned
121 && summary.collectors.deny.license_violations <= thresholds.deny.max_license_violations,
122 "deny", "banned_count + license_violations",
123 serde_json::json!({"max_banned": thresholds.deny.max_banned, "max_license_violations": thresholds.deny.max_license_violations}),
124 serde_json::json!({"banned_count": summary.collectors.deny.banned_count, "license_violations": summary.collectors.deny.license_violations}),
125 format!("deny check failed: {} banned, {} license violations", summary.collectors.deny.banned_count, summary.collectors.deny.license_violations)
126 );
127
128 check_pass!(
130 summary.collectors.audit.vulnerability_count <= thresholds.audit.max_vulnerabilities
131 && summary.collectors.audit.critical_count <= thresholds.audit.max_critical,
132 "audit", "vulnerability_count + critical_count",
133 serde_json::json!({"max_vulnerabilities": thresholds.audit.max_vulnerabilities, "max_critical": thresholds.audit.max_critical}),
134 serde_json::json!({"vulnerability_count": summary.collectors.audit.vulnerability_count, "critical_count": summary.collectors.audit.critical_count}),
135 format!("audit found {} vulnerabilities ({} critical), exceeds baseline", summary.collectors.audit.vulnerability_count, summary.collectors.audit.critical_count)
136 );
137
138 check_status!(summary.collectors.hack.status, "hack", thresholds.hack.must_pass);
140
141 check_pass!(
143 summary.collectors.mutants.mutation_score >= thresholds.mutants.min_score,
144 "mutants", "mutation_score",
145 serde_json::json!(thresholds.mutants.min_score),
146 serde_json::json!(summary.collectors.mutants.mutation_score),
147 format!("mutation score ({:.2}) below minimum ({:.2})", summary.collectors.mutants.mutation_score, thresholds.mutants.min_score)
148 );
149
150 let dup_max = cfg.max_duplicate_lines.unwrap_or(thresholds.duplicates.max_duplicate_lines);
152 check_pass!(
153 summary.collectors.duplicates.duplicate_lines <= dup_max,
154 "duplicates", "duplicate_lines",
155 serde_json::json!(dup_max),
156 serde_json::json!(summary.collectors.duplicates.duplicate_lines),
157 format!("duplicate lines ({}) exceed maximum ({})", summary.collectors.duplicates.duplicate_lines, dup_max)
158 );
159
160 let line_len_max = cfg.max_line_length.unwrap_or(thresholds.loc.max_line_length);
162 check_pass!(
163 summary.collectors.loc.long_lines == 0,
164 "loc", "long_lines",
165 serde_json::json!(0),
166 serde_json::json!(summary.collectors.loc.long_lines),
167 format!("{} lines exceed max length ({})", summary.collectors.loc.long_lines, line_len_max)
168 );
169
170 let size_max_lines_per_file = cfg.max_lines_per_file
172 .or(thresholds.size.max_lines_per_file);
173 let size_max_code_lines_per_file = cfg.max_code_lines_per_file
174 .or(thresholds.size.max_code_lines_per_file);
175 let size_max_lines_per_function = cfg.max_lines_per_function
176 .or(thresholds.size.max_lines_per_function);
177 let size_max_params = cfg.max_parameters_per_function
178 .or(thresholds.size.max_parameters_per_function);
179
180 let size_has_thresholds = size_max_lines_per_file.is_some()
181 || size_max_code_lines_per_file.is_some()
182 || size_max_lines_per_function.is_some()
183 || size_max_params.is_some();
184 check_pass!(
185 !size_has_thresholds || summary.collectors.size.violations.is_empty(),
186 "size", "violations",
187 serde_json::json!(0),
188 serde_json::json!(summary.collectors.size.violations.len()),
189 format!("{} size violation(s) detected", summary.collectors.size.violations.len())
190 );
191
192 let complexity_max_cc = cfg.max_cyclomatic_per_function
194 .or(thresholds.complexity.max_cyclomatic_per_function);
195 let complexity_max_depth = cfg.max_nesting_depth
196 .or(thresholds.complexity.max_nesting_depth);
197
198 let complexity_has_thresholds = complexity_max_cc.is_some()
199 || complexity_max_depth.is_some();
200 check_pass!(
201 !complexity_has_thresholds || summary.collectors.complexity.violations.is_empty(),
202 "complexity", "violations",
203 serde_json::json!(0),
204 serde_json::json!(summary.collectors.complexity.violations.len()),
205 format!("{} complexity violation(s) detected", summary.collectors.complexity.violations.len())
206 );
207
208 let collectors_run = collectors_passed + collectors_failed;
209 let gate_result = if violations.is_empty() {
210 GateResult::Pass
211 } else {
212 GateResult::Fail
213 };
214
215 QualityReport {
216 schema_version: "1".to_string(),
217 generated_at: crate::util::chrono_now(),
218 gate_result,
219 violations,
220 summary: ReportSummary {
221 collectors_run,
222 collectors_passed,
223 collectors_failed,
224 collectors_skipped,
225 },
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::schema::*;
234
235 #[allow(clippy::too_many_arguments)]
236 fn make_summary(
237 fmt_status: CollectorStatus,
238 clippy_warnings: u32,
239 test_failed: u32,
240 line_percent: f64,
241 deny_banned: u32,
242 deny_license: u32,
243 vuln_count: u32,
244 critical_count: u32,
245 hack_status: CollectorStatus,
246 mutation_score: f64,
247 ) -> MetricsSummary {
248 MetricsSummary {
249 schema_version: "1".to_string(),
250 generated_at: "2026-05-04T12:00:00Z".to_string(),
251 rustquty_version: "0.1.0".to_string(),
252 project: ProjectInfo {
253 name: "test".to_string(),
254 rust_edition: "2021".to_string(),
255 workspace_root: "/tmp".to_string(),
256 },
257 collectors: CollectorsSummary {
258 fmt: FmtResult {
259 status: fmt_status,
260 details: Default::default(),
261 },
262 clippy: ClippyResult {
263 status: if clippy_warnings == 0 {
264 CollectorStatus::Pass
265 } else {
266 CollectorStatus::Fail
267 },
268 warning_count: clippy_warnings,
269 details: vec![],
270 },
271 tests: TestResult {
272 status: if test_failed == 0 {
273 CollectorStatus::Pass
274 } else {
275 CollectorStatus::Fail
276 },
277 passed: 10,
278 failed: test_failed,
279 ignored: 0,
280 runner: None,
281 },
282 coverage: CoverageResult {
283 status: CollectorStatus::Pass,
284 line_percent,
285 },
286 deny: DenyResult {
287 status: CollectorStatus::Pass,
288 banned_count: deny_banned,
289 license_violations: deny_license,
290 },
291 audit: AuditResult {
292 status: if vuln_count == 0 {
293 CollectorStatus::Pass
294 } else {
295 CollectorStatus::Fail
296 },
297 vulnerability_count: vuln_count,
298 critical_count,
299 },
300 hack: HackResult {
301 status: hack_status,
302 feature_combinations_tested: 8,
303 },
304 mutants: MutantsResult {
305 status: if mutation_score >= 0.8 {
306 CollectorStatus::Pass
307 } else {
308 CollectorStatus::Fail
309 },
310 mutation_score,
311 caught: 80,
312 missed: 20,
313 },
314 duplicates: DuplicatesResult {
315 status: CollectorStatus::Pass,
316 total_lines: 1000,
317 duplicate_lines: 0,
318 files_with_duplicates: 0,
319 duplicate_files: vec![],
320 },
321 loc: LocResult {
322 status: CollectorStatus::Pass,
323 total_lines: 1000,
324 code_lines: 800,
325 comment_lines: 100,
326 blank_lines: 100,
327 long_lines: 0,
328 max_line_length_found: 100,
329 max_line_length_allowed: 120,
330 files: 10,
331 files_with_long_lines: 0,
332 long_line_files: vec![],
333 },
334 size: SizeResult {
335 status: CollectorStatus::Pass,
336 files: 10,
337 max_lines_per_file: 500,
338 max_code_lines_per_file: 400,
339 max_lines_per_function: 80,
340 max_parameters_per_function: 5,
341 violations: vec![],
342 },
343 complexity: ComplexityResult {
344 status: CollectorStatus::Pass,
345 functions: 10,
346 max_cyclomatic_complexity: 5,
347 max_nesting_depth: 3,
348 complex_functions: 0,
349 violations: vec![],
350 },
351 },
352 }
353 }
354
355 #[allow(clippy::too_many_arguments)]
356 fn make_baseline(
357 fmt_must_pass: bool,
358 max_clippy: u32,
359 max_failures: u32,
360 min_coverage: f64,
361 max_banned: u32,
362 max_license: u32,
363 max_vuln: u32,
364 max_critical: u32,
365 hack_must_pass: bool,
366 min_score: f64,
367 max_duplicate_lines: u32,
368 max_line_length: usize,
369 size_max_lines_per_file: Option<u32>,
370 size_max_code_lines_per_file: Option<u32>,
371 size_max_lines_per_function: Option<u32>,
372 size_max_parameters_per_function: Option<u32>,
373 ) -> Baseline {
374 Baseline {
375 schema_version: "1".to_string(),
376 created_at: "2026-05-04T00:00:00Z".to_string(),
377 rustquty_version: "0.1.0".to_string(),
378 thresholds: Thresholds {
379 fmt: FmtThreshold {
380 must_pass: fmt_must_pass,
381 },
382 clippy: ClippyThreshold {
383 max_warnings: max_clippy,
384 },
385 tests: TestThreshold { max_failures },
386 coverage: CoverageThreshold {
387 min_line_percent: min_coverage,
388 },
389 deny: DenyThreshold {
390 max_banned,
391 max_license_violations: max_license,
392 },
393 audit: AuditThreshold {
394 max_vulnerabilities: max_vuln,
395 max_critical,
396 },
397 hack: HackThreshold {
398 must_pass: hack_must_pass,
399 },
400 mutants: MutantsThreshold { min_score },
401 duplicates: DuplicatesThreshold {
402 max_duplicate_lines,
403 },
404 loc: LocThreshold { max_line_length },
405 size: SizeThreshold {
406 max_lines_per_file: size_max_lines_per_file,
407 max_code_lines_per_file: size_max_code_lines_per_file,
408 max_lines_per_function: size_max_lines_per_function,
409 max_parameters_per_function: size_max_parameters_per_function,
410 },
411 complexity: ComplexityThreshold {
412 max_cyclomatic_per_function: None,
413 max_nesting_depth: None,
414 },
415 },
416 }
417 }
418
419 #[test]
420 fn test_gate_passes_when_all_metrics_within_baseline() {
421 let summary = make_summary(
422 CollectorStatus::Pass,
423 0,
424 0,
425 90.0,
426 0,
427 0,
428 0,
429 0,
430 CollectorStatus::Pass,
431 0.9,
432 );
433 let baseline = make_baseline(
434 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
435 );
436 let report = Gate::run(&summary, &baseline);
437 assert!(matches!(report.gate_result, GateResult::Pass));
438 assert!(report.violations.is_empty());
439 assert_eq!(report.summary.collectors_passed, 12);
440 assert_eq!(report.summary.collectors_failed, 0);
441 }
442
443 #[test]
444 fn test_gate_fails_when_clippy_exceeds_baseline() {
445 let summary = make_summary(
446 CollectorStatus::Pass,
447 5,
448 0,
449 90.0,
450 0,
451 0,
452 0,
453 0,
454 CollectorStatus::Pass,
455 0.9,
456 );
457 let baseline = make_baseline(
458 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
459 );
460 let report = Gate::run(&summary, &baseline);
461 assert!(matches!(report.gate_result, GateResult::Fail));
462 assert_eq!(report.violations.len(), 1);
463 assert_eq!(report.violations[0].collector, "clippy");
464 }
465
466 #[test]
467 fn test_equal_values_do_not_fail() {
468 let summary = make_summary(
470 CollectorStatus::Pass,
471 3,
472 1,
473 85.0,
474 0,
475 0,
476 0,
477 0,
478 CollectorStatus::Pass,
479 0.8,
480 );
481 let baseline = make_baseline(
482 true, 3, 1, 85.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
483 );
484 let report = Gate::run(&summary, &baseline);
485 assert!(matches!(report.gate_result, GateResult::Pass));
486 }
487
488 #[test]
489 fn test_gate_fails_when_loc_exceeds_max_line_length() {
490 let mut summary = make_summary(
491 CollectorStatus::Pass,
492 0,
493 0,
494 90.0,
495 0,
496 0,
497 0,
498 0,
499 CollectorStatus::Pass,
500 0.9,
501 );
502 summary.collectors.loc.long_lines = 5;
503 summary.collectors.loc.status = CollectorStatus::Fail;
504
505 let baseline = make_baseline(
506 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
507 );
508 let report = Gate::run(&summary, &baseline);
509 assert!(matches!(report.gate_result, GateResult::Fail));
510 assert!(report.violations.iter().any(|v| v.collector == "loc"));
511 }
512
513 #[test]
514 fn test_size_gate_passes_without_size_thresholds() {
515 let summary = make_summary(
517 CollectorStatus::Pass,
518 0,
519 0,
520 90.0,
521 0,
522 0,
523 0,
524 0,
525 CollectorStatus::Pass,
526 0.9,
527 );
528 let baseline = make_baseline(
529 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
530 );
531 let report = Gate::run(&summary, &baseline);
532 assert!(matches!(report.gate_result, GateResult::Pass));
533 }
534
535 #[test]
536 fn test_size_gate_fails_with_violations_and_threshold() {
537 let summary = make_summary(
539 CollectorStatus::Pass,
540 0,
541 0,
542 90.0,
543 0,
544 0,
545 0,
546 0,
547 CollectorStatus::Pass,
548 0.9,
549 );
550 let baseline = make_baseline(
551 true,
552 0,
553 0,
554 80.0,
555 0,
556 0,
557 0,
558 0,
559 true,
560 0.8,
561 100,
562 120,
563 Some(500),
564 Some(400),
565 Some(80),
566 Some(5),
567 );
568 let report = Gate::run(&summary, &baseline);
569 assert!(matches!(report.gate_result, GateResult::Pass));
571 }
572
573 #[test]
576 fn test_gate_regression_generated_at_is_iso8601() {
577 let summary = make_summary(
578 CollectorStatus::Pass,
579 0,
580 0,
581 90.0,
582 0,
583 0,
584 0,
585 0,
586 CollectorStatus::Pass,
587 0.9,
588 );
589 let baseline = make_baseline(
590 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
591 );
592 let report = Gate::run(&summary, &baseline);
593 assert!(
595 report.generated_at.contains('T'),
596 "generated_at should be ISO-8601: {}",
597 report.generated_at
598 );
599 assert!(
600 report.generated_at.ends_with('Z'),
601 "generated_at should end with Z: {}",
602 report.generated_at
603 );
604 assert!(
605 report.generated_at.len() == 20,
606 "generated_at should be 20 chars: {}",
607 report.generated_at
608 );
609 }
610
611 #[test]
612 fn test_gate_regression_summary_counts_correct() {
613 let summary = make_summary(
614 CollectorStatus::Pass,
615 5,
616 0,
617 90.0,
618 0,
619 0,
620 0,
621 0,
622 CollectorStatus::Pass,
623 0.9,
624 );
625 let baseline = make_baseline(
626 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
627 );
628 let report = Gate::run(&summary, &baseline);
629 assert_eq!(report.summary.collectors_failed, 1);
631 assert_eq!(report.summary.collectors_passed, 11);
632 assert!(report.violations.iter().any(|v| v.collector == "clippy"));
633 }
634
635 #[test]
636 fn test_gate_regression_violation_messages_not_empty() {
637 let summary = make_summary(
638 CollectorStatus::Pass,
639 10,
640 3,
641 50.0,
642 0,
643 0,
644 0,
645 0,
646 CollectorStatus::Pass,
647 0.5,
648 );
649 let baseline = make_baseline(
650 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
651 );
652 let report = Gate::run(&summary, &baseline);
653 for v in &report.violations {
654 assert!(
655 !v.message.is_empty(),
656 "Violation message should not be empty for {}",
657 v.collector
658 );
659 }
660 }
661
662 #[test]
665 fn test_gate_config_clippy_override() {
666 let summary = make_summary(
668 CollectorStatus::Pass,
669 5,
670 0,
671 90.0,
672 0,
673 0,
674 0,
675 0,
676 CollectorStatus::Pass,
677 0.9,
678 );
679 let baseline = make_baseline(
680 true, 10, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
681 );
682 let config = GateConfig {
683 max_clippy_warnings: Some(0),
684 ..Default::default()
685 };
686 let report = Gate::run_with_config(&summary, &baseline, Some(&config));
687 assert!(matches!(report.gate_result, GateResult::Fail));
688 assert!(report.violations.iter().any(|v| v.collector == "clippy"));
689 }
690
691 #[test]
692 fn test_gate_config_coverage_override() {
693 let summary = make_summary(
695 CollectorStatus::Pass,
696 0,
697 0,
698 60.0,
699 0,
700 0,
701 0,
702 0,
703 CollectorStatus::Pass,
704 0.9,
705 );
706 let baseline = make_baseline(
707 true, 0, 0, 50.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
708 );
709 let config = GateConfig {
710 min_coverage_percent: Some(80.0),
711 ..Default::default()
712 };
713 let report = Gate::run_with_config(&summary, &baseline, Some(&config));
714 assert!(matches!(report.gate_result, GateResult::Fail));
715 assert!(report.violations.iter().any(|v| v.collector == "coverage"));
716 }
717
718 #[test]
719 fn test_gate_config_passes_when_within_absolute_thresholds() {
720 let summary = make_summary(
721 CollectorStatus::Pass,
722 0,
723 0,
724 85.0,
725 0,
726 0,
727 0,
728 0,
729 CollectorStatus::Pass,
730 0.9,
731 );
732 let baseline = make_baseline(
733 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
734 );
735 let config = GateConfig {
736 max_clippy_warnings: Some(0),
737 min_coverage_percent: Some(80.0),
738 max_lines_per_function: Some(80),
739 max_nesting_depth: Some(5),
740 ..Default::default()
741 };
742 let report = Gate::run_with_config(&summary, &baseline, Some(&config));
743 assert!(matches!(report.gate_result, GateResult::Pass));
744 }
745
746 #[test]
747 fn test_gate_config_none_falls_back_to_baseline() {
748 let summary = make_summary(
750 CollectorStatus::Pass,
751 3,
752 0,
753 90.0,
754 0,
755 0,
756 0,
757 0,
758 CollectorStatus::Pass,
759 0.9,
760 );
761 let baseline = make_baseline(
762 true, 5, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
763 );
764 let report = Gate::run_with_config(&summary, &baseline, None);
766 assert!(matches!(report.gate_result, GateResult::Pass));
767 }
768
769 #[test]
770 fn test_gate_config_sonarqube_defaults() {
771 let summary = make_summary(
773 CollectorStatus::Pass,
774 0,
775 0,
776 85.0,
777 0,
778 0,
779 0,
780 0,
781 CollectorStatus::Pass,
782 0.9,
783 );
784 let baseline = make_baseline(
785 true, 0, 0, 80.0, 0, 0, 0, 0, true, 0.8, 100, 120, None, None, None, None,
786 );
787 let config = GateConfig {
788 max_cyclomatic_per_function: Some(15),
789 max_nesting_depth: Some(5),
790 max_lines_per_function: Some(80),
791 max_lines_per_file: Some(1000),
792 max_code_lines_per_file: Some(700),
793 max_parameters_per_function: Some(7),
794 min_coverage_percent: Some(80.0),
795 max_clippy_warnings: Some(0),
796 max_line_length: Some(120),
797 ..Default::default()
798 };
799 let report = Gate::run_with_config(&summary, &baseline, Some(&config));
800 assert!(matches!(report.gate_result, GateResult::Pass));
801 }
802}