// 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 { 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; }