use crate::domain::error::{AivcsError, Result};
use crate::multi_repo::health::{CIHealthView, RepoCIStatus};
use crate::multi_repo::model::{CrossRepoGraph, RepoId};
#[derive(Debug, Clone)]
pub struct MultiRepoExecutionPlan {
pub order: Vec<RepoId>,
}
pub struct MultiRepoOrchestrator;
impl MultiRepoOrchestrator {
pub fn execution_plan(graph: &CrossRepoGraph) -> Result<MultiRepoExecutionPlan> {
let order = graph.execution_order().map_err(AivcsError::MultiRepo)?;
Ok(MultiRepoExecutionPlan { order })
}
pub fn rollout_allowed(health: &CIHealthView) -> bool {
health.can_rollout()
}
pub fn check_rollout_gate(health: &CIHealthView) -> Result<()> {
if health.rollout_blocked() {
let status = health.overall_status();
return Err(AivcsError::MultiRepo(format!(
"rollout blocked: overall CI status is {:?}",
status
)));
}
Ok(())
}
pub fn consolidate_health(objective_id: &str, repos: Vec<RepoCIStatus>) -> CIHealthView {
CIHealthView::new(objective_id, repos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::ci::CIStatus;
use crate::multi_repo::model::RepoDependency;
fn repo_status(name: &str, status: CIStatus) -> RepoCIStatus {
RepoCIStatus {
repo: RepoId::new(name),
status,
run_id: Some(uuid::Uuid::new_v4()),
updated_at: chrono::Utc::now(),
}
}
#[test]
fn test_execution_plan_order() {
let a = RepoId::new("a");
let b = RepoId::new("b");
let c = RepoId::new("c");
let graph = CrossRepoGraph::new(
vec![a.clone(), b.clone(), c.clone()],
vec![
RepoDependency {
dependent: c.clone(),
dependency: b.clone(),
},
RepoDependency {
dependent: b.clone(),
dependency: a.clone(),
},
],
);
let plan = MultiRepoOrchestrator::execution_plan(&graph).expect("plan");
assert_eq!(plan.order.len(), 3);
assert_eq!(plan.order[0].name, "a");
assert_eq!(plan.order[1].name, "b");
assert_eq!(plan.order[2].name, "c");
}
#[test]
fn test_execution_plan_cycle_errors() {
let a = RepoId::new("a");
let b = RepoId::new("b");
let graph = CrossRepoGraph::new(
vec![a.clone(), b.clone()],
vec![
RepoDependency {
dependent: b.clone(),
dependency: a.clone(),
},
RepoDependency {
dependent: a.clone(),
dependency: b.clone(),
},
],
);
let res = MultiRepoOrchestrator::execution_plan(&graph);
assert!(matches!(res, Err(AivcsError::MultiRepo(_))));
}
#[test]
fn test_rollout_allowed_all_pass() {
let health = MultiRepoOrchestrator::consolidate_health(
"obj-1",
vec![
repo_status("org/a", CIStatus::Passed),
repo_status("org/b", CIStatus::Passed),
],
);
assert!(MultiRepoOrchestrator::rollout_allowed(&health));
assert!(MultiRepoOrchestrator::check_rollout_gate(&health).is_ok());
}
#[test]
fn test_rollout_blocked_on_fail() {
let health = MultiRepoOrchestrator::consolidate_health(
"obj-1",
vec![
repo_status("org/a", CIStatus::Passed),
repo_status("org/b", CIStatus::Failed),
],
);
assert!(!MultiRepoOrchestrator::rollout_allowed(&health));
assert!(MultiRepoOrchestrator::check_rollout_gate(&health).is_err());
}
}