use chrono::{Duration, Utc};
use xbp_analytics::DoraMetrics;
use xbp_delivery::{DeliveryEvent, DeliveryEventKind, RepositoryId, SourceSystem};
#[test]
fn dora_metrics_project_from_delivery_events() {
let now = Utc::now();
let mut commit = DeliveryEvent::new(
"commit",
DeliveryEventKind::CommitCreated,
SourceSystem::GitHub,
"c1",
);
commit.occurred_at = now - Duration::hours(10);
commit.repository_id = Some(RepositoryId("repo_1".into()));
let mut deployment = DeliveryEvent::new(
"deployment",
DeliveryEventKind::DeploymentSucceeded,
SourceSystem::Xbp,
"d1",
);
deployment.occurred_at = now;
deployment.repository_id = Some(RepositoryId("repo_1".into()));
deployment.attributes.insert(
"environment".into(),
serde_json::Value::String("production".into()),
);
deployment
.attributes
.insert("commit_ids".into(), serde_json::json!(["commit"]));
let metrics = DoraMetrics::from_events(&[commit, deployment], now - Duration::days(1), now);
assert_eq!(metrics.sample_count, 1);
assert!(metrics.lead_time_for_changes_hours.unwrap() >= 9.9);
assert_eq!(metrics.change_failure_rate, Some(0.0));
}
#[test]
fn dora_does_not_correlate_changes_by_repository_proximity() {
let now = Utc::now();
let mut commit = DeliveryEvent::new(
"commit",
DeliveryEventKind::CommitCreated,
SourceSystem::GitHub,
"c1",
);
commit.occurred_at = now - Duration::hours(10);
commit.repository_id = Some(RepositoryId("repo_1".into()));
let mut deployment = DeliveryEvent::new(
"deployment",
DeliveryEventKind::DeploymentSucceeded,
SourceSystem::Xbp,
"d1",
);
deployment.occurred_at = now;
deployment.repository_id = Some(RepositoryId("repo_1".into()));
deployment.attributes.insert(
"environment".into(),
serde_json::Value::String("production".into()),
);
let metrics = DoraMetrics::from_events(&[commit, deployment], now - Duration::days(1), now);
assert_eq!(metrics.sample_count, 0);
assert!(metrics.lead_time_for_changes_hours.is_none());
}
#[test]
fn dora_resolves_linked_commits_before_the_reporting_window() {
let now = Utc::now();
let mut commit = DeliveryEvent::new(
"commit",
DeliveryEventKind::CommitCreated,
SourceSystem::GitHub,
"c1",
);
commit.occurred_at = now - Duration::days(2);
let mut deployment = DeliveryEvent::new(
"deployment",
DeliveryEventKind::DeploymentSucceeded,
SourceSystem::Xbp,
"d1",
);
deployment.occurred_at = now;
deployment.attributes.insert(
"environment".into(),
serde_json::Value::String("production".into()),
);
deployment
.attributes
.insert("commit_ids".into(), serde_json::json!(["commit"]));
let metrics = DoraMetrics::from_events(&[commit, deployment], now - Duration::days(1), now);
assert_eq!(metrics.sample_count, 1);
assert!(metrics.lead_time_for_changes_hours.is_some());
}
#[test]
fn dora_serializes_no_change_failure_rate_as_unknown() {
let now = Utc::now();
let metrics = DoraMetrics::from_events(&[], now - Duration::days(1), now);
let json = serde_json::to_value(&metrics).expect("serialize DORA metrics");
assert!(json["change_failure_rate"].is_null());
assert_eq!(
metrics.deployment_frequency_evidence.availability,
xbp_analytics::MetricAvailability::Available
);
}
#[test]
fn dora_v1_projection_preserves_zero_based_wire_compatibility() {
let now = Utc::now();
let metrics = DoraMetrics::from_events(&[], now - Duration::days(1), now);
let legacy = metrics.to_v1();
assert_eq!(legacy.change_failure_rate, 0.0);
assert_eq!(legacy.deployment_frequency_per_week, 0.0);
assert_eq!(legacy.sample_count, 0);
}
#[test]
fn dora_treats_failure_outcomes_case_insensitively() {
let now = Utc::now();
let mut deployment = DeliveryEvent::new(
"deployment",
DeliveryEventKind::DeploymentSucceeded,
SourceSystem::Xbp,
"d1",
);
deployment.occurred_at = now;
deployment.attributes.insert(
"environment".into(),
serde_json::Value::String("production".into()),
);
deployment
.attributes
.insert("outcome".into(), serde_json::Value::String("FAILED".into()));
let metrics = DoraMetrics::from_events(&[deployment], now - Duration::days(1), now);
assert_eq!(metrics.change_failure_rate, Some(1.0));
}