Skip to content

Commit 09fd1da

Browse files
committed
feat: 实现 S05 5.4 报告产物 freshness(5 测试全绿)
- 报告产物 freshness + 陈旧/伪造报告不能通过 - scope/rule 执行覆盖核对 - ReportFreshnessChecker + ReportMetadata + ReportStatus - 5.4 已勾选,5/5 测试全绿
1 parent 920ed30 commit 09fd1da

3 files changed

Lines changed: 183 additions & 0 deletions

File tree

‎crates/codeguard-adapters/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,4 @@ pub mod adapter_protocol;
520520
pub mod conformance_harness;
521521
pub mod tool_resolution;
522522
pub mod tools_install;
523+
pub mod report_freshness;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! 报告产物 freshness:陈旧或伪造空成功报告不能通过
2+
//!
3+
//! 验收标准:scope/rule 执行覆盖核对
4+
5+
use serde::{Deserialize, Serialize};
6+
7+
/// 报告状态
8+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9+
pub enum ReportStatus {
10+
/// 新鲜
11+
Fresh,
12+
/// 陈旧
13+
Stale,
14+
/// 伪造
15+
Forged,
16+
}
17+
18+
/// 报告元数据
19+
#[derive(Debug, Clone, Serialize, Deserialize)]
20+
pub struct ReportMetadata {
21+
/// 报告 ID
22+
pub id: String,
23+
/// 创建时间
24+
pub created_at: u64,
25+
/// 过期时间
26+
pub expires_at: u64,
27+
/// 覆盖的规则
28+
pub rules_covered: Vec<String>,
29+
/// 覆盖的范围
30+
pub scope_covered: Vec<String>,
31+
}
32+
33+
/// Freshness 检查结果
34+
#[derive(Debug, Clone, Serialize, Deserialize)]
35+
pub struct FreshnessResult {
36+
/// 报告状态
37+
pub status: ReportStatus,
38+
/// 是否通过
39+
pub passed: bool,
40+
/// 原因
41+
pub reason: Option<String>,
42+
}
43+
44+
/// 报告 freshness 检查器
45+
pub struct ReportFreshnessChecker;
46+
47+
impl ReportFreshnessChecker {
48+
/// 检查报告 freshness
49+
pub fn check(
50+
metadata: &ReportMetadata,
51+
current_time: u64,
52+
expected_rules: &[String],
53+
expected_scope: &[String],
54+
) -> FreshnessResult {
55+
// 检查是否陈旧
56+
if current_time > metadata.expires_at {
57+
return FreshnessResult {
58+
status: ReportStatus::Stale,
59+
passed: false,
60+
reason: Some("report_expired".into()),
61+
};
62+
}
63+
64+
// 检查是否伪造空成功
65+
if metadata.rules_covered.is_empty() && metadata.scope_covered.is_empty() {
66+
return FreshnessResult {
67+
status: ReportStatus::Forged,
68+
passed: false,
69+
reason: Some("empty_success_forged".into()),
70+
};
71+
}
72+
73+
// 检查覆盖范围
74+
let rules_covered = expected_rules.iter()
75+
.all(|r| metadata.rules_covered.contains(r));
76+
let scope_covered = expected_scope.iter()
77+
.all(|s| metadata.scope_covered.contains(s));
78+
79+
if !rules_covered || !scope_covered {
80+
return FreshnessResult {
81+
status: ReportStatus::Stale,
82+
passed: false,
83+
reason: Some("incomplete_coverage".into()),
84+
};
85+
}
86+
87+
FreshnessResult {
88+
status: ReportStatus::Fresh,
89+
passed: true,
90+
reason: None,
91+
}
92+
}
93+
94+
/// 验证报告不伪造
95+
pub fn validate_not_forged(metadata: &ReportMetadata) -> bool {
96+
!metadata.rules_covered.is_empty() || !metadata.scope_covered.is_empty()
97+
}
98+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//! 报告 freshness 测试
2+
3+
use codeguard_adapters::report_freshness::*;
4+
5+
fn create_metadata() -> ReportMetadata {
6+
ReportMetadata {
7+
id: "report-1".into(),
8+
created_at: 100,
9+
expires_at: 200,
10+
rules_covered: vec!["rule1".into(), "rule2".into()],
11+
scope_covered: vec!["file1.rs".into()],
12+
}
13+
}
14+
15+
#[test]
16+
fn fresh_report_passes() {
17+
let metadata = create_metadata();
18+
let result = ReportFreshnessChecker::check(
19+
&metadata,
20+
150,
21+
&["rule1".to_string(), "rule2".to_string()],
22+
&["file1.rs".to_string()],
23+
);
24+
25+
assert!(result.passed);
26+
assert_eq!(result.status, ReportStatus::Fresh);
27+
}
28+
29+
#[test]
30+
fn stale_report_fails() {
31+
let metadata = create_metadata();
32+
let result = ReportFreshnessChecker::check(
33+
&metadata,
34+
250,
35+
&["rule1".to_string()],
36+
&["file1.rs".to_string()],
37+
);
38+
39+
assert!(!result.passed);
40+
assert_eq!(result.status, ReportStatus::Stale);
41+
}
42+
43+
#[test]
44+
fn forged_empty_report_fails() {
45+
let metadata = ReportMetadata {
46+
id: "report-1".into(),
47+
created_at: 100,
48+
expires_at: 200,
49+
rules_covered: vec![],
50+
scope_covered: vec![],
51+
};
52+
53+
let result = ReportFreshnessChecker::check(&metadata, 150, &[], &[]);
54+
assert!(!result.passed);
55+
assert_eq!(result.status, ReportStatus::Forged);
56+
}
57+
58+
#[test]
59+
fn incomplete_coverage_fails() {
60+
let metadata = create_metadata();
61+
let result = ReportFreshnessChecker::check(
62+
&metadata,
63+
150,
64+
&["rule1".to_string(), "rule2".to_string(), "rule3".to_string()],
65+
&["file1.rs".to_string()],
66+
);
67+
68+
assert!(!result.passed);
69+
}
70+
71+
#[test]
72+
fn validate_not_forged() {
73+
let metadata = create_metadata();
74+
assert!(ReportFreshnessChecker::validate_not_forged(&metadata));
75+
76+
let empty = ReportMetadata {
77+
id: "report-1".into(),
78+
created_at: 100,
79+
expires_at: 200,
80+
rules_covered: vec![],
81+
scope_covered: vec![],
82+
};
83+
assert!(!ReportFreshnessChecker::validate_not_forged(&empty));
84+
}

0 commit comments

Comments
 (0)