diff --git a/Cargo.lock b/Cargo.lock index 8603a378e14d..1d769593fdb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10944,6 +10944,7 @@ name = "ic-migration-canister" version = "0.9.0" dependencies = [ "candid", + "candid_parser", "canister-test", "futures", "ic-base-types", diff --git a/rs/migration_canister/BUILD.bazel b/rs/migration_canister/BUILD.bazel index 05a30dd77b39..17b479495846 100644 --- a/rs/migration_canister/BUILD.bazel +++ b/rs/migration_canister/BUILD.bazel @@ -50,12 +50,14 @@ rust_canister( rust_test( name = "mc_tests", srcs = glob(["src/**/*.rs"]), + compile_data = [":migration_canister.did"], proc_macro_deps = [ "@crate_index//:strum_macros", ], deps = [ # Keep sorted. "@crate_index//:candid", + "@crate_index//:candid_parser", "@crate_index//:futures", "@crate_index//:ic-cdk", "@crate_index//:ic-cdk-timers", diff --git a/rs/migration_canister/Cargo.toml b/rs/migration_canister/Cargo.toml index dccccdc85ae3..6fbb439b1f9d 100644 --- a/rs/migration_canister/Cargo.toml +++ b/rs/migration_canister/Cargo.toml @@ -20,6 +20,7 @@ strum = { workspace = true } strum_macros = { workspace = true } [dev-dependencies] +candid_parser = { workspace = true } pocket-ic = { path = "../../packages/pocket-ic" } tempfile = { workspace = true } tokio = { workspace = true } diff --git a/rs/migration_canister/migration_canister.did b/rs/migration_canister/migration_canister.did index f12904520e06..59b09d5c5481 100644 --- a/rs/migration_canister/migration_canister.did +++ b/rs/migration_canister/migration_canister.did @@ -1,5 +1,40 @@ -type MigrationCanisterInitPayload = record {}; +type ListEventsArgs = record { page_size : nat64; page_index : nat64 }; +type MigrationStatus = variant { + Failed : record { time : nat64; reason : text }; + Succeeded : record { time : nat64 }; + InProgress : record { status : text }; +}; + +type MigrateCanisterArgs = record { source : principal; target : principal }; -service : (opt MigrationCanisterInitPayload) -> { +type MigrationCanisterResult = variant { Ok; Err : MigrationCanisterError }; +type MigrationCanisterError = variant { CallerNotAuthorized }; +type ValidationResult = variant { Ok; Err : ValidationError }; +type ValidationError = variant { + CanisterNotFound : record { canister : principal }; + CallFailed : record { reason : text }; + NotController : record { canister : principal }; + CallerNotController : record { canister : principal }; + TargetHasSnapshots; + MigrationInProgress : record { canister : principal }; + SourceNotStopped; + RateLimited; + MigrationsDisabled; + SourceNotReady; + SourceInsufficientCycles; + SameSubnet; + TargetNotStopped; }; + +type MigrationCanisterInitArgs = record { allowlist : opt vec principal }; + +service : (MigrationCanisterInitArgs) -> { + disable_api : () -> (MigrationCanisterResult); + enable_api : () -> (MigrationCanisterResult); + list_events : (ListEventsArgs) -> (vec MigrationStatus) query; + migrate_canister : (MigrateCanisterArgs) -> (ValidationResult); + // The same `(source, target)` pair might be processed again after a failure + // and thus we return a vector. + migration_status : (MigrateCanisterArgs) -> (vec MigrationStatus) query; +} diff --git a/rs/migration_canister/src/migration_canister.rs b/rs/migration_canister/src/migration_canister.rs index 7f004cc31dbe..6cad0bd83905 100644 --- a/rs/migration_canister/src/migration_canister.rs +++ b/rs/migration_canister/src/migration_canister.rs @@ -23,7 +23,7 @@ use crate::{ }; #[derive(CandidType, Deserialize)] -struct MigrationCanisterInitArgs { +pub(crate) struct MigrationCanisterInitArgs { allowlist: Option>, } @@ -124,7 +124,7 @@ fn migration_status(args: MigrateCanisterArgs) -> Vec { } #[derive(Clone, CandidType, Deserialize)] -struct ListEventsArgs { +pub(crate) struct ListEventsArgs { page_index: u64, page_size: u64, } diff --git a/rs/migration_canister/src/privileged.rs b/rs/migration_canister/src/privileged.rs index 83a7ec105fac..ca400e6cc56d 100644 --- a/rs/migration_canister/src/privileged.rs +++ b/rs/migration_canister/src/privileged.rs @@ -20,7 +20,7 @@ fn check_caller() -> Result<(), MigrationCanisterError> { } #[derive(Clone, Debug, CandidType, Deserialize)] -enum MigrationCanisterError { +pub(crate) enum MigrationCanisterError { CallerNotAuthorized, } diff --git a/rs/migration_canister/src/tests.rs b/rs/migration_canister/src/tests.rs index 309a8713979f..64e61a477a1b 100644 --- a/rs/migration_canister/src/tests.rs +++ b/rs/migration_canister/src/tests.rs @@ -1,5 +1,9 @@ //! Unit tests +use crate::migration_canister::{ListEventsArgs, MigrationCanisterInitArgs}; +use crate::privileged::MigrationCanisterError; +use crate::{MigrateCanisterArgs, MigrationStatus, ValidationError}; use candid::Principal; +use candid_parser::utils::{CandidSource, service_equal}; use crate::{ Request, RequestState, @@ -26,3 +30,17 @@ fn test() { insert_request(RequestState::Accepted { request }); assert!(find_request(source, target).len() == 1); } + +#[test] +fn test_implemented_interface_matches_declared_interface_exactly() { + let declared_interface = CandidSource::Text(include_str!("../migration_canister.did")); + + // The line below generates did types and service definition. + // The service definition is then obtained with `__export_service()`. + candid::export_service!(); + let implemented_interface_str = __export_service(); + let implemented_interface = CandidSource::Text(&implemented_interface_str); + + let result = service_equal(declared_interface, implemented_interface); + assert!(result.is_ok(), "{:?}\n\n", result.unwrap_err()); +}