use super::{TaskId, TaskStatus};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskCheckKind {
Readiness,
Liveness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskCheckStatus {
Pass,
Warn,
Fail,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaskHealthReport {
pub kind: TaskCheckKind,
pub status: TaskCheckStatus,
pub summary: String,
pub observed_tasks: Vec<TaskId>,
}
impl TaskHealthReport {
#[must_use]
pub fn is_passing(&self) -> bool {
matches!(self.status, TaskCheckStatus::Pass)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TaskHealthSnapshot {
pub backend_configured: bool,
pub scheduler_metadata_loaded: bool,
pub registered_tasks: usize,
pub tracked_tasks: Vec<(TaskId, TaskStatus)>,
}
impl TaskHealthSnapshot {
#[must_use]
pub fn readiness(&self) -> TaskHealthReport {
let status = if self.backend_configured && self.scheduler_metadata_loaded {
TaskCheckStatus::Pass
} else {
TaskCheckStatus::Fail
};
let summary = match status {
TaskCheckStatus::Pass => format!(
"task subsystem is ready from local metadata: {} registered task(s); no worker or broker probe was performed",
self.registered_tasks
),
TaskCheckStatus::Fail if !self.backend_configured => {
"task subsystem is not ready: backend configuration is missing; this stub only checks local metadata"
.to_string()
}
_ => {
"task subsystem is not ready: scheduler metadata is unavailable; this stub does not inspect external infrastructure"
.to_string()
}
};
TaskHealthReport {
kind: TaskCheckKind::Readiness,
status,
summary,
observed_tasks: self
.tracked_tasks
.iter()
.map(|(task_id, _)| task_id.clone())
.collect(),
}
}
#[must_use]
pub fn liveness(&self) -> TaskHealthReport {
let active_tasks = self
.tracked_tasks
.iter()
.filter(|(_, status)| {
matches!(status, TaskStatus::Running | TaskStatus::Retrying { .. })
})
.map(|(task_id, _)| task_id.clone())
.collect::<Vec<_>>();
let failed_tasks = self
.tracked_tasks
.iter()
.filter(|(_, status)| matches!(status, TaskStatus::Failed(_)))
.count();
let status = if self.backend_configured {
TaskCheckStatus::Pass
} else {
TaskCheckStatus::Warn
};
let summary = if self.backend_configured {
format!(
"task subsystem is live from local metadata: {} active task(s), {} failed task(s); no worker heartbeat was checked",
active_tasks.len(),
failed_tasks
)
} else {
format!(
"task subsystem can still report metadata, but backend configuration is missing: {} active task(s), {} failed task(s)",
active_tasks.len(),
failed_tasks
)
};
TaskHealthReport {
kind: TaskCheckKind::Liveness,
status,
summary,
observed_tasks: active_tasks,
}
}
}
#[cfg(test)]
mod tests {
use super::{TaskCheckKind, TaskCheckStatus, TaskHealthSnapshot};
use crate::tasks::TaskStatus;
#[test]
fn readiness_passes_when_backend_and_scheduler_metadata_exist() {
let snapshot = TaskHealthSnapshot {
backend_configured: true,
scheduler_metadata_loaded: true,
registered_tasks: 2,
tracked_tasks: vec![],
};
let report = snapshot.readiness();
assert_eq!(report.kind, TaskCheckKind::Readiness);
assert_eq!(report.status, TaskCheckStatus::Pass);
assert!(report.is_passing());
assert!(
report
.summary
.contains("no worker or broker probe was performed")
);
}
#[test]
fn readiness_fails_when_backend_configuration_is_missing() {
let snapshot = TaskHealthSnapshot {
backend_configured: false,
scheduler_metadata_loaded: true,
registered_tasks: 1,
tracked_tasks: vec![("task-1".to_string(), TaskStatus::Pending)],
};
let report = snapshot.readiness();
assert_eq!(report.status, TaskCheckStatus::Fail);
assert_eq!(report.observed_tasks, vec!["task-1".to_string()]);
assert!(report.summary.contains("backend configuration is missing"));
}
#[test]
fn liveness_reports_only_active_task_ids() {
let snapshot = TaskHealthSnapshot {
backend_configured: true,
scheduler_metadata_loaded: false,
registered_tasks: 3,
tracked_tasks: vec![
("task-1".to_string(), TaskStatus::Running),
(
"task-2".to_string(),
TaskStatus::Retrying {
attempt: 2,
max_retries: 3,
},
),
("task-3".to_string(), TaskStatus::Failed("boom".to_string())),
("task-4".to_string(), TaskStatus::Completed),
],
};
let report = snapshot.liveness();
assert_eq!(report.kind, TaskCheckKind::Liveness);
assert_eq!(report.status, TaskCheckStatus::Pass);
assert_eq!(
report.observed_tasks,
vec!["task-1".to_string(), "task-2".to_string()]
);
assert!(report.summary.contains("1 failed task(s)"));
}
#[test]
fn liveness_warns_when_backend_is_not_configured() {
let snapshot = TaskHealthSnapshot {
backend_configured: false,
scheduler_metadata_loaded: false,
registered_tasks: 0,
tracked_tasks: vec![],
};
let report = snapshot.liveness();
assert_eq!(report.status, TaskCheckStatus::Warn);
assert!(!report.is_passing());
assert!(report.summary.contains("backend configuration is missing"));
}
}