ic-query 0.94.0

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use super::*;
use crate::nns::governance::NnsGovernanceSourceProvenance;
use crate::nns::governance::{NnsGovernanceError, validate_governance_time_interval};

#[test]
fn restored_collection_requires_complete_pages_until_short_terminal_page() {
    let request = NnsGovernanceRequest::replicated_inter_canister_call_from_unix_secs("ic", 100);
    let initial = NnsNeuronCollectionState::new(&request, 2, 3).unwrap();
    for (pages, rows, cursor, has_source, valid) in [
        (0, 0, None, false, true),
        (0, 0, Some(10), false, false),
        (1, 2, Some(10), true, true),
        (1, 1, Some(10), true, false),
        (1, 1, None, true, true),
        (1, 2, None, true, false),
        (1, 1, None, false, false),
        (3, 6, Some(10), true, true),
        (3, 5, Some(10), true, false),
    ] {
        let mut state = initial.clone();
        state.collection.pages_fetched = pages;
        state.collection.source =
            has_source.then(
                || NnsGovernanceSourceProvenance::ReplicatedInterCanisterCall {
                    collector_canister_id: "aaaaa-aa".to_string(),
                },
            );
        state.neurons_fetched = rows;
        state.next_start_neuron_id = cursor;
        assert_eq!(
            validate_collection_state(&state).is_ok(),
            valid,
            "pages={pages}, rows={rows}, cursor={cursor:?}, source={has_source}"
        );
    }
}

struct UnexpectedSource;
impl NnsNeuronSource for UnexpectedSource {
    fn fetch_neuron_page<'a>(
        &'a self,
        _: &'a NnsGovernanceRequest,
        _: Option<u64>,
        _: u32,
    ) -> crate::nns::neuron::NnsNeuronSourceFuture<'a, crate::nns::neuron::NnsNeuronPage> {
        panic!("unexpected source call")
    }
    fn fetch_neuron<'a>(
        &'a self,
        _: &'a NnsGovernanceRequest,
        _: u64,
    ) -> crate::nns::neuron::NnsNeuronSourceFuture<'a, crate::nns::neuron::NnsNeuronRow> {
        panic!("unexpected source call")
    }
}

#[tokio::test(flavor = "current_thread")]
async fn collection_rejects_backward_continuations_and_restored_intervals() {
    let start = NnsGovernanceRequest::replicated_inter_canister_call_from_unix_secs("ic", 100);
    let state = NnsNeuronCollectionState::new(&start, 2, 3).unwrap();
    let before = NnsGovernanceRequest::replicated_inter_canister_call_from_unix_secs("ic", 99);
    let mut future = std::pin::pin!(advance_nns_neuron_collection_with_source(
        &before,
        &state,
        &UnexpectedSource
    ));
    let mut context = std::task::Context::from_waker(std::task::Waker::noop());
    let result = std::future::Future::poll(future.as_mut(), &mut context);
    assert!(matches!(
        result,
        std::task::Poll::Ready(Err(NnsNeuronError::Governance(
            NnsGovernanceError::InvalidTimestampOrder { .. }
        )))
    ));
    let mut value = serde_json::to_value(&state).unwrap();
    value["updated_at"] = serde_json::json!(before.fetched_at);
    let restored = serde_json::from_value(value).unwrap();
    assert!(matches!(
        validate_collection_state(&restored),
        Err(NnsNeuronError::Governance(
            NnsGovernanceError::InvalidTimestampOrder { .. }
        ))
    ));
    assert!(
        validate_governance_time_interval(
            "updated_at",
            &state.collection.updated_at,
            "fetched_at",
            &start.fetched_at
        )
        .is_ok()
    );
}

#[test]
fn collection_validates_request_and_restored_timestamps() {
    let mut request =
        NnsGovernanceRequest::replicated_inter_canister_call_from_unix_secs("ic", 1_700_000_000);
    let valid_state = NnsNeuronCollectionState::new(&request, 2, 3).expect("valid state");
    for (field, value) in [
        ("governance_canister_id", serde_json::json!("aaaaa-aa")),
        ("max_pages", serde_json::json!(0)),
        ("pages_fetched", serde_json::json!(4)),
    ] {
        let mut restored = serde_json::to_value(&valid_state).unwrap();
        restored[field] = value;
        let restored = serde_json::from_value(restored).unwrap();
        assert!(
            matches!(
                validate_collection_state(&restored),
                Err(NnsNeuronError::InvalidCollectionState { .. })
            ),
            "invalid restored {field}"
        );
    }
    for timestamp in ["not a UTC timestamp", "+2023-11-14T22:13:20Z"] {
        request.fetched_at = timestamp.to_string();
        assert!(matches!(
            NnsNeuronCollectionState::new(&request, 2, 3),
            Err(NnsNeuronError::Governance(
                NnsGovernanceError::InvalidTimestamp {
                    field: "fetched_at",
                    ..
                }
            ))
        ));
        for field in ["started_at", "updated_at"] {
            let mut value = serde_json::to_value(&valid_state).expect("serialize state");
            value[field] = serde_json::json!(timestamp);
            let state = serde_json::from_value(value).expect("restore caller state");
            assert!(matches!(
                validate_collection_state(&state),
                Err(NnsNeuronError::Governance(
                    NnsGovernanceError::InvalidTimestamp { value, .. }
                )) if value == timestamp
            ));
        }
    }
}