ic-query 0.95.0

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use super::*;

#[derive(Clone, Debug, Eq, PartialEq)]
struct Row(&'static str);

#[test]
fn resolves_normalized_text_exactly_and_principals_by_prefix() {
    let text_rows = [Row("dc1")];
    assert_eq!(
        resolve_nns_inventory_row(
            &text_rows,
            |row| row.0,
            " DC1 ",
            NnsInventoryInputKind::Text,
            "id"
        ),
        Ok((0, "id".to_string()))
    );

    assert_eq!(
        resolve_nns_inventory_row(
            &text_rows,
            |row| row.0,
            " DC ",
            NnsInventoryInputKind::Text,
            "id"
        ),
        Ok((0, "id_prefix".to_string()))
    );

    let principal_rows = [Row("ryjl3-tyaaa-aaaaa-aaaba-cai")];
    assert_eq!(
        resolve_nns_inventory_row(
            &principal_rows,
            |row| row.0,
            "ryjl",
            NnsInventoryInputKind::Principal,
            "principal",
        ),
        Ok((0, "principal_prefix".to_string(),))
    );
}

#[test]
fn preserves_not_found_input_and_ambiguous_matches() {
    let rows = [Row("dc1"), Row("dc2")];
    assert_eq!(
        resolve_nns_inventory_row(&rows, |row| row.0, " ", NnsInventoryInputKind::Text, "id"),
        Err(NnsInventoryResolveError::NotFound {
            input: " ".to_string(),
        })
    );
    assert_eq!(
        resolve_nns_inventory_row(&rows, |row| row.0, "DC", NnsInventoryInputKind::Text, "id"),
        Err(NnsInventoryResolveError::Ambiguous {
            prefix: "dc".to_string(),
            matches: vec!["dc1".to_string(), "dc2".to_string()],
        })
    );
}

struct SharedInventorySource(std::sync::atomic::AtomicUsize);
impl crate::nns::NnsRegistryInventorySource for SharedInventorySource {
    fn fetch_registry_inventory<'a>(
        &'a self,
        request: &'a crate::report::CollectionSourceRequest,
    ) -> crate::report::SourceFuture<
        'a,
        Result<crate::nns::NnsRegistryInventorySnapshot, crate::nns::NnsInventoryError>,
    > {
        Box::pin(async move {
            self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
            let providers = [crate::nns::node_provider::NnsNodeProviderRow {
                node_provider_principal: "provider".to_string(),
                name: None,
                node_count: Some(2),
                reward_account_hex: None,
            }];
            let mut snapshot = crate::tests::inventory_for_provider_rows(request, &providers);
            snapshot
                .node_operators
                .push(crate::nns::node_operator::NnsNodeOperatorRow {
                    node_operator_principal: "fixture-operator".to_string(),
                    node_provider_principal: "provider".to_string(),
                    node_count: Some(2),
                    node_allowance: 3,
                    data_center_id: "fixture".to_string(),
                });
            snapshot
                .data_centers
                .push(crate::nns::data_center::NnsDataCenterRow {
                    data_center_id: "fixture".to_string(),
                    region: "region".to_string(),
                    owner: "owner".to_string(),
                    latitude: None,
                    longitude: None,
                    node_count: 2,
                    node_operator_count: 1,
                    node_provider_count: 1,
                });
            Ok(snapshot)
        })
    }
}
impl crate::nns::node_provider::NnsNodeProviderSource for SharedInventorySource {
    fn fetch_node_provider_metadata<'a>(
        &'a self,
        _request: &'a crate::report::CollectionSourceRequest,
    ) -> crate::report::SourceFuture<
        'a,
        Result<crate::nns::node_provider::NnsNodeProviderMetadata, crate::nns::NnsInventoryError>,
    > {
        Box::pin(async {
            Err(crate::nns::NnsInventoryError::InvalidSourceData {
                collection: crate::nns::NnsInventoryCollection::NodeProvider,
                reason: "Governance unavailable".to_string(),
            })
        })
    }
}

#[tokio::test(flavor = "current_thread")]
async fn registry_views_share_one_pin_and_survive_governance_failure() {
    use crate::nns::{NnsInventoryCacheRequest, NnsInventoryListRequest, node::NnsNodeListRequest};
    let root = crate::tests::temp_dir("ic-query-shared-registry-inventory");
    let cache = NnsInventoryCacheRequest::new(&root, "ic");
    let request = NnsInventoryListRequest::new(cache.clone(), "https://icp-api.io", 1_780_531_200);
    let source = SharedInventorySource(std::sync::atomic::AtomicUsize::new(0));
    let mut progress = Vec::new();
    let nodes = crate::nns::node::build_nns_node_list_report_with_source_and_progress(
        &NnsNodeListRequest::new(
            cache.clone(),
            &request.source_endpoint,
            request.now_unix_secs,
        ),
        &source,
        &mut |event| progress.push(event),
    )
    .await
    .unwrap();
    let operators =
        crate::nns::node_operator::build_nns_node_operator_list_report_with_source_and_progress(
            &request,
            &source,
            &mut |event| progress.push(event),
        )
        .await
        .unwrap();
    let centres =
        crate::nns::data_center::build_nns_data_center_list_report_with_source_and_progress(
            &request,
            &source,
            &mut |event| progress.push(event),
        )
        .await
        .unwrap();
    assert_eq!(source.0.load(std::sync::atomic::Ordering::SeqCst), 1);
    assert_eq!(progress.len(), 1);
    assert_eq!(nodes.nodes.len(), 2);
    assert_eq!(operators.node_operators.len(), 1);
    assert_eq!(centres.data_centers.len(), 1);
    assert_eq!(nodes.provenance, operators.provenance);
    assert_eq!(nodes.provenance, centres.provenance);
    let path = super::nns_registry_inventory_cache_path(&root, "ic");
    let before = std::fs::read(&path).unwrap();
    assert!(matches!(
        crate::nns::node_provider::build_nns_node_provider_list_report_with_source_and_progress(
            &request,
            &source,
            &mut |_| {}
        )
        .await,
        Err(crate::nns::NnsInventoryError::InvalidSourceData {
            collection: crate::nns::NnsInventoryCollection::NodeProvider,
            ..
        })
    ));
    assert_eq!(std::fs::read(&path).unwrap(), before);
    assert!(!crate::nns::node_provider::nns_node_provider_cache_path(&root, "ic").exists());
    crate::nns::data_center::build_nns_data_center_list_report_with_source_and_progress(
        &request,
        &source,
        &mut |_| {},
    )
    .await
    .unwrap();
    assert_eq!(source.0.load(std::sync::atomic::Ordering::SeqCst), 1);
}

#[tokio::test(flavor = "current_thread")]
async fn shared_snapshot_rejects_unknown_and_duplicate_cache_fields() {
    let source = SharedInventorySource(std::sync::atomic::AtomicUsize::new(0));
    let request = crate::report::CollectionSourceRequest::from_unix_secs(
        "ic",
        "https://icp-api.io",
        1_780_531_200,
        "fixture",
    );
    let snapshot = source.fetch_registry_inventory(&request).await.unwrap();
    let original = serde_json::to_string(&snapshot).unwrap();
    assert_eq!(
        serde_json::from_str::<NnsRegistryInventorySnapshot>(&original).unwrap(),
        snapshot
    );
    for extra in [
        "\"unexpected\":true,",
        "\"schema_version\":1,",
        "\"registry_version\":42,",
    ] {
        let bad = format!("{{{extra}{}", &original[1..]);
        assert!(serde_json::from_str::<NnsRegistryInventorySnapshot>(&bad).is_err());
    }
    let mut nested = serde_json::to_value(&snapshot).unwrap();
    nested["nodes"][0]["unexpected"] = true.into();
    assert!(serde_json::from_value::<NnsRegistryInventorySnapshot>(nested).is_err());
}