canic-macros 0.64.0

Canic — a canister orchestration and management toolkit for the Internet Computer
Documentation
use super::*;
use crate::endpoint::parse::{
    AccessExprAst, AccessPredicateAst, BuiltinPredicate, CanisterRoleArg, ParsedArgs,
};

fn parsed_authenticated() -> ParsedArgs {
    ParsedArgs {
        forwarded: Vec::new(),
        export_name: None,
        payload_max_bytes: None,
        requires: vec![AccessExprAst::Pred(AccessPredicateAst::Builtin(
            BuiltinPredicate::Authenticated {
                required_scope: None,
            },
        ))],
        requires_async: true,
        requires_fallible: true,
        internal: false,
        query_mode: QueryMode::Plain,
    }
}

fn parsed_registered_to_subnet(internal: bool) -> ParsedArgs {
    ParsedArgs {
        forwarded: Vec::new(),
        export_name: None,
        payload_max_bytes: None,
        requires: vec![AccessExprAst::Any(vec![
            AccessExprAst::Pred(AccessPredicateAst::Builtin(
                BuiltinPredicate::CallerIsController,
            )),
            AccessExprAst::Not(Box::new(AccessExprAst::Pred(AccessPredicateAst::Builtin(
                BuiltinPredicate::CallerIsRegisteredToSubnet,
            )))),
        ])],
        requires_async: true,
        requires_fallible: true,
        internal,
        query_mode: QueryMode::Plain,
    }
}

fn parsed_attested_role(internal: bool) -> ParsedArgs {
    ParsedArgs {
        forwarded: Vec::new(),
        export_name: None,
        payload_max_bytes: None,
        requires: vec![AccessExprAst::Pred(AccessPredicateAst::Builtin(
            BuiltinPredicate::CallerHasRole {
                role: CanisterRoleArg::Literal("project_hub".to_string()),
            },
        ))],
        requires_async: true,
        requires_fallible: true,
        internal,
        query_mode: QueryMode::Plain,
    }
}

#[test]
fn authenticated_requires_first_argument() {
    let sig: Signature = syn::parse_quote!(async fn hello() -> Result<(), ::canic::Error>);
    let err = validate(EndpointKind::Update, parsed_authenticated(), &sig, true).unwrap_err();
    assert!(
        err.to_string()
            .contains("authenticated(...) requires a first argument")
    );
}

#[test]
fn authenticated_accepts_delegated_token_first_arg() {
    let sig: Signature = syn::parse_quote!(
        async fn hello(token: ::canic::dto::auth::DelegatedToken) -> Result<(), ::canic::Error>
    );
    validate(EndpointKind::Update, parsed_authenticated(), &sig, true)
        .expect("authenticated arg ok");
}

#[test]
fn authenticated_rejects_wrong_first_arg_type() {
    let sig: Signature = syn::parse_quote!(
        async fn hello(user: ::canic::cdk::candid::Principal) -> Result<(), ::canic::Error>
    );
    let err = validate(EndpointKind::Update, parsed_authenticated(), &sig, true).unwrap_err();
    assert!(
        err.to_string()
            .contains("authenticated(...) requires a first argument")
    );
}

#[test]
fn registered_to_subnet_requires_internal_endpoint() {
    let sig: Signature = syn::parse_quote!(async fn hello() -> Result<(), ::canic::Error>);
    let err = validate(
        EndpointKind::Update,
        parsed_registered_to_subnet(false),
        &sig,
        true,
    )
    .unwrap_err();
    assert!(
        err.to_string()
            .contains("caller topology predicates are internal-only")
    );
}

#[test]
fn registered_to_subnet_is_allowed_for_internal_endpoint() {
    let sig: Signature = syn::parse_quote!(async fn hello() -> Result<(), ::canic::Error>);
    validate(
        EndpointKind::Update,
        parsed_registered_to_subnet(true),
        &sig,
        true,
    )
    .expect("internal predicate ok");
}

#[test]
fn attested_role_requires_internal_update_endpoint() {
    let sig: Signature = syn::parse_quote!(async fn hello() -> Result<(), ::canic::Error>);
    let err = validate(
        EndpointKind::Update,
        parsed_attested_role(false),
        &sig,
        true,
    )
    .expect_err("attested role must be internal");
    assert!(
        err.to_string()
            .contains("caller topology predicates are internal-only")
    );

    let err = validate(EndpointKind::Query, parsed_attested_role(true), &sig, true)
        .expect_err("attested query must fail");
    assert!(
        err.to_string()
            .contains("protected internal endpoints are update-only")
    );
}

#[test]
fn attested_role_is_allowed_for_internal_update_endpoint() {
    let sig: Signature = syn::parse_quote!(async fn hello() -> Result<(), ::canic::Error>);
    validate(EndpointKind::Update, parsed_attested_role(true), &sig, true)
        .expect("internal update attested role predicate ok");
}

#[test]
fn payload_limit_is_update_only() {
    let sig: Signature = syn::parse_quote!(fn hello() -> bool);
    let parsed = ParsedArgs {
        forwarded: Vec::new(),
        export_name: None,
        payload_max_bytes: Some(quote::quote!(1024)),
        requires: Vec::new(),
        requires_async: false,
        requires_fallible: false,
        internal: false,
        query_mode: QueryMode::Plain,
    };

    let err = validate(EndpointKind::Query, parsed, &sig, false).unwrap_err();
    assert!(
        err.to_string()
            .contains("payload(...) is supported only on canic_update")
    );
}

#[test]
fn composite_query_marker_is_query_only() {
    let sig: Signature = syn::parse_quote!(fn hello() -> bool);
    let parsed = ParsedArgs {
        forwarded: vec![quote::quote!(composite = true)],
        export_name: None,
        payload_max_bytes: None,
        requires: Vec::new(),
        requires_async: false,
        requires_fallible: false,
        internal: false,
        query_mode: QueryMode::Composite,
    };

    let err = validate(EndpointKind::Update, parsed, &sig, false).unwrap_err();
    assert!(
        err.to_string()
            .contains("composite is supported only on canic_query")
    );
}