use crate::config::{PolicyMode, ResolvedConfig};
use crate::delta::{Delta, FunctionDeltaEntry, FunctionStatus};
use crate::risk::RiskBand;
use crate::snapshot::Snapshot;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PolicyId {
CriticalIntroduction,
ExcessiveRiskRegression,
NetRepoRegression,
WatchThreshold,
AttentionThreshold,
RapidGrowth,
SuppressionMissingReason,
}
impl PolicyId {
pub fn as_str(&self) -> &'static str {
match self {
PolicyId::CriticalIntroduction => "critical-introduction",
PolicyId::ExcessiveRiskRegression => "excessive-risk-regression",
PolicyId::NetRepoRegression => "net-repo-regression",
PolicyId::WatchThreshold => "watch-threshold",
PolicyId::AttentionThreshold => "attention-threshold",
PolicyId::RapidGrowth => "rapid-growth",
PolicyId::SuppressionMissingReason => "suppression-missing-reason",
}
}
fn sort_order(self) -> u8 {
match self {
PolicyId::CriticalIntroduction => 0,
PolicyId::ExcessiveRiskRegression => 1,
PolicyId::WatchThreshold => 2,
PolicyId::AttentionThreshold => 3,
PolicyId::RapidGrowth => 4,
PolicyId::SuppressionMissingReason => 5,
PolicyId::NetRepoRegression => 6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PolicySeverity {
Blocking,
Warning,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PolicyMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub delta_lrs: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_delta: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub growth_percent: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PolicyResult {
pub id: PolicyId,
pub severity: PolicySeverity,
#[serde(skip_serializing_if = "Option::is_none")]
pub function_id: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<PolicyMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PolicyResults {
pub failed: Vec<PolicyResult>,
pub warnings: Vec<PolicyResult>,
}
impl PolicyResults {
pub fn new() -> Self {
Self {
failed: Vec::new(),
warnings: Vec::new(),
}
}
pub fn has_blocking_failures(&self) -> bool {
!self.failed.is_empty()
}
pub fn sort(&mut self) {
self.failed.sort_by(compare_policy_results);
self.warnings.sort_by(compare_policy_results);
}
}
impl Default for PolicyResults {
fn default() -> Self {
Self::new()
}
}
fn compare_policy_results(a: &PolicyResult, b: &PolicyResult) -> Ordering {
let id_order = a.id.sort_order().cmp(&b.id.sort_order());
if id_order != Ordering::Equal {
return id_order;
}
match (&a.function_id, &b.function_id) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(a_id), Some(b_id)) => a_id.cmp(b_id),
}
}
fn active_deltas(deltas: &[FunctionDeltaEntry]) -> impl Iterator<Item = &FunctionDeltaEntry> {
deltas.iter().filter(|e| e.suppression_reason.is_none())
}
pub fn evaluate_policies(
delta: &Delta,
current_snapshot: &Snapshot,
repo_root: &Path,
config: &ResolvedConfig,
) -> Result<Option<PolicyResults>> {
if delta.baseline {
return Ok(None);
}
let mut results = PolicyResults::new();
evaluate_critical_introduction(&delta.deltas, config, &mut results);
evaluate_excessive_risk_regression(&delta.deltas, config, &mut results);
evaluate_watch_threshold(&delta.deltas, config, &mut results);
evaluate_attention_threshold(&delta.deltas, config, &mut results);
evaluate_rapid_growth(&delta.deltas, config, &mut results);
evaluate_suppression_missing_reason(&delta.deltas, &mut results);
evaluate_net_repo_regression(delta, current_snapshot, repo_root, &mut results)?;
results.sort();
Ok(Some(results))
}
fn evaluate_critical_introduction(
deltas: &[FunctionDeltaEntry],
config: &ResolvedConfig,
results: &mut PolicyResults,
) {
if config.critical_introduction_mode == PolicyMode::Off {
return;
}
let severity = match config.critical_introduction_mode {
PolicyMode::Block => PolicySeverity::Blocking,
PolicyMode::Warn => PolicySeverity::Warning,
PolicyMode::Off => unreachable!("handled above"),
};
for entry in active_deltas(deltas) {
let becomes_critical = if let Some(after) = &entry.after {
after.band == RiskBand::Critical
} else {
false
};
if !becomes_critical {
continue;
}
let was_critical_before = if let Some(before) = &entry.before {
before.band == RiskBand::Critical
} else {
false
};
if !was_critical_before {
let message = format!("Function {} introduced as Critical", entry.function_id);
let result = PolicyResult {
id: PolicyId::CriticalIntroduction,
severity,
function_id: Some(entry.function_id.clone()),
message,
metadata: None,
};
match severity {
PolicySeverity::Blocking => results.failed.push(result),
PolicySeverity::Warning => results.warnings.push(result),
}
}
}
}
fn evaluate_excessive_risk_regression(
deltas: &[FunctionDeltaEntry],
config: &ResolvedConfig,
results: &mut PolicyResults,
) {
if config.excessive_risk_regression_mode == PolicyMode::Off {
return;
}
let severity = match config.excessive_risk_regression_mode {
PolicyMode::Block => PolicySeverity::Blocking,
PolicyMode::Warn => PolicySeverity::Warning,
PolicyMode::Off => unreachable!("handled above"),
};
const REGRESSION_THRESHOLD: f64 = 1.0;
for entry in active_deltas(deltas) {
if entry.status != FunctionStatus::Modified {
continue;
}
if let Some(delta) = &entry.delta {
if delta.lrs >= REGRESSION_THRESHOLD {
let message = format!(
"Function {} regressed by {:.2} LRS",
entry.function_id, delta.lrs
);
let result = PolicyResult {
id: PolicyId::ExcessiveRiskRegression,
severity,
function_id: Some(entry.function_id.clone()),
message,
metadata: Some(PolicyMetadata {
delta_lrs: Some(delta.lrs),
total_delta: None,
growth_percent: None,
}),
};
match severity {
PolicySeverity::Blocking => results.failed.push(result),
PolicySeverity::Warning => results.warnings.push(result),
}
}
}
}
}
fn evaluate_watch_threshold(
deltas: &[FunctionDeltaEntry],
config: &ResolvedConfig,
results: &mut PolicyResults,
) {
for entry in active_deltas(deltas) {
if entry.status != FunctionStatus::New && entry.status != FunctionStatus::Modified {
continue;
}
let after_lrs = if let Some(after) = &entry.after {
after.lrs
} else {
continue;
};
let in_watch_range = after_lrs >= config.watch_min && after_lrs < config.watch_max;
if !in_watch_range {
continue;
}
let entering_watch = if let Some(before) = &entry.before {
before.lrs < config.watch_min
} else {
true
};
if entering_watch {
let message = format!(
"Function {} approaching moderate threshold (LRS: {:.2})",
entry.function_id, after_lrs
);
results.warnings.push(PolicyResult {
id: PolicyId::WatchThreshold,
severity: PolicySeverity::Warning,
function_id: Some(entry.function_id.clone()),
message,
metadata: entry.delta.as_ref().map(|d| PolicyMetadata {
delta_lrs: Some(d.lrs),
total_delta: None,
growth_percent: None,
}),
});
}
}
}
fn evaluate_attention_threshold(
deltas: &[FunctionDeltaEntry],
config: &ResolvedConfig,
results: &mut PolicyResults,
) {
for entry in active_deltas(deltas) {
if entry.status != FunctionStatus::New && entry.status != FunctionStatus::Modified {
continue;
}
let after_lrs = if let Some(after) = &entry.after {
after.lrs
} else {
continue;
};
let in_attention_range =
after_lrs >= config.attention_min && after_lrs < config.attention_max;
if !in_attention_range {
continue;
}
let entering_attention = if let Some(before) = &entry.before {
before.lrs < config.attention_min
} else {
true
};
if entering_attention {
let message = format!(
"Function {} approaching high threshold (LRS: {:.2})",
entry.function_id, after_lrs
);
results.warnings.push(PolicyResult {
id: PolicyId::AttentionThreshold,
severity: PolicySeverity::Warning,
function_id: Some(entry.function_id.clone()),
message,
metadata: entry.delta.as_ref().map(|d| PolicyMetadata {
delta_lrs: Some(d.lrs),
total_delta: None,
growth_percent: None,
}),
});
}
}
}
fn evaluate_rapid_growth(
deltas: &[FunctionDeltaEntry],
config: &ResolvedConfig,
results: &mut PolicyResults,
) {
for entry in active_deltas(deltas) {
if entry.status != FunctionStatus::Modified {
continue;
}
let (before_lrs, after_lrs) = match (&entry.before, &entry.after) {
(Some(before), Some(after)) => (before.lrs, after.lrs),
_ => continue,
};
if before_lrs <= f64::EPSILON {
continue;
}
let delta_lrs = after_lrs - before_lrs;
let growth_percent = (delta_lrs / before_lrs) * 100.0;
if growth_percent >= config.rapid_growth_percent {
let message = format!(
"Function {} LRS increased by {:.1}% ({:.2} -> {:.2})",
entry.function_id, growth_percent, before_lrs, after_lrs
);
results.warnings.push(PolicyResult {
id: PolicyId::RapidGrowth,
severity: PolicySeverity::Warning,
function_id: Some(entry.function_id.clone()),
message,
metadata: Some(PolicyMetadata {
delta_lrs: Some(delta_lrs),
total_delta: None,
growth_percent: Some(growth_percent),
}),
});
}
}
}
fn evaluate_suppression_missing_reason(deltas: &[FunctionDeltaEntry], results: &mut PolicyResults) {
for entry in deltas {
if let Some(reason) = &entry.suppression_reason {
if reason.is_empty() {
let message = format!("Function {} suppressed without reason", entry.function_id);
results.warnings.push(PolicyResult {
id: PolicyId::SuppressionMissingReason,
severity: PolicySeverity::Warning,
function_id: Some(entry.function_id.clone()),
message,
metadata: None,
});
}
}
}
}
fn evaluate_net_repo_regression(
delta: &Delta,
current_snapshot: &Snapshot,
repo_root: &Path,
results: &mut PolicyResults,
) -> Result<()> {
let parent_sha = &delta.commit.parent;
let before_snapshot = if !parent_sha.is_empty() {
crate::delta::load_parent_snapshot(repo_root, parent_sha)?
} else {
None
};
let before_total: f64 = if let Some(snapshot) = &before_snapshot {
snapshot.functions.iter().map(|f| f.lrs).sum()
} else {
0.0
};
let after_total: f64 = current_snapshot.functions.iter().map(|f| f.lrs).sum();
let total_delta = after_total - before_total;
if total_delta > 0.0 {
let message = format!("Repository total LRS increased by {:.2}", total_delta);
results.warnings.push(PolicyResult {
id: PolicyId::NetRepoRegression,
severity: PolicySeverity::Warning,
function_id: None,
message,
metadata: Some(PolicyMetadata {
delta_lrs: None,
total_delta: Some(total_delta),
growth_percent: None,
}),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::delta::{Delta, DeltaCommitInfo, FunctionDelta, FunctionDeltaEntry, FunctionState};
use crate::git::GitContext;
use crate::report::MetricsReport;
use tempfile::TempDir;
fn create_test_delta_entry(
function_id: &str,
status: FunctionStatus,
before_band: Option<&str>,
after_band: Option<&str>,
delta_lrs: Option<f64>,
) -> FunctionDeltaEntry {
let before = before_band.map(|band| FunctionState {
metrics: MetricsReport {
cc: 4,
nd: 2,
fo: 2,
ns: 1,
loc: 10,
},
lrs: 3.9,
band: RiskBand::parse(band).unwrap_or(RiskBand::Low),
});
let after = after_band.map(|band| FunctionState {
metrics: MetricsReport {
cc: 6,
nd: 3,
fo: 3,
ns: 1,
loc: 15,
},
lrs: if band == "critical" { 10.5 } else { 6.2 },
band: RiskBand::parse(band).unwrap_or(RiskBand::Low),
});
let delta = delta_lrs.map(|lrs| FunctionDelta {
cc: 2,
nd: 1,
fo: 1,
ns: 0,
lrs,
});
FunctionDeltaEntry {
function_id: function_id.to_string(),
status,
before,
after,
delta,
band_transition: None,
suppression_reason: None,
rename_hint: None,
}
}
#[test]
fn test_critical_introduction_new_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some("critical"),
None,
)];
evaluate_critical_introduction(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 1);
assert_eq!(results.failed[0].id, PolicyId::CriticalIntroduction);
assert_eq!(results.failed[0].severity, PolicySeverity::Blocking);
assert_eq!(
results.failed[0].function_id,
Some("src/foo.ts::handler".to_string())
);
}
#[test]
fn test_critical_introduction_modified_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some("high"),
Some("critical"),
Some(2.3),
)];
evaluate_critical_introduction(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 1);
assert_eq!(results.failed[0].id, PolicyId::CriticalIntroduction);
}
#[test]
fn test_critical_introduction_no_violation() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![
create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some("critical"),
Some("critical"),
Some(0.1),
),
create_test_delta_entry(
"src/bar.ts::process",
FunctionStatus::Modified,
Some("moderate"),
Some("high"),
Some(2.0),
),
];
evaluate_critical_introduction(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 0);
}
#[test]
fn test_excessive_risk_regression_triggered() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some("moderate"),
Some("high"),
Some(1.5), )];
evaluate_excessive_risk_regression(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 1);
assert_eq!(results.failed[0].id, PolicyId::ExcessiveRiskRegression);
assert_eq!(results.failed[0].severity, PolicySeverity::Blocking);
assert!(results.failed[0]
.metadata
.as_ref()
.unwrap()
.delta_lrs
.is_some());
}
#[test]
fn test_excessive_risk_regression_below_threshold() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some("moderate"),
Some("moderate"),
Some(0.9), )];
evaluate_excessive_risk_regression(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 0);
}
#[test]
fn test_excessive_risk_regression_new_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some("high"),
None,
)];
evaluate_excessive_risk_regression(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 0);
}
#[test]
fn test_critical_introduction_warn_mode_downgrades_severity() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("warn".to_string()),
critical_introduction_reason: Some(
"research repo, dense one-shot scripts".to_string(),
),
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
let config = raw.resolve().unwrap();
let mut results = PolicyResults::new();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some("critical"),
None,
)];
evaluate_critical_introduction(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 0);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::CriticalIntroduction);
assert_eq!(results.warnings[0].severity, PolicySeverity::Warning);
}
#[test]
fn test_critical_introduction_off_mode_skips_entirely() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("off".to_string()),
critical_introduction_reason: Some(
"research repo, dense one-shot scripts".to_string(),
),
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
let config = raw.resolve().unwrap();
let mut results = PolicyResults::new();
let deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some("critical"),
None,
)];
evaluate_critical_introduction(&deltas, &config, &mut results);
assert_eq!(results.failed.len(), 0);
assert_eq!(results.warnings.len(), 0);
}
#[test]
fn test_critical_introduction_new_vs_regressed_same_severity() {
let config = ResolvedConfig::defaults().unwrap();
let mut new_results = PolicyResults::new();
let new_deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some("critical"),
None,
)];
evaluate_critical_introduction(&new_deltas, &config, &mut new_results);
let mut regressed_results = PolicyResults::new();
let regressed_deltas = vec![create_test_delta_entry(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some("high"),
Some("critical"),
Some(2.3),
)];
evaluate_critical_introduction(®ressed_deltas, &config, &mut regressed_results);
assert_eq!(
new_results.failed[0].severity,
regressed_results.failed[0].severity
);
}
#[test]
fn test_policy_config_invalid_mode_rejected() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("blocc".to_string()),
critical_introduction_reason: None,
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
assert!(raw.resolve().is_err());
}
#[test]
fn test_policy_config_warn_without_reason_rejected() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("warn".to_string()),
critical_introduction_reason: None,
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
let err = raw.resolve().unwrap_err();
assert!(err.to_string().contains("critical_introduction_reason"));
}
#[test]
fn test_policy_config_warn_with_blank_reason_rejected() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("off".to_string()),
critical_introduction_reason: Some(" ".to_string()),
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
assert!(raw.resolve().is_err());
}
#[test]
fn test_policy_config_block_mode_does_not_require_reason() {
let raw = crate::config::HotspotsConfig {
policy: Some(crate::config::PolicyConfig {
critical_introduction: Some("block".to_string()),
critical_introduction_reason: None,
excessive_risk_regression: None,
excessive_risk_regression_reason: None,
}),
..Default::default()
};
assert!(raw.resolve().is_ok());
}
#[test]
fn test_policy_results_sorting() {
let mut results = PolicyResults::new();
results.failed.push(PolicyResult {
id: PolicyId::ExcessiveRiskRegression,
severity: PolicySeverity::Blocking,
function_id: Some("src/z.ts::func".to_string()),
message: "".to_string(),
metadata: None,
});
results.failed.push(PolicyResult {
id: PolicyId::CriticalIntroduction,
severity: PolicySeverity::Blocking,
function_id: Some("src/a.ts::func".to_string()),
message: "".to_string(),
metadata: None,
});
results.failed.push(PolicyResult {
id: PolicyId::CriticalIntroduction,
severity: PolicySeverity::Blocking,
function_id: Some("src/b.ts::func".to_string()),
message: "".to_string(),
metadata: None,
});
results.sort();
assert_eq!(results.failed[0].id, PolicyId::CriticalIntroduction);
assert_eq!(
results.failed[0].function_id,
Some("src/a.ts::func".to_string())
);
assert_eq!(results.failed[1].id, PolicyId::CriticalIntroduction);
assert_eq!(
results.failed[1].function_id,
Some("src/b.ts::func".to_string())
);
assert_eq!(results.failed[2].id, PolicyId::ExcessiveRiskRegression);
}
#[test]
fn test_baseline_delta_skips_policies() {
let delta = Delta {
schema_version: 1,
commit: DeltaCommitInfo {
sha: "abc123".to_string(),
parent: "".to_string(),
},
baseline: true,
deltas: vec![],
policy: None,
aggregates: None,
};
let temp_dir = TempDir::new().unwrap();
let repo_root = temp_dir.path();
let git_context = GitContext {
head_sha: "abc123".to_string(),
parent_shas: vec![],
timestamp: 1705600000,
branch: Some("main".to_string()),
is_detached: false,
message: Some("test commit".to_string()),
author: Some("Test Author".to_string()),
is_fix_commit: Some(false),
is_revert_commit: Some(false),
ticket_ids: vec![],
};
let snapshot = Snapshot::new(git_context, vec![]);
let config = ResolvedConfig::defaults().unwrap();
let result = evaluate_policies(&delta, &snapshot, repo_root, &config).unwrap();
assert!(result.is_none());
}
fn create_test_delta_entry_with_lrs(
function_id: &str,
status: FunctionStatus,
before_lrs: Option<f64>,
after_lrs: Option<f64>,
) -> FunctionDeltaEntry {
let before = before_lrs.map(|lrs| FunctionState {
metrics: MetricsReport {
cc: 4,
nd: 2,
fo: 2,
ns: 1,
loc: 10,
},
lrs,
band: if lrs >= 9.0 {
RiskBand::Critical
} else if lrs >= 6.0 {
RiskBand::High
} else if lrs >= 3.0 {
RiskBand::Moderate
} else {
RiskBand::Low
},
});
let after = after_lrs.map(|lrs| FunctionState {
metrics: MetricsReport {
cc: 6,
nd: 3,
fo: 3,
ns: 1,
loc: 15,
},
lrs,
band: if lrs >= 9.0 {
RiskBand::Critical
} else if lrs >= 6.0 {
RiskBand::High
} else if lrs >= 3.0 {
RiskBand::Moderate
} else {
RiskBand::Low
},
});
let delta = match (before_lrs, after_lrs) {
(Some(before), Some(after)) => Some(FunctionDelta {
cc: 2,
nd: 1,
fo: 1,
ns: 0,
lrs: after - before,
}),
_ => None,
};
FunctionDeltaEntry {
function_id: function_id.to_string(),
status,
before,
after,
delta,
band_transition: None,
suppression_reason: None,
rename_hint: None,
}
}
#[test]
fn test_watch_threshold_new_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some(2.7),
)];
evaluate_watch_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::WatchThreshold);
assert_eq!(results.warnings[0].severity, PolicySeverity::Warning);
}
#[test]
fn test_watch_threshold_modified_entering_range() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.0),
Some(2.8),
)];
evaluate_watch_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::WatchThreshold);
}
#[test]
fn test_watch_threshold_already_in_range() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.6),
Some(2.9),
)];
evaluate_watch_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 0);
}
#[test]
fn test_watch_threshold_above_range() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.0),
Some(4.0),
)];
evaluate_watch_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 0);
}
#[test]
fn test_attention_threshold_new_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some(5.8),
)];
evaluate_attention_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::AttentionThreshold);
assert_eq!(results.warnings[0].severity, PolicySeverity::Warning);
}
#[test]
fn test_attention_threshold_modified_entering_range() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(5.0),
Some(5.7),
)];
evaluate_attention_threshold(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::AttentionThreshold);
}
#[test]
fn test_rapid_growth_triggered() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.0),
Some(4.0),
)];
evaluate_rapid_growth(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::RapidGrowth);
assert_eq!(results.warnings[0].severity, PolicySeverity::Warning);
assert!(results.warnings[0]
.metadata
.as_ref()
.unwrap()
.growth_percent
.is_some());
let growth = results.warnings[0]
.metadata
.as_ref()
.unwrap()
.growth_percent
.unwrap();
assert!((growth - 100.0).abs() < 0.1); }
#[test]
fn test_rapid_growth_exactly_at_threshold() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.0),
Some(3.0),
)];
evaluate_rapid_growth(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].id, PolicyId::RapidGrowth);
}
#[test]
fn test_rapid_growth_below_threshold() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(2.5),
Some(3.5),
)];
evaluate_rapid_growth(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 0);
}
#[test]
fn test_rapid_growth_negative_delta() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::Modified,
Some(5.0),
Some(3.0),
)];
evaluate_rapid_growth(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 0);
}
#[test]
fn test_rapid_growth_new_function() {
let mut results = PolicyResults::new();
let config = ResolvedConfig::defaults().unwrap();
let deltas = vec![create_test_delta_entry_with_lrs(
"src/foo.ts::handler",
FunctionStatus::New,
None,
Some(10.0),
)];
evaluate_rapid_growth(&deltas, &config, &mut results);
assert_eq!(results.warnings.len(), 0);
}
}