Files
2026-09-04 14:21:05 +02:00

152 lines
6.2 KiB
Rust

// file: crates/ksp-worker-api/unit_tests/lifecycle.rs
// version: 1
const TEST_FAULT: crate::ErrorCode = crate::ErrorCode::new("worker_test", "fault");
fn new_lifecycle() -> std::option::Option<crate::WorkerLifecycle> {
let id = match crate::WorkerId::new("worker-001") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
let kind = match crate::WorkerKindCode::new("continuous_worker") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(crate::WorkerLifecycle::new(id, kind));
}
#[test]
fn pre_002_lifecycle_accepts_every_planned_terminal_path() {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.mark_stopped().is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Stopped);
assert!(lifecycle.state().is_terminal());
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
assert!(lifecycle.mark_running().is_ok());
assert!(lifecycle.mark_stopping().is_ok());
assert!(lifecycle.mark_stopped().is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Stopped);
for fault_from_running in [false, true] {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
if fault_from_running {
assert!(lifecycle.mark_running().is_ok());
}
assert!(lifecycle.fault(TEST_FAULT).is_ok());
assert_eq!(lifecycle.state(), crate::WorkerState::Faulted(TEST_FAULT));
assert_eq!(lifecycle.state().fault_code(), std::option::Option::Some(TEST_FAULT));
}
return;
}
#[test]
fn pre_002_stopping_allows_normal_stop_or_fault() {
for terminal in [crate::WorkerState::Stopped, crate::WorkerState::Faulted(TEST_FAULT)] {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert!(lifecycle.start().is_ok());
assert!(lifecycle.mark_running().is_ok());
assert!(lifecycle.mark_stopping().is_ok());
let result = match terminal {
crate::WorkerState::Stopped => lifecycle.mark_stopped(),
crate::WorkerState::Faulted(code) => lifecycle.fault(code),
_ => return,
};
assert!(result.is_ok());
assert_eq!(lifecycle.state(), terminal);
}
return;
}
#[test]
fn pre_002_invalid_transition_preserves_source_state_and_reports_safe_context() {
let lifecycle = new_lifecycle();
assert!(lifecycle.is_some());
let mut lifecycle = match lifecycle {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let rejected = lifecycle.mark_running();
assert!(rejected.is_err());
assert_eq!(lifecycle.state(), crate::WorkerState::Created);
let error = match rejected {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_TRANSITION_INVALID);
assert_eq!(error.context().len(), 2);
assert_eq!(error.context()[0].key(), "source_state");
assert_eq!(error.context()[0].value(), "created");
assert_eq!(error.context()[1].key(), "target_state");
assert_eq!(error.context()[1].value(), "running");
return;
}
#[test]
fn pre_002_transition_matrix_is_exact() {
let states = [
crate::WorkerState::Created,
crate::WorkerState::Starting,
crate::WorkerState::Running,
crate::WorkerState::Stopping,
crate::WorkerState::Stopped,
crate::WorkerState::Faulted(TEST_FAULT),
];
for source in states {
for target in states {
let expected = matches!(
(source, target),
(crate::WorkerState::Created, crate::WorkerState::Starting)
| (crate::WorkerState::Created, crate::WorkerState::Stopped)
| (crate::WorkerState::Starting, crate::WorkerState::Running)
| (crate::WorkerState::Starting, crate::WorkerState::Stopping)
| (crate::WorkerState::Starting, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Running, crate::WorkerState::Stopping)
| (crate::WorkerState::Running, crate::WorkerState::Faulted(_))
| (crate::WorkerState::Stopping, crate::WorkerState::Stopped)
| (crate::WorkerState::Stopping, crate::WorkerState::Faulted(_))
);
assert_eq!(super::allowed_transition(source, target), expected, "unexpected transition matrix cell: {source:?} -> {target:?}");
}
}
return;
}
#[test]
fn pre_002_health_activity_and_state_codes_are_stable() {
assert_eq!(crate::WorkerState::Created.code(), "created");
assert_eq!(crate::WorkerState::Starting.code(), "starting");
assert_eq!(crate::WorkerState::Running.code(), "running");
assert_eq!(crate::WorkerState::Stopping.code(), "stopping");
assert_eq!(crate::WorkerState::Stopped.code(), "stopped");
assert_eq!(crate::WorkerState::Faulted(TEST_FAULT).code(), "faulted");
assert_eq!(crate::WorkerHealth::Unknown.code(), "unknown");
assert_eq!(crate::WorkerHealth::Healthy.code(), "healthy");
assert_eq!(crate::WorkerHealth::Degraded.code(), "degraded");
assert_eq!(crate::WorkerHealth::Unhealthy.code(), "unhealthy");
assert_eq!(crate::WorkerActivity::Unknown.code(), "unknown");
assert_eq!(crate::WorkerActivity::Idle.code(), "idle");
assert_eq!(crate::WorkerActivity::Active.code(), "active");
return;
}