ic-query 0.52.1

Internet Computer query library for CloudEngine, NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
use crate::sns::report::source::validate_mainnet_sns_neuron;
use crate::sns::report::tests::{fixtures::*, *};

#[test]
fn neuron_evidence_is_rejected_at_live_detail_page_and_restored_boundaries() {
    let mut row = fixture_reward_row(1);
    row.disburse_maturity_in_progress = fixture_sns_neuron().detail.disburse_maturity_in_progress;
    let (mint, staking) = row.derived_policy_observations();
    row.maturity_mint_conversion_observed_disabled = mint;
    row.manual_maturity_staking_observed_disabled = staking;
    let valid = build_sns_reward_checkpoint_report_with_source(
        &reward_checkpoint_request("1"),
        &FixtureSnsRewardSource::new(vec![fixture_reward_page(vec![row], None)]),
    )
    .expect("valid variable evidence");
    let mutations: [fn(&mut SnsRewardCheckpointRow); 8] = [
        |row| row.permissions[0].principal = Some("invalid".to_string()),
        |row| row.permissions[0].principal = Some(ROOT_A.to_uppercase()),
        |row| row.permissions.push(row.permissions[0].clone()),
        |row| {
            let permission = row.permissions[0].permission_types[0].clone();
            row.permissions[0].permission_types.push(permission);
        },
        |row| row.permissions[0].permission_types[0].name = "incorrect".to_string(),
        |row| {
            row.disburse_maturity_in_progress[0]
                .account_to_disburse_to
                .as_mut()
                .unwrap()
                .owner = Some("invalid".to_string());
        },
        |row| {
            row.disburse_maturity_in_progress[0]
                .account_to_disburse_to
                .as_mut()
                .unwrap()
                .subaccount_hex = Some("AB".repeat(32));
        },
        |row| {
            row.disburse_maturity_in_progress[0]
                .account_to_disburse_to
                .as_mut()
                .unwrap()
                .subaccount_hex = Some("ab".to_string());
        },
    ];
    for mutate in mutations {
        let mut restored = valid.clone();
        mutate(&mut restored.rows[0]);
        assert!(validate_sns_reward_checkpoint_report(&restored).is_err());
        assert!(matches!(
            validate_mainnet_sns_reward_neuron_page(&fixture_reward_page(
                restored.rows.clone(),
                None,
            )),
            Err(SnsHostError::InvalidSourceData {
                capability: "SNS reward checkpoint",
                ..
            })
        ));
        let row = &restored.rows[0];
        let mut neuron = fixture_sns_neuron();
        neuron.detail.permissions = row.permissions.clone();
        neuron.detail.disburse_maturity_in_progress = row.disburse_maturity_in_progress.clone();
        let (mint, staking) = neuron.detail.derived_policy_observations();
        neuron.detail.maturity_mint_conversion_observed_disabled = mint;
        neuron.detail.manual_maturity_staking_observed_disabled = staking;
        assert!(matches!(
            validate_mainnet_sns_neuron(&neuron, &neuron.detail.neuron.neuron_id),
            Err(SnsHostError::InvalidSourceData {
                capability: "SNS neuron detail",
                ..
            })
        ));
    }
}

#[test]
fn checkpoint_retains_unassessable_permission_evidence_while_detail_requires_principals() {
    let mut row = fixture_reward_row(1);
    row.permissions[0].permission_types = [0, 11, -1]
        .map(SnsNeuronPermissionValue::from_code)
        .to_vec();
    row.permissions[0].principal = None;
    let (mint, staking) = row.derived_policy_observations();
    row.maturity_mint_conversion_observed_disabled = mint;
    row.manual_maturity_staking_observed_disabled = staking;
    let report = build_sns_reward_checkpoint_report_with_source(
        &reward_checkpoint_request("1"),
        &FixtureSnsRewardSource::new(vec![fixture_reward_page(vec![row], None)]),
    )
    .expect("checkpoint preserves incomplete and unknown evidence");
    validate_sns_reward_checkpoint_report(&report).expect("restored evidence remains valid");
    assert_eq!(
        report.maturity_conversion_policy_observed_status,
        SnsPolicyObservationStatus::Unassessable,
    );
    let mut neuron = fixture_sns_neuron();
    neuron.detail.permissions = report.rows[0].permissions.clone();
    assert!(matches!(
        validate_mainnet_sns_neuron(&neuron, &neuron.detail.neuron.neuron_id),
        Err(SnsHostError::InvalidSourceData { reason, .. })
            if reason.contains("principal is missing")
    ));
    neuron.detail.permissions[0].principal = Some(ROOT_A.to_string());
    let (mint, staking) = neuron.detail.derived_policy_observations();
    neuron.detail.maturity_mint_conversion_observed_disabled = mint;
    neuron.detail.manual_maturity_staking_observed_disabled = staking;
    validate_mainnet_sns_neuron(&neuron, &neuron.detail.neuron.neuron_id)
        .expect("detail preserves unknown permission codes with canonical labels");
}

#[test]
fn reward_checkpoint_collects_stable_brackets_and_exhausted_rows_in_order() {
    let source = FixtureSnsRewardSource::new(vec![fixture_reward_page(
        vec![fixture_reward_row(1), fixture_reward_row(2)],
        None,
    )]);
    let report =
        build_sns_reward_checkpoint_report_with_source(&reward_checkpoint_request("1"), &source)
            .expect("stable checkpoint");
    let text = sns_reward_checkpoint_report_text(&report);

    assert_eq!(
        source.calls(),
        [
            "version",
            "parameters",
            "event",
            "page",
            "event",
            "parameters",
            "version",
        ]
    );
    assert_eq!(report.page_count, 1);
    assert_eq!(report.row_count, 2);
    assert_eq!(report.unique_neuron_id_count, 2);
    assert_eq!(report.client_query_count, 9);
    assert_eq!(report.aggregate_maturity_e8s_equivalent, 300);
    assert_eq!(report.aggregate_staked_maturity_e8s_equivalent, 30);
    assert_eq!(report.aggregate_combined_maturity_e8s_equivalent, 330);
    assert_eq!(
        report.collection_status,
        SnsRewardCollectionStatus::ApiExhaustedObserved
    );
    assert!(!report.point_in_time_guaranteed);
    assert_eq!(
        report.maturity_conversion_policy_observed_status,
        SnsPolicyObservationStatus::ObservedSatisfied
    );
    assert!(text.contains("collection_status: api_exhausted_observed"));
    assert!(text.contains("row_count: 2"));
    assert!(!text.contains(&report.rows[0].neuron_id));
}

#[test]
fn reward_checkpoint_rejects_invalid_request_before_source_access() {
    let source = FixtureSnsRewardSource::new(vec![fixture_reward_page(Vec::new(), None)]);
    let mut request = reward_checkpoint_request("1");
    request.network = "local".to_string();
    assert!(matches!(
        build_sns_reward_checkpoint_report_with_source(&request, &source),
        Err(SnsHostError::UnsupportedNetwork { network }) if network == "local"
    ));
    assert_eq!(source.calls(), Vec::<&str>::new());

    let source = FixtureSnsRewardSource::new(vec![fixture_reward_page(Vec::new(), None)]);
    let request = reward_checkpoint_request("1").with_max_pages(Some(0));
    assert!(matches!(
        build_sns_reward_checkpoint_report_with_source(&request, &source),
        Err(SnsHostError::InvalidRewardCheckpointPageCap { max_pages: 0 })
    ));
    assert_eq!(source.calls(), Vec::<&str>::new());
}

#[test]
fn live_reward_source_rejects_non_mainnet_before_agent_construction() {
    let request = SnsSourceRequest::new(
        "local",
        "not a valid endpoint",
        "2026-08-03T00:00:00Z",
        "test",
    );
    let sns = fixture_sns_a();

    let errors = [
        LiveSnsSource
            .fetch_sns_reward_running_version(&request, &sns)
            .expect_err("running-version source must reject non-mainnet"),
        LiveSnsSource
            .fetch_sns_reward_parameters(&request, &sns)
            .expect_err("parameter source must reject non-mainnet"),
        LiveSnsSource
            .fetch_sns_reward_event(&request, &sns)
            .expect_err("reward-event source must reject non-mainnet"),
        LiveSnsSource
            .fetch_sns_reward_neuron_page(&request, &sns, SNS_REWARD_CHECKPOINT_PAGE_SIZE, None)
            .expect_err("neuron-page source must reject non-mainnet"),
    ];

    assert!(errors.into_iter().all(|error| matches!(
        error,
        SnsHostError::UnsupportedNetwork { network } if network == "local"
    )));
}

#[test]
fn reward_checkpoint_rejects_any_changed_complete_bracket() {
    for (component, expected) in [
        ("parameters", "nervous-system parameters"),
        ("event", "reward event"),
        ("version", "running SNS version"),
    ] {
        let source = FixtureSnsRewardSource::unstable(
            vec![fixture_reward_page(vec![fixture_reward_row(1)], None)],
            component,
        );
        assert!(matches!(
            build_sns_reward_checkpoint_report_with_source(
                &reward_checkpoint_request("1"),
                &source,
            ),
            Err(SnsHostError::UnstableRewardCheckpoint { component }) if component == expected
        ));
    }
}

#[test]
fn reward_checkpoint_rejects_overlap_and_noncanonical_cursor_evidence() {
    let mut full_rows = (1..=100).map(fixture_reward_row).collect::<Vec<_>>();
    let final_cursor = SnsNeuronId { id: vec![100; 32] };
    let mut state = SnsRewardCollectionState::new();
    assert!(!state.exhausted());
    state
        .ingest_page(fixture_reward_page(full_rows.clone(), Some(final_cursor)))
        .expect("first full page");
    assert!(!state.exhausted());
    assert!(matches!(
        state.ingest_page(fixture_reward_page(
            vec![fixture_reward_row(100)],
            None,
        )),
        Err(SnsHostError::InvalidSourceData {
            capability: "SNS reward checkpoint",
            reason,
        }) if reason.contains("does not increase")
    ));
    assert_eq!(state.page_count(), 1);
    assert_eq!(state.row_count(), 100);
    assert_eq!(
        state.next_cursor().expect("full-page cursor").id,
        vec![100; 32]
    );
    assert!(!state.exhausted());
    state
        .ingest_page(fixture_reward_page(Vec::new(), None))
        .expect("empty terminal page after a rejected page");
    assert_eq!(state.page_count(), 2);
    assert_eq!(state.row_count(), 100);
    assert!(state.next_cursor().is_none());
    assert!(state.exhausted());

    let short_with_cursor = fixture_reward_page(
        vec![fixture_reward_row(1)],
        Some(SnsNeuronId { id: vec![1; 32] }),
    );
    assert!(matches!(
        validate_mainnet_sns_reward_neuron_page(&short_with_cursor),
        Err(SnsHostError::InvalidSourceData { reason, .. })
            if reason.contains("must not advertise a cursor")
    ));

    full_rows[99].neuron_id = "ff".repeat(32);
    let wrong_cursor = fixture_reward_page(full_rows, Some(SnsNeuronId { id: vec![100; 32] }));
    assert!(matches!(
        validate_mainnet_sns_reward_neuron_page(&wrong_cursor),
        Err(SnsHostError::InvalidSourceData { reason, .. })
            if reason.contains("does not equal final neuron id")
    ));
}

#[test]
fn reward_checkpoint_rejects_diagnostic_cap_before_exhaustion() {
    let rows = (1..=100).map(fixture_reward_row).collect::<Vec<_>>();
    let source = FixtureSnsRewardSource::new(vec![
        fixture_reward_page(rows, Some(SnsNeuronId { id: vec![100; 32] })),
        fixture_reward_page(Vec::new(), None),
    ]);
    let request = reward_checkpoint_request("1").with_max_pages(Some(1));

    assert!(matches!(
        build_sns_reward_checkpoint_report_with_source(&request, &source),
        Err(SnsHostError::IncompleteRewardCheckpoint {
            pages_fetched: 1,
            rows_fetched: 100,
            reason,
        }) if reason.contains("diagnostic max_pages 1")
    ));
}

#[test]
fn reward_checkpoint_enforces_parameter_derived_collection_ceiling() {
    for ceiling in [None, Some(0), Some(200_001)] {
        let source = FixtureSnsRewardSource::new(vec![fixture_reward_page(Vec::new(), None)])
            .with_max_number_of_neurons(ceiling);
        assert!(matches!(
            build_sns_reward_checkpoint_report_with_source(
                &reward_checkpoint_request("1"),
                &source,
            ),
            Err(SnsHostError::InvalidRewardCheckpointCeiling { value, maximum: 200_000 })
                if value == ceiling
        ));
    }

    let source = FixtureSnsRewardSource::new(vec![fixture_reward_page(
        vec![fixture_reward_row(1), fixture_reward_row(2)],
        None,
    )])
    .with_max_number_of_neurons(Some(1));
    assert!(matches!(
        build_sns_reward_checkpoint_report_with_source(&reward_checkpoint_request("1"), &source),
        Err(SnsHostError::InvalidSourceData { reason, .. })
            if reason.contains("above mandatory ceiling 1")
    ));
}

#[test]
fn reward_checkpoint_collection_rejects_rows_after_exhaustion() {
    for rows in [Vec::new(), vec![fixture_reward_row(1)]] {
        let row_count = rows.len();
        let mut state = SnsRewardCollectionState::new();
        assert!(!state.exhausted());
        state
            .ingest_page(fixture_reward_page(rows, None))
            .expect("short or empty first page exhausts the API");
        assert!(state.exhausted());
        assert!(matches!(
            state.ingest_page(fixture_reward_page(Vec::new(), None)),
            Err(SnsHostError::InvalidSourceData { reason, .. })
                if reason.contains("after reported API exhaustion")
        ));
        assert_eq!(state.page_count(), 1);
        assert_eq!(state.row_count(), row_count);
        assert!(state.exhausted());
    }
}

#[test]
fn reward_row_policy_fails_closed_but_preserves_known_violations() {
    let mut row = fixture_reward_row(1);
    row.permissions[0].principal = None;
    row.permissions[0]
        .permission_types
        .push(SnsNeuronPermissionValue::from_code(11));
    let observations = row.derived_policy_observations();
    assert_eq!(
        observations,
        (
            SnsPolicyObservationStatus::Unassessable,
            SnsPolicyObservationStatus::Unassessable,
        )
    );

    row.permissions[0]
        .permission_types
        .push(SnsNeuronPermissionValue::from_code(7));
    row.maturity_mint_conversion_observed_disabled = SnsPolicyObservationStatus::Violated;
    row.manual_maturity_staking_observed_disabled = SnsPolicyObservationStatus::Unassessable;
    assert_eq!(
        row.derived_policy_observations().0,
        SnsPolicyObservationStatus::Violated
    );
}