ic-query 0.90.0

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use super::*;
use std::io::Cursor;
#[cfg(unix)]
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};

#[test]
fn unmanaged_history_status_reads_only_the_header_prefix() {
    let transactions = format!("{}0", "0,".repeat(10_000));
    let cache = format!(
        r#"{{"schema_version":1,"collection_completed_at":"2026-08-03T00:00:00Z","completeness":{{"status":"api_exhausted"}},"transactions":[{transactions}]}}"#
    );
    let mut reader = BufReader::with_capacity(64, Cursor::new(cache.as_bytes()));

    let header = read_cache_header(
        &classify_cache_path(Path::new(
            "icrc/endpoint/ledger/account/transactions/full.json",
        )),
        &mut reader,
        u64::try_from(cache.len()).unwrap(),
        1_024,
    )
    .expect("history header");

    assert_eq!(header.schema_version, 1);
    assert_eq!(
        header.fields.collection_completed_at.as_deref(),
        Some("2026-08-03T00:00:00Z")
    );
    assert!(reader.get_ref().position() <= 1_024);
    assert!(cache.len() > 10_000);
}

#[test]
fn registered_cache_rejects_ambiguous_schema_version_fields() {
    let error = read_cache_header(
        &classify_cache_path(Path::new("nns/ic/subnet-catalog/catalog.json")),
        Cursor::new(br#"{"schema_version":1,"catalog_schema_version":1,"network":"ic"}"#),
        0,
        1_024,
    )
    .err()
    .expect("multiple current schema fields are ambiguous");

    assert_eq!(error.io_error_kind(), Some(std::io::ErrorKind::InvalidData));
}

#[test]
fn registered_cache_inspection_bounds_metadata_and_stream_growth() {
    let path = Path::new("nns/ic/subnet-catalog/catalog.json");
    let mut reader = Cursor::new(br#"{"schema_version":1}             "#);
    let error = read_cache_header(&classify_cache_path(path), &mut reader, 32, 20)
        .err()
        .expect("oversized metadata");
    assert_eq!(error.io_error_kind(), Some(std::io::ErrorKind::InvalidData));
    assert_eq!(reader.position(), 0);

    let error = read_cache_header(&classify_cache_path(path), &mut reader, 19, 20)
        .err()
        .expect("growth after metadata admission");
    assert_eq!(error.io_error_kind(), Some(std::io::ErrorKind::InvalidData));
    assert_eq!(reader.position(), 21);
}

#[test]
fn unmanaged_cache_inspection_bounds_values_before_payload() {
    let cache = format!(
        r#"{{"schema_version":1,"extra":"{}","transactions":[]}}"#,
        "x".repeat(1_024)
    );
    let mut reader = Cursor::new(cache.as_bytes());
    let error = read_cache_header(
        &classify_cache_path(Path::new("icrc/ic/account/transactions/full.json")),
        &mut reader,
        u64::try_from(cache.len()).unwrap(),
        64,
    )
    .err()
    .expect("header inspection budget is exhausted");
    assert_eq!(error.io_error_kind(), Some(std::io::ErrorKind::InvalidData));
    assert_eq!(reader.position(), 64);

    let cache = br#"{"schema_version":1,"network":"ic","data":{"rows":[unread payload"#;
    let mut reader = Cursor::new(cache);
    let header = read_cache_header(
        &classify_cache_path(Path::new("sns/ic/root/neurons/full.json")),
        &mut reader,
        cache.len() as u64,
        64,
    )
    .expect("nested snapshot payload is not part of generic header inspection");
    assert_eq!(header.schema_version, 1);
    assert!(reader.position() < cache.len() as u64);
}

#[test]
fn registered_cache_inspection_checks_the_payload_syntax() {
    let cache = br#"{"schema_version":1,"subnets":[invalid]}"#;
    assert!(
        read_cache_header(
            &classify_cache_path(Path::new("nns/ic/subnet-catalog/catalog.json")),
            Cursor::new(cache),
            u64::try_from(cache.len()).unwrap(),
            1_024,
        )
        .is_err()
    );
}

#[test]
fn path_components_do_not_expose_variable_cache_identity() {
    for (path, expected) in [
        ("nns/ic/node/nodes.json", "nns/nodes"),
        (
            "nns/ic/governance/proposals/full.json",
            "nns/governance/proposals",
        ),
        ("sns/ic/root-principal/neurons/full.json", "sns/neurons"),
        (
            "sns/ic/catalog/discovery/full.json",
            "sns/catalog/discovery",
        ),
        (
            "icrc/ic/account-hash/transactions/full.json",
            "icrc/transactions",
        ),
        (
            "ic/ic/nodes/operational-status/full.json",
            "ic/nodes/operational-status",
        ),
    ] {
        assert_eq!(component_from_path(Path::new(path)), expected);
    }
}

#[cfg(unix)]
#[test]
fn opaque_path_components_keep_unregistered_caches_unmanaged() {
    let canonical = Path::new("nns/ic/subnet-catalog/catalog.json");
    let components = canonical.components().collect::<Vec<_>>();
    for position in 0..components.len() {
        let mut path = std::path::PathBuf::new();
        for (index, component) in components.iter().enumerate() {
            if index == position {
                path.push(OsStr::from_bytes(&[0xff]));
            }
            path.push(component.as_os_str());
        }
        let info = classify_cache_path(&path);
        assert_eq!(info.relative, path);
        assert_eq!(info.component, "unknown");
        assert_eq!(info.recovery_policy, CacheRecoveryPolicy::Unknown);
        assert_eq!(info.stale_after_seconds, None);
        assert!(matches!(info.inspection, CacheInspection::HeaderPrefix));
    }
}

#[test]
fn recovery_policy_follows_only_current_canonical_cache_paths() {
    for (path, expected) in [
        (
            "nns/ic/subnet-catalog/catalog.json",
            CacheRecoveryPolicy::Automatic,
        ),
        ("nns/ic/node/nodes.json", CacheRecoveryPolicy::Automatic),
        (
            "nns/ic/node-provider/providers.json",
            CacheRecoveryPolicy::Automatic,
        ),
        (
            "nns/ic/node-operator/operators.json",
            CacheRecoveryPolicy::Automatic,
        ),
        (
            "nns/ic/data-center/data-centers.json",
            CacheRecoveryPolicy::Automatic,
        ),
        (
            "sns/ic/catalog/discovery/full.json",
            CacheRecoveryPolicy::Automatic,
        ),
        (
            "ic/ic/nodes/operational-status/full.json",
            CacheRecoveryPolicy::Automatic,
        ),
        (
            "nns/ic/subnet-topology/report.json",
            CacheRecoveryPolicy::Explicit,
        ),
        (
            "nns/ic/governance/proposals/full.json",
            CacheRecoveryPolicy::Explicit,
        ),
        (
            "nns/ic/governance/neurons/full.json",
            CacheRecoveryPolicy::Explicit,
        ),
        (
            "sns/ic/root/neurons/full.json",
            CacheRecoveryPolicy::Explicit,
        ),
        (
            "icrc/ic/account/transactions/full.json",
            CacheRecoveryPolicy::Explicit,
        ),
        (
            "sns/ic/root/proposals/full.json",
            CacheRecoveryPolicy::MissingOnly,
        ),
        ("nns/local/node/nodes.json", CacheRecoveryPolicy::Unknown),
        ("unmanaged/ic/full.json", CacheRecoveryPolicy::Unknown),
    ] {
        assert_eq!(
            classify_cache_path(Path::new(path)).recovery_policy,
            expected,
            "{path}"
        );
    }
}

#[test]
fn age_policy_follows_only_current_canonical_mainnet_paths() {
    for (path, expected) in [
        (
            "nns/ic/subnet-catalog/catalog.json",
            Some(DEFAULT_STALE_AFTER_SECONDS),
        ),
        (
            "nns/ic/subnet-topology/report.json",
            Some(DEFAULT_NNS_SUBNET_TOPOLOGY_STALE_AFTER_SECONDS),
        ),
        (
            "sns/ic/catalog/discovery/full.json",
            Some(DEFAULT_SNS_CATALOG_STALE_AFTER_SECONDS),
        ),
        (
            "ic/ic/nodes/operational-status/full.json",
            Some(DEFAULT_IC_NODE_STATUS_STALE_AFTER_SECONDS),
        ),
        ("nns/local/subnet-catalog/catalog.json", None),
        ("unmanaged/ic/full.json", None),
    ] {
        assert_eq!(
            classify_cache_path(Path::new(path)).stale_after_seconds,
            expected,
            "{path}"
        );
    }
}