Files
crank/crates/crank-core/tests/execution.rs
T

143 lines
5.0 KiB
Rust

use crank_core::{
AgentId, CorrelationContext, ExecutionErrorCode, ExecutionFailure, ExecutionOrigin,
ExecutionStage, OutcomeCertainty, Retryability,
};
#[test]
fn frozen_execution_taxonomy_is_closed_and_consistent() {
let cases = [
(
ExecutionErrorCode::AuthorizationDenied,
ExecutionStage::Authorization,
Retryability::Never,
OutcomeCertainty::Certain,
),
(
ExecutionErrorCode::InputSchemaInvalid,
ExecutionStage::InputSchema,
Retryability::Never,
OutcomeCertainty::Certain,
),
(
ExecutionErrorCode::ExecutionOverloaded,
ExecutionStage::Admission,
Retryability::AfterDelay,
OutcomeCertainty::Certain,
),
(
ExecutionErrorCode::ConfirmationRequired,
ExecutionStage::Admission,
Retryability::RequiresConfirmation,
OutcomeCertainty::Certain,
),
(
ExecutionErrorCode::IdempotencyOutcomeUnknown,
ExecutionStage::Admission,
Retryability::ManualReconcile,
OutcomeCertainty::OutcomeUnknown,
),
(
ExecutionErrorCode::RuntimeInternal,
ExecutionStage::Runtime,
Retryability::Never,
OutcomeCertainty::Certain,
),
];
for (code, stage, retryability, certainty) in cases {
let descriptor = ExecutionFailure::new(code, CorrelationContext::generate());
assert_eq!(descriptor.stage(), stage);
assert_eq!(descriptor.retryability(), retryability);
assert_eq!(descriptor.outcome_certainty(), certainty);
assert_eq!(descriptor.error_code(), code);
}
}
#[test]
fn execution_origin_enforces_agent_scope() {
let agent_id = AgentId::new("agent_01");
assert!(ExecutionOrigin::AdminDraft.validate_agent(None).is_ok());
assert!(
ExecutionOrigin::AdminDraft
.validate_agent(Some(&agent_id))
.is_err()
);
assert!(
ExecutionOrigin::AgentSnapshot
.validate_agent(Some(&agent_id))
.is_ok()
);
assert!(ExecutionOrigin::AgentSnapshot.validate_agent(None).is_err());
}
#[test]
fn dispatch_uncertainty_is_allowed_only_for_ambiguous_codes() {
let correlation = CorrelationContext::generate();
let timeout = ExecutionFailure::new(ExecutionErrorCode::UpstreamTimeout, correlation.clone())
.with_dispatch_uncertainty();
assert_eq!(timeout.retryability(), Retryability::ManualReconcile);
assert_eq!(
timeout.outcome_certainty(),
OutcomeCertainty::OutcomeUnknown
);
let validation = ExecutionFailure::new(ExecutionErrorCode::InputSchemaInvalid, correlation)
.with_dispatch_uncertainty();
assert_eq!(validation.retryability(), Retryability::Never);
assert_eq!(validation.outcome_certainty(), OutcomeCertainty::Certain);
}
#[test]
fn failure_metadata_is_code_specific_and_bounded() {
let correlation = CorrelationContext::generate();
assert!(
ExecutionFailure::new(ExecutionErrorCode::InputSchemaInvalid, correlation.clone())
.try_with_retry_after_ms(1)
.is_err()
);
assert!(
ExecutionFailure::new(ExecutionErrorCode::UpstreamRateLimited, correlation.clone())
.try_with_retry_after_ms(3_600_001)
.is_err()
);
assert!(
ExecutionFailure::new(ExecutionErrorCode::RuntimeInternal, correlation.clone())
.try_with_confirmation("token", 1)
.is_err()
);
assert!(
ExecutionFailure::new(
ExecutionErrorCode::ConfirmationRequired,
correlation.clone()
)
.try_with_confirmation("x".repeat(4_097), 1)
.is_err()
);
let challenge = ExecutionFailure::new(ExecutionErrorCode::ConfirmationRequired, correlation)
.try_with_confirmation("secret-canary", 1_000)
.expect("bounded confirmation");
assert!(!format!("{challenge:?}").contains("secret-canary"));
}
#[test]
fn unknown_taxonomy_values_are_rejected_by_serde() {
assert!(serde_json::from_str::<ExecutionStage>("\"unknown\"").is_err());
assert!(serde_json::from_str::<ExecutionErrorCode>("\"unknown\"").is_err());
assert!(serde_json::from_str::<Retryability>("\"automatic\"").is_err());
}
#[test]
fn every_published_code_has_stable_wire_value_stage_and_localized_messages() {
let mut wire_values = std::collections::BTreeSet::new();
for code in ExecutionErrorCode::ALL {
assert!(wire_values.insert(code.as_str()));
assert_eq!(serde_json::to_value(code).unwrap(), code.as_str());
assert!(!code.message(crank_core::ExecutionLocale::Ru).is_empty());
assert!(!code.message(crank_core::ExecutionLocale::En).is_empty());
assert!(!code.stage().as_str().is_empty());
assert!(!code.retryability().as_str().is_empty());
assert!(!code.outcome_certainty().as_str().is_empty());
}
assert_eq!(wire_values.len(), ExecutionErrorCode::ALL.len());
}