ic-query 0.29.7

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

fn validation_context() -> CatalogValidationContext {
    CatalogValidationContext::new(
        MAINNET_NETWORK,
        MAINNET_REGISTRY_CANISTER_ID,
        1_780_531_300,
        DEFAULT_CATALOG_MAX_FUTURE_SKEW_SECONDS,
    )
}

#[test]
fn validated_route_retains_exact_catalog_authority() {
    let raw = fixture_catalog();
    let expected_digest = raw.catalog_digest.clone();
    let validated =
        ValidatedSubnetCatalog::try_from_raw(raw, &validation_context()).expect("valid catalog");

    let route = validated
        .resolve_canister_route(CANISTER_A)
        .expect("canister route");

    assert_eq!(route.canister.to_text(), CANISTER_A);
    assert_eq!(route.subnet.to_text(), SUBNET_A);
    assert_eq!(route.subnet_info.subnet_principal, SUBNET_A);
    assert_eq!(route.subnet_info.subnet_kind, SubnetKind::Application);
    assert_eq!(route.registry_version, 123_456);
    assert_eq!(
        route.provenance.assurance,
        CatalogAssurance::UncertifiedQuery
    );
    assert_eq!(
        crate::hex::hex_bytes(&route.catalog_digest),
        expected_digest
    );
    assert_eq!(route.matched_range.subnet_principal, SUBNET_A);
}

#[test]
fn assurance_minimums_are_ordered_from_query_to_certificate() {
    let cases = [
        (
            CatalogAssurance::UncertifiedQuery,
            CatalogAssurance::UncertifiedQuery,
            true,
        ),
        (
            CatalogAssurance::MultiEndpointAgreement,
            CatalogAssurance::UncertifiedQuery,
            true,
        ),
        (
            CatalogAssurance::MultiEndpointAgreement,
            CatalogAssurance::MultiEndpointAgreement,
            true,
        ),
        (
            CatalogAssurance::UncertifiedQuery,
            CatalogAssurance::MultiEndpointAgreement,
            false,
        ),
        (
            CatalogAssurance::MultiEndpointAgreement,
            CatalogAssurance::Certified,
            false,
        ),
        (
            CatalogAssurance::Certified,
            CatalogAssurance::MultiEndpointAgreement,
            true,
        ),
    ];

    for (actual, minimum, expected) in cases {
        assert_eq!(actual.satisfies(minimum), expected);
    }
}

#[test]
fn authority_validation_rejects_future_and_unclean_source_evidence() {
    let mut future = fixture_catalog();
    future.provenance.fetched_at = "2099-01-01T00:00:00Z".to_string();
    future.canonicalize_and_seal().expect("seal future fixture");
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(future, &validation_context()),
        Err(CatalogError::FutureTimestamp { .. })
    ));

    let mut unclean_endpoint = fixture_catalog();
    unclean_endpoint.provenance.source_endpoints =
        vec!["https://reader:secret@example.com/?query=1".to_string()];
    unclean_endpoint
        .canonicalize_and_seal()
        .expect("seal endpoint fixture");
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(unclean_endpoint, &validation_context()),
        Err(CatalogError::InvalidSourceEndpoint { .. })
    ));
}

#[test]
fn certified_and_incomplete_agreement_claims_fail_closed() {
    let mut raw = fixture_catalog();
    raw.provenance.assurance = CatalogAssurance::Certified;
    raw.provenance.certificate_time = Some(raw.provenance.fetched_at.clone());
    raw.provenance.root_key_digest = Some("00".repeat(32));
    raw.canonicalize_and_seal()
        .expect("seal claimed certificate");

    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(raw, &validation_context()),
        Err(CatalogError::UnsupportedAssurance { assurance }) if assurance == "certified"
    ));

    let mut agreement = fixture_catalog();
    agreement.provenance.assurance = CatalogAssurance::MultiEndpointAgreement;
    agreement.provenance.source_endpoints = vec![
        "https://ic0.app".to_string(),
        "https://icp-api.io".to_string(),
    ];
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(agreement, &validation_context()),
        Err(CatalogError::InvalidAgreementDigest { .. })
    ));
}

#[test]
fn raw_registry_kind_charging_and_policy_relations_fail_closed() {
    let mut kind = fixture_catalog();
    kind.subnets[0].registry_subnet_type = 2;
    assert!(matches!(
        kind.validate(),
        Err(CatalogError::SubnetKindMismatch { .. })
    ));

    let mut charging = fixture_catalog();
    charging.subnets[0].charges_apply_by_default = false;
    assert!(matches!(
        charging.validate(),
        Err(CatalogError::ChargingPolicyMismatch { .. })
    ));

    let mut annotation = fixture_catalog();
    annotation.subnets[0].subnet_label = "edited".to_string();
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(annotation, &validation_context()),
        Err(CatalogError::ClassificationMismatch { .. })
    ));
}

#[test]
fn authority_validation_recomputes_catalog_digest_and_mainnet_identity() {
    let mut digest = fixture_catalog();
    digest.catalog_digest = "ff".repeat(32);
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(digest, &validation_context()),
        Err(CatalogError::CatalogDigestMismatch { .. })
    ));

    let mut identity = fixture_catalog();
    identity.provenance.registry_canister_id = "aaaaa-aa".to_string();
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(identity, &validation_context()),
        Err(CatalogError::RegistryCanisterMismatch { .. })
    ));
}

#[test]
fn authority_validation_requires_canonical_time_and_current_policy_identity() {
    let mut timestamp = fixture_catalog();
    timestamp.provenance.fetched_at = "2026-6-4T0:0:0Z".to_string();
    timestamp
        .canonicalize_and_seal()
        .expect("seal noncanonical timestamp");
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(timestamp, &validation_context()),
        Err(CatalogError::InvalidTimestamp { .. })
    ));

    let mut classification = fixture_catalog();
    classification.provenance.classification_policy_digest = "ff".repeat(32);
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(classification, &validation_context()),
        Err(CatalogError::ClassificationPolicyDigestMismatch { .. })
    ));

    let mut resolver = fixture_catalog();
    resolver.provenance.resolver_schema_version += 1;
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(resolver, &validation_context()),
        Err(CatalogError::ResolverPolicyMismatch { .. })
    ));
}

#[test]
fn authority_validation_requires_call_counts_and_recomputes_agreement_digest() {
    let mut zero_calls = fixture_catalog();
    zero_calls.provenance.registry_query_call_count = 0;
    zero_calls
        .canonicalize_and_seal()
        .expect("seal zero-call fixture");
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(zero_calls, &validation_context()),
        Err(CatalogError::InvalidProvenance {
            field: "provenance.registry_query_call_count",
            ..
        })
    ));

    let mut agreement = fixture_catalog();
    agreement
        .promote_to_multi_endpoint_agreement(
            vec![
                "https://alpha.example".to_string(),
                "https://beta.example".to_string(),
            ],
            10,
        )
        .expect("promote fixture agreement");
    ValidatedSubnetCatalog::try_from_raw(agreement.clone(), &validation_context())
        .expect("valid agreement");

    agreement.provenance.agreement_digest = Some("ff".repeat(32));
    agreement
        .canonicalize_and_seal()
        .expect("reseal tampered agreement metadata");
    assert!(matches!(
        ValidatedSubnetCatalog::try_from_raw(agreement, &validation_context()),
        Err(CatalogError::AgreementDigestMismatch { .. })
    ));
}