v0.3.9-pre.002
This commit is contained in:
64
crates/ksp-worker-api/unit_tests/identity.rs
Normal file
64
crates/ksp-worker-api/unit_tests/identity.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
// file: crates/ksp-worker-api/unit_tests/identity.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn pre_002_identity_accepts_safe_codes_at_exact_bounds() {
|
||||
let worker_id = crate::WorkerId::new("a".repeat(crate::MAX_WORKER_ID_BYTES));
|
||||
let kind = crate::WorkerKindCode::new("continuous.raw:example-1");
|
||||
assert!(worker_id.is_ok());
|
||||
assert!(kind.is_ok());
|
||||
let worker_id = match worker_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let kind = match kind {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(worker_id.as_str().len(), crate::MAX_WORKER_ID_BYTES);
|
||||
assert_eq!(kind.as_str(), "continuous.raw:example-1");
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_identity_rejects_empty_oversized_and_unsafe_values() {
|
||||
for value in [
|
||||
std::string::String::new(),
|
||||
"a".repeat(crate::MAX_WORKER_ID_BYTES + 1),
|
||||
"unsafe/value".to_string(),
|
||||
"unsafe\\value".to_string(),
|
||||
"space value".to_string(),
|
||||
"unicode-é".to_string(),
|
||||
] {
|
||||
let rejected = crate::WorkerId::new(value);
|
||||
assert!(rejected.is_err());
|
||||
let error = match rejected {
|
||||
std::result::Result::Err(value) => value,
|
||||
std::result::Result::Ok(_) => continue,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_ID_INVALID);
|
||||
assert_eq!(error.context().len(), 1);
|
||||
assert_eq!(error.context()[0].key(), "field");
|
||||
assert_eq!(error.context()[0].value(), "worker_id");
|
||||
}
|
||||
for value in [std::string::String::new(), "b".repeat(crate::MAX_WORKER_KIND_CODE_BYTES + 1), "worker kind".to_string()] {
|
||||
let rejected = crate::WorkerKindCode::new(value);
|
||||
assert!(rejected.is_err());
|
||||
let error = match rejected {
|
||||
std::result::Result::Err(value) => value,
|
||||
std::result::Result::Ok(_) => continue,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_KIND_INVALID);
|
||||
assert_eq!(error.context()[0].value(), "worker_kind");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_private_validator_uses_exact_safe_code_alphabet() {
|
||||
assert!(super::valid_worker_code("AZaz09_.:-", 10));
|
||||
for rejected in ["slash/value", "back\\slash", "space value", "line\nbreak", "é"] {
|
||||
assert!(!super::valid_worker_code(rejected, crate::MAX_WORKER_ID_BYTES));
|
||||
}
|
||||
return;
|
||||
}
|
||||
151
crates/ksp-worker-api/unit_tests/lifecycle.rs
Normal file
151
crates/ksp-worker-api/unit_tests/lifecycle.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
// 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;
|
||||
}
|
||||
66
crates/ksp-worker-api/unit_tests/snapshot.rs
Normal file
66
crates/ksp-worker-api/unit_tests/snapshot.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
// file: crates/ksp-worker-api/unit_tests/snapshot.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn pre_002_snapshot_sequence_advances_strictly_and_orders_positions() {
|
||||
let first = crate::WorkerSnapshotSequence::initial();
|
||||
assert_eq!(first.value(), 0);
|
||||
let second = first.next();
|
||||
assert!(second.is_ok());
|
||||
let second = match second {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(second.value(), 1);
|
||||
assert!(second.is_after(first));
|
||||
assert!(!first.is_after(second));
|
||||
assert!(!first.is_after(first));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_snapshot_sequence_exhaustion_is_explicit_and_non_wrapping() {
|
||||
let exhausted = super::exhausted_snapshot_sequence();
|
||||
let result = exhausted.next();
|
||||
assert!(result.is_err());
|
||||
let error = match result {
|
||||
std::result::Result::Err(value) => value,
|
||||
std::result::Result::Ok(_) => return,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WORKER_SNAPSHOT_SEQUENCE_EXHAUSTED);
|
||||
assert_eq!(error.context().len(), 1);
|
||||
assert_eq!(error.context()[0].key(), "sequence");
|
||||
assert_eq!(error.context()[0].value(), u64::MAX.to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_snapshot_preserves_exact_common_dimensions() {
|
||||
let id = crate::WorkerId::new("worker-001");
|
||||
let kind = crate::WorkerKindCode::new("example_worker");
|
||||
assert!(id.is_ok());
|
||||
assert!(kind.is_ok());
|
||||
let id = match id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let kind = match kind {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let snapshot = crate::WorkerSnapshot::new(
|
||||
id,
|
||||
kind,
|
||||
crate::WorkerSnapshotSequence::initial(),
|
||||
crate::WorkerState::Running,
|
||||
crate::WorkerHealth::Healthy,
|
||||
crate::WorkerActivity::Active,
|
||||
);
|
||||
assert_eq!(snapshot.id().as_str(), "worker-001");
|
||||
assert_eq!(snapshot.kind().as_str(), "example_worker");
|
||||
assert_eq!(snapshot.sequence().value(), 0);
|
||||
assert_eq!(snapshot.state(), crate::WorkerState::Running);
|
||||
assert_eq!(snapshot.health(), crate::WorkerHealth::Healthy);
|
||||
assert_eq!(snapshot.activity(), crate::WorkerActivity::Active);
|
||||
return;
|
||||
}
|
||||
22
crates/ksp-worker-api/unit_tests/stop.rs
Normal file
22
crates/ksp-worker-api/unit_tests/stop.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// file: crates/ksp-worker-api/unit_tests/stop.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn pre_002_stop_token_is_shared_and_idempotent() {
|
||||
let token = crate::WorkerStopToken::new();
|
||||
let clone = token.clone();
|
||||
assert!(!token.is_stop_requested());
|
||||
assert!(clone.request_stop());
|
||||
assert!(token.is_stop_requested());
|
||||
assert!(!token.request_stop());
|
||||
assert!(!clone.request_stop());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_default_stop_token_starts_without_stop_request() {
|
||||
let token = crate::WorkerStopToken::default();
|
||||
assert!(!token.is_stop_requested());
|
||||
assert_eq!(std::format!("{token:?}"), "WorkerStopToken { stop_requested: false }");
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user