use crate::report::CollectionSourceRequest;
use crate::sns::report::SnsNeuronDetail;
use crate::sns::report::tests::fixtures::*;
use crate::sns::report::tests::*;
struct NoCallSnsNeuronSource;
impl SnsDiscoverySource for NoCallSnsNeuronSource {
fn fetch_sns_inventory<'a>(
&'a self,
_request: &'a CollectionSourceRequest,
) -> crate::report::SourceFuture<'a, Result<MainnetSnsInventory, SnsHostError>> {
Box::pin(async move { unreachable!("invalid neuron id must fail before discovery") })
}
fn fetch_sns_metadata<'a>(
&'a self,
_request: &'a CollectionSourceRequest,
_targets: &'a [MainnetSnsCanisters],
) -> crate::report::SourceFuture<'a, Result<Vec<MainnetSnsMetadata>, SnsHostError>> {
Box::pin(async move { unreachable!("invalid neuron id must fail before metadata") })
}
}
impl SnsNeuronSource for NoCallSnsNeuronSource {
fn fetch_sns_neuron<'a>(
&'a self,
_request: &'a CollectionSourceRequest,
_sns: &'a MainnetSns,
_neuron_id: &'a str,
) -> crate::report::SourceFuture<'a, Result<SnsNeuronDetail, SnsHostError>> {
Box::pin(async move { unreachable!("invalid neuron id must fail before exact lookup") })
}
}
struct InvalidPermissionSnsNeuronSource;
delegate_sns_discovery!(InvalidPermissionSnsNeuronSource);
impl SnsNeuronSource for InvalidPermissionSnsNeuronSource {
fn fetch_sns_neuron<'a>(
&'a self,
_request: &'a CollectionSourceRequest,
_sns: &'a MainnetSns,
_neuron_id: &'a str,
) -> crate::report::SourceFuture<'a, Result<SnsNeuronDetail, SnsHostError>> {
Box::pin(async move {
let mut neuron = fixture_sns_neuron();
let code = neuron.permissions[0].permission_types[0];
neuron.permissions[0].permission_types.push(code);
Ok(neuron)
})
}
}
#[tokio::test(flavor = "current_thread")]
async fn exact_neuron_detail_preserves_native_permission_and_disbursement_evidence() {
let report =
build_sns_neuron_detail_report_with_source(&neuron_request("1"), &FixtureSnsNeuronSource)
.await
.expect("exact neuron detail");
assert_eq!(
report.schema_version,
SNS_NEURON_DETAIL_REPORT_SCHEMA_VERSION
);
assert_eq!(report.neuron_id, NEURON_A);
assert_eq!(report.context.instance.root_canister_id, ROOT_A);
assert_eq!(report.context.governance_canister_id, GOVERNANCE_A);
assert_eq!(report.data_source.as_str(), "live");
assert_eq!(report.detail.neuron.neuron_id, NEURON_A);
assert_eq!(report.detail.permissions[0].permission_types[4], 9);
assert_eq!(
sns_neuron_permission_name(report.detail.permissions[0].permission_types[4]),
"stake_maturity"
);
assert_eq!(
report.detail.derived_policy_observations().0,
SnsPolicyObservationStatus::Violated
);
assert_eq!(
report.detail.derived_policy_observations().1,
SnsPolicyObservationStatus::Violated
);
let destination = report.detail.disburse_maturity_in_progress[0]
.account_to_disburse_to
.as_ref()
.expect("destination account");
assert_eq!(destination.owner.as_deref(), Some(ROOT_A));
assert_eq!(
destination.subaccount_hex.as_deref(),
Some(&*"ab".repeat(32))
);
assert_eq!(
report.detail.followees[0].followee_neuron_ids[0],
"11".repeat(32)
);
assert_eq!(
report.detail.topic_followees.as_ref().expect("topic rows")[0].followees[0]
.alias
.as_deref(),
Some("governance lead")
);
}
#[tokio::test(flavor = "current_thread")]
async fn exact_neuron_detail_rejects_invalid_input_before_source_calls() {
for invalid in [
"AB".repeat(32),
"gg".repeat(32),
"+a".repeat(32),
format!("aƩ{}", "0".repeat(61)),
"00".repeat(31),
"00".repeat(33),
String::new(),
] {
let mut request = neuron_request("1");
request.neuron_id = invalid.clone();
assert!(matches!(
build_sns_neuron_detail_report_with_source(&request, &NoCallSnsNeuronSource).await,
Err(SnsHostError::InvalidNeuronIdText { neuron_id }) if neuron_id == invalid
));
}
}
#[tokio::test(flavor = "current_thread")]
async fn exact_neuron_detail_rejects_duplicate_custom_source_permission_codes() {
let error = build_sns_neuron_detail_report_with_source(
&neuron_request("1"),
&InvalidPermissionSnsNeuronSource,
)
.await
.expect_err("duplicate permission rejected");
assert!(matches!(
error,
SnsHostError::InvalidSourceData {
capability: "SNS neuron detail",
reason,
} if reason.contains("duplicate permission code")
));
}