cloud-sdk 0.55.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
use super::fixture::{endpoint, prepared, read_only};
use crate::operation::{
    AttemptBudget, CostIntent, CurrencyCode, OperationImpact, PermitContext, PermitIdempotencyKey,
    PermitTimestamp, PermitValidity, PlanChange, PlanConfirmation, PlanCost,
    PlanFingerprintBuildError, PlanFingerprintScope, ReplayPolicy, build_canonical_plan,
    build_plan_digest,
};
use crate::retry::{DigestAlgorithm, FingerprintHasher};

#[cfg(feature = "std")]
use crate::std as test_std;

const ACCOUNT: &[u8] = b"account-a";
const TENANT: &[u8] = b"tenant-a";
const CONTEXT: &[u8] = b"review-ticket-42";
const IDENTITY: &[u8] = b"0123456789abcdef0123456789abcdef";

#[cfg(feature = "std")]
#[test]
fn canonical_plan_is_domain_separated_bounded_and_cleared() {
    let Some(request) = prepared(
        "/resources?label=one",
        OperationImpact::Mutation,
        CostIntent::NoKnownCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(endpoint) = endpoint() else {
        unreachable!("permit security fixture construction failed")
    };
    let Some(plan) = plan(
        request,
        endpoint,
        CONTEXT,
        200,
        ReplayPolicy::ReconcileThenRetry,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let mut output = [0xa5_u8; 4096];
    {
        let Ok(fingerprint) = build_canonical_plan(plan, &mut output) else {
            unreachable!("permit security fixture construction failed");
        };
        assert!(fingerprint.len() > request.transport_request().body().len());
        assert_eq!(
            fingerprint.subject().scope(),
            crate::operation::PermitScope::Mutation
        );
        assert!(!format_debug(&fingerprint).contains("review-ticket"));
    }
    assert_eq!(output, [0_u8; 4096]);
}

#[test]
fn exact_query_bytes_change_plan_identity() {
    let Some(endpoint) = endpoint() else {
        unreachable!("permit security fixture construction failed")
    };
    let Some(first_request) = prepared(
        "/resources?label=one",
        OperationImpact::Mutation,
        CostIntent::NoKnownCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(second_request) = prepared(
        "/resources?label=two",
        OperationImpact::Mutation,
        CostIntent::NoKnownCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(first_plan) = plan(
        first_request,
        endpoint,
        CONTEXT,
        200,
        ReplayPolicy::ReconcileThenRetry,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(second_plan) = plan(
        second_request,
        endpoint,
        CONTEXT,
        200,
        ReplayPolicy::ReconcileThenRetry,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let mut first_output = [0_u8; 4096];
    let mut second_output = [0_u8; 4096];
    let Ok(first) = build_canonical_plan(first_plan, &mut first_output) else {
        unreachable!("permit security fixture construction failed");
    };
    let Ok(second) = build_canonical_plan(second_plan, &mut second_output) else {
        unreachable!("permit security fixture construction failed");
    };
    assert!(!first.as_ref().matches(second.as_ref()));
}

#[test]
fn validation_rejects_read_only_no_op_cost_mismatch_and_small_output() {
    let Some(endpoint) = endpoint() else {
        unreachable!("permit security fixture construction failed")
    };
    let Some(read_only) = read_only("/resources") else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(context) = PermitContext::new(CONTEXT).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(validity) = PermitValidity::new(time(100), time(200)).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(attempts) = AttemptBudget::new(1).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let read_only_plan = PlanConfirmation::new(
        read_only,
        endpoint,
        PlanFingerprintScope::Absent,
        PlanFingerprintScope::Absent,
        context,
        validity,
        ReplayPolicy::SingleAttempt,
        attempts,
        PlanChange::ChangesState,
        None,
        None,
    );
    assert!(matches!(
        build_canonical_plan(read_only_plan, &mut [0_u8; 1024]),
        Err(PlanFingerprintBuildError::ReadOnlyOperation)
    ));

    let Some(mutation) = prepared(
        "/resources",
        OperationImpact::Mutation,
        CostIntent::NoKnownCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let no_op = PlanConfirmation::new(
        mutation,
        endpoint,
        PlanFingerprintScope::Absent,
        PlanFingerprintScope::Absent,
        context,
        validity,
        ReplayPolicy::SingleAttempt,
        attempts,
        PlanChange::NoOp,
        None,
        None,
    );
    assert!(matches!(
        build_canonical_plan(no_op, &mut [0_u8; 1024]),
        Err(PlanFingerprintBuildError::NoOp)
    ));

    let Some(cost_request) = prepared(
        "/resources",
        OperationImpact::Mutation,
        CostIntent::MayIncurCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let missing_cost = PlanConfirmation::new(
        cost_request,
        endpoint,
        PlanFingerprintScope::Absent,
        PlanFingerprintScope::Absent,
        context,
        validity,
        ReplayPolicy::SingleAttempt,
        attempts,
        PlanChange::ChangesState,
        None,
        None,
    );
    assert!(matches!(
        build_canonical_plan(missing_cost, &mut [0_u8; 1024]),
        Err(PlanFingerprintBuildError::MissingCost)
    ));

    let Some(valid_plan) = plan(
        mutation,
        endpoint,
        CONTEXT,
        200,
        ReplayPolicy::SingleAttempt,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let mut tiny = [0xa5_u8; 4];
    assert!(matches!(
        build_canonical_plan(valid_plan, &mut tiny),
        Err(PlanFingerprintBuildError::OutputTooSmall)
    ));
    assert_eq!(tiny, [0_u8; 4]);
}

#[test]
fn cost_and_digest_policy_are_exact_and_cleanup_owned() {
    let Some(endpoint) = endpoint() else {
        unreachable!("permit security fixture construction failed")
    };
    let Some(request) = prepared(
        "/resources",
        OperationImpact::Mutation,
        CostIntent::MayIncurCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(context) = PermitContext::new(CONTEXT).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(validity) = PermitValidity::new(time(100), time(200)).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(attempts) = AttemptBudget::new(1).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(currency) = CurrencyCode::new("EUR").ok() else {
        unreachable!("permit security fixture construction failed");
    };
    assert!(PlanCost::new(currency, 2, 101, 100).is_err());
    let Some(cost) = PlanCost::new(currency, 2, 100, 100).ok() else {
        unreachable!("permit security fixture construction failed");
    };
    let plan = PlanConfirmation::new(
        request,
        endpoint,
        PlanFingerprintScope::Value(ACCOUNT),
        PlanFingerprintScope::Value(TENANT),
        context,
        validity,
        ReplayPolicy::SingleAttempt,
        attempts,
        PlanChange::ChangesState,
        Some(cost),
        None,
    );
    let mut scratch = [0xa5_u8; 4096];
    let mut digest = [0xa5_u8; 64];
    {
        let Ok(fingerprint) = build_plan_digest(plan, &mut scratch, &mut digest, &TestHasher)
        else {
            unreachable!("permit security fixture construction failed");
        };
        assert_eq!(fingerprint.algorithm(), DigestAlgorithm::Sha256);
        assert_eq!(
            fingerprint.subject().scope(),
            crate::operation::PermitScope::Cost
        );
        assert_eq!(scratch, [0_u8; 4096]);
    }
    assert_eq!(digest, [0_u8; 64]);
}

#[cfg(feature = "std")]
#[test]
fn digest_failures_and_panics_clear_all_caller_storage() {
    let Some(endpoint) = endpoint() else {
        unreachable!("permit security fixture construction failed")
    };
    let Some(request) = prepared(
        "/resources",
        OperationImpact::Mutation,
        CostIntent::NoKnownCost,
    ) else {
        unreachable!("permit security fixture construction failed");
    };
    let Some(plan) = plan(request, endpoint, CONTEXT, 200, ReplayPolicy::SingleAttempt) else {
        unreachable!("permit security fixture construction failed");
    };
    let mut scratch = [0xa5_u8; 4096];
    let mut digest = [0xa5_u8; 64];
    assert!(matches!(
        build_plan_digest(plan, &mut scratch, &mut digest, &FailingHasher),
        Err(PlanFingerprintBuildError::Hasher(HashFailure))
    ));
    assert_eq!(scratch, [0_u8; 4096]);
    assert_eq!(digest, [0_u8; 64]);

    scratch.fill(0xa5);
    digest.fill(0xa5);
    let panic = test_std::panic::catch_unwind(test_std::panic::AssertUnwindSafe(|| {
        let _ = build_plan_digest(plan, &mut scratch, &mut digest, &PanickingHasher);
    }));
    assert!(panic.is_err());
    assert_eq!(scratch, [0_u8; 4096]);
    assert_eq!(digest, [0_u8; 64]);
}

fn plan<'a>(
    request: crate::operation::PreparedRequest<'static>,
    endpoint: crate::transport::EndpointIdentity<'static>,
    context: &'a [u8],
    expires: u64,
    replay: ReplayPolicy,
) -> Option<PlanConfirmation<'a, 'static>> {
    let attempts = if replay == ReplayPolicy::SingleAttempt {
        1
    } else {
        3
    };
    Some(PlanConfirmation::new(
        request,
        endpoint,
        PlanFingerprintScope::Value(ACCOUNT),
        PlanFingerprintScope::Value(TENANT),
        PermitContext::new(context).ok()?,
        PermitValidity::new(time(100), time(expires)).ok()?,
        replay,
        AttemptBudget::new(attempts).ok()?,
        PlanChange::ChangesState,
        None,
        (replay == ReplayPolicy::ReconcileThenRetry)
            .then(|| PermitIdempotencyKey::new(IDENTITY).ok())
            .flatten(),
    ))
}

const fn time(value: u64) -> PermitTimestamp {
    PermitTimestamp::from_seconds(value)
}

struct TestHasher;

impl FingerprintHasher for TestHasher {
    type Error = core::convert::Infallible;

    fn algorithm(&self) -> DigestAlgorithm {
        DigestAlgorithm::Sha256
    }

    fn digest(&self, input: &[u8], output: &mut [u8]) -> Result<usize, Self::Error> {
        let mut accumulator = 0_u8;
        for byte in input {
            accumulator ^= byte;
        }
        if let Some(target) = output.get_mut(..32) {
            target.fill(accumulator);
            Ok(32)
        } else {
            Ok(0)
        }
    }
}

#[cfg(feature = "std")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct HashFailure;

#[cfg(feature = "std")]
struct FailingHasher;

#[cfg(feature = "std")]
impl FingerprintHasher for FailingHasher {
    type Error = HashFailure;

    fn algorithm(&self) -> DigestAlgorithm {
        DigestAlgorithm::Sha256
    }

    fn digest(&self, _input: &[u8], output: &mut [u8]) -> Result<usize, Self::Error> {
        output.fill(0x5a);
        Err(HashFailure)
    }
}

#[cfg(feature = "std")]
struct PanickingHasher;

#[cfg(feature = "std")]
impl FingerprintHasher for PanickingHasher {
    type Error = core::convert::Infallible;

    fn algorithm(&self) -> DigestAlgorithm {
        DigestAlgorithm::Sha256
    }

    fn digest(&self, _input: &[u8], output: &mut [u8]) -> Result<usize, Self::Error> {
        output.fill(0x5a);
        test_std::panic::resume_unwind(test_std::boxed::Box::new("test hasher panic"))
    }
}

#[cfg(feature = "std")]
fn format_debug(value: &impl core::fmt::Debug) -> test_std::string::String {
    test_std::format!("{value:?}")
}