cloud-sdk 0.21.0

no_std-first multi-provider cloud SDK foundations.
Documentation
use core::time::Duration;

use super::{
    ActionPollError, ActionPollStep, ActionPoller, ActionUpdate, PollContext, PollDecision,
    PollPolicy,
};
use crate::rate_limit::RateLimit;

struct Policy {
    decision: PollDecision,
    context: Option<PollContext>,
}

impl PollPolicy for Policy {
    type Error = ();

    fn decide(&mut self, context: PollContext) -> Result<PollDecision, Self::Error> {
        self.context = Some(context);
        Ok(self.decision)
    }
}

struct FailingPolicy;

impl PollPolicy for FailingPolicy {
    type Error = u8;

    fn decide(&mut self, _context: PollContext) -> Result<PollDecision, Self::Error> {
        Err(7)
    }
}

#[test]
fn caller_policy_receives_progress_and_rate_limit() {
    let rate_limit = RateLimit::new(3600, 3, 42).ok();
    let mut policy = Policy {
        decision: PollDecision::Delay(Duration::from_secs(2)),
        context: None,
    };
    let mut poller = ActionPoller::new();
    assert_eq!(
        poller.observe(ActionUpdate::<()>::Running, 25, rate_limit, &mut policy),
        Ok(ActionPollStep::Delay(Duration::from_secs(2)))
    );
    assert_eq!(
        policy.context,
        Some(PollContext {
            observation: 1,
            progress: 25,
            rate_limit,
        })
    );
    assert!(!poller.is_terminal());
}

#[test]
fn terminal_success_and_failure_bypass_delay_policy() {
    let mut policy = Policy {
        decision: PollDecision::Delay(Duration::from_secs(1)),
        context: None,
    };
    let mut succeeded = ActionPoller::new();
    assert_eq!(
        succeeded.observe(ActionUpdate::<u8>::Success, 100, None, &mut policy),
        Ok(ActionPollStep::Complete)
    );
    assert!(succeeded.is_terminal());
    assert_eq!(policy.context, None);

    let mut failed = ActionPoller::new();
    assert_eq!(
        failed.observe(ActionUpdate::Failed(7_u8), 60, None, &mut policy),
        Ok(ActionPollStep::Failed(7))
    );
    assert!(failed.is_terminal());
}

#[test]
fn terminal_failure_takes_precedence_over_progress_telemetry() {
    let mut policy = Policy {
        decision: PollDecision::Delay(Duration::from_secs(1)),
        context: None,
    };
    let mut poller = ActionPoller::new();
    assert!(
        poller
            .observe(ActionUpdate::<u8>::Running, 80, None, &mut policy)
            .is_ok()
    );
    assert_eq!(
        poller.observe(ActionUpdate::Failed(7_u8), 60, None, &mut policy),
        Ok(ActionPollStep::Failed(7))
    );
    assert!(poller.is_terminal());
    assert_eq!(poller.observations(), 2);

    let mut malformed_progress = ActionPoller::new();
    assert_eq!(
        malformed_progress.observe(ActionUpdate::Failed(9_u8), 255, None, &mut policy),
        Ok(ActionPollStep::Failed(9))
    );
}

#[test]
fn caller_policy_controls_cancel_and_timeout() {
    for (decision, expected) in [
        (PollDecision::Cancel, ActionPollStep::Cancelled),
        (PollDecision::Timeout, ActionPollStep::TimedOut),
    ] {
        let mut policy = Policy {
            decision,
            context: None,
        };
        let mut poller = ActionPoller::new();
        assert_eq!(
            poller.observe(ActionUpdate::<()>::Running, 0, None, &mut policy),
            Ok(expected)
        );
        assert!(poller.is_terminal());
    }
}

#[test]
fn policy_errors_are_preserved_without_advancing_the_poller() {
    let mut poller = ActionPoller::new();
    assert_eq!(
        poller.observe(ActionUpdate::<()>::Running, 10, None, &mut FailingPolicy,),
        Err(ActionPollError::Policy(7))
    );
    assert_eq!(poller.observations(), 0);
    assert!(!poller.is_terminal());
}

#[test]
fn rejects_busy_loops_progress_regression_and_post_terminal_polling() {
    let mut zero = Policy {
        decision: PollDecision::Delay(Duration::ZERO),
        context: None,
    };
    let mut poller = ActionPoller::new();
    assert_eq!(
        poller.observe(ActionUpdate::<()>::Running, 10, None, &mut zero),
        Err(ActionPollError::ZeroDelay)
    );
    assert_eq!(poller.observations(), 0);

    zero.decision = PollDecision::Delay(Duration::from_secs(1));
    assert!(
        poller
            .observe(ActionUpdate::<()>::Running, 10, None, &mut zero)
            .is_ok()
    );
    assert_eq!(
        poller.observe(ActionUpdate::<()>::Running, 9, None, &mut zero),
        Err(ActionPollError::ProgressRegressed)
    );
    assert_eq!(poller.observations(), 1);

    assert!(
        poller
            .observe(ActionUpdate::<()>::Success, 100, None, &mut zero)
            .is_ok()
    );
    assert_eq!(
        poller.observe(ActionUpdate::<()>::Success, 100, None, &mut zero),
        Err(ActionPollError::Terminal)
    );
}