use crate::core::engine::{EngineAdapter, EngineError};
use crate::core::navigation::is_navigation_error;
use std::time::Duration;
use tokio_util::sync::CancellationToken;
#[derive(Debug, Clone)]
pub struct WaitResult {
pub completed: bool,
pub aborted: bool,
}
impl WaitResult {
pub fn completed() -> Self {
Self {
completed: true,
aborted: false,
}
}
pub fn aborted() -> Self {
Self {
completed: false,
aborted: true,
}
}
}
pub async fn wait(duration: Duration, _reason: Option<&str>) -> WaitResult {
tokio::time::sleep(duration).await;
WaitResult::completed()
}
pub async fn wait_with_cancel(
duration: Duration,
cancel_token: &CancellationToken,
_reason: Option<&str>,
) -> WaitResult {
if cancel_token.is_cancelled() {
return WaitResult::aborted();
}
tokio::select! {
_ = tokio::time::sleep(duration) => {
WaitResult::completed()
}
_ = cancel_token.cancelled() => {
WaitResult::aborted()
}
}
}
pub async fn evaluate(
adapter: &dyn EngineAdapter,
script: &str,
) -> Result<serde_json::Value, EngineError> {
adapter.evaluate(script).await
}
pub async fn safe_evaluate(
adapter: &dyn EngineAdapter,
script: &str,
default: serde_json::Value,
) -> SafeEvaluateResult {
match adapter.evaluate(script).await {
Ok(value) => SafeEvaluateResult {
success: true,
value,
navigation_error: false,
},
Err(e) if is_navigation_error(&e.to_string()) => SafeEvaluateResult {
success: false,
value: default,
navigation_error: true,
},
Err(_) => SafeEvaluateResult {
success: false,
value: default,
navigation_error: false,
},
}
}
#[derive(Debug, Clone)]
pub struct SafeEvaluateResult {
pub success: bool,
pub value: serde_json::Value,
pub navigation_error: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wait_result_completed() {
let result = WaitResult::completed();
assert!(result.completed);
assert!(!result.aborted);
}
#[test]
fn wait_result_aborted() {
let result = WaitResult::aborted();
assert!(!result.completed);
assert!(result.aborted);
}
#[tokio::test]
async fn wait_completes() {
let start = std::time::Instant::now();
let result = wait(Duration::from_millis(50), Some("test wait")).await;
let elapsed = start.elapsed();
assert!(result.completed);
assert!(!result.aborted);
assert!(elapsed >= Duration::from_millis(50));
}
#[tokio::test]
async fn wait_with_cancel_completes_normally() {
let token = CancellationToken::new();
let result = wait_with_cancel(Duration::from_millis(10), &token, Some("test wait")).await;
assert!(result.completed);
assert!(!result.aborted);
}
#[tokio::test]
async fn wait_with_cancel_aborts_on_cancel() {
let token = CancellationToken::new();
token.cancel();
let result = wait_with_cancel(
Duration::from_secs(10), &token,
Some("test wait"),
)
.await;
assert!(!result.completed);
assert!(result.aborted);
}
#[tokio::test]
async fn wait_with_cancel_aborts_during_wait() {
let token = CancellationToken::new();
let token_clone = token.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
token_clone.cancel();
});
let start = std::time::Instant::now();
let result = wait_with_cancel(
Duration::from_secs(10), &token,
Some("test wait"),
)
.await;
let elapsed = start.elapsed();
assert!(!result.completed);
assert!(result.aborted);
assert!(elapsed < Duration::from_secs(1));
}
#[test]
fn safe_evaluate_result_success() {
let result = SafeEvaluateResult {
success: true,
value: serde_json::json!(42),
navigation_error: false,
};
assert!(result.success);
assert!(!result.navigation_error);
assert_eq!(result.value, serde_json::json!(42));
}
#[test]
fn safe_evaluate_result_navigation_error() {
let result = SafeEvaluateResult {
success: false,
value: serde_json::Value::Null,
navigation_error: true,
};
assert!(!result.success);
assert!(result.navigation_error);
assert!(result.value.is_null());
}
}