use super::build_network::resolve_install_build_context;
use super::build_snapshot::InstallBuildTarget;
use super::commands::{
add_create_root_target, add_icp_environment_target, icp_canister_command, root_init_args,
};
use super::config_selection::{
config_selection_error, discover_canic_config_choices, discover_project_canic_config_choices,
resolve_install_config_path, select_discovered_app_config_path,
};
use super::current_execution::{
current_install_execution_context, current_install_executor_missing_capabilities,
};
use super::deployment_truth_gate::{
enforce_install_deployment_truth_gate, install_deployment_truth_gate_lines,
install_deployment_truth_gate_receipt,
};
use super::execution_preflight::write_current_install_execution_preflight_receipt;
use super::operations::{
BuildInstallTargetsOperation, EmitRootManifestOperation, InstallPhaseLabel,
InstallPhaseOperation, InstallRootWasmOperation, ResolveRootCanisterOperation,
};
use super::output::render_install_timing_summary;
use super::phase_receipts::{
CompletedInstallPhase, InstallReceiptScope, install_deployment_truth_phase_receipt,
write_completed_install_phase_receipt,
};
use super::plan_artifacts::{
PlanArtifactError, PreparedPlanArtifacts, prepare_plan_artifacts_with_phase,
};
use super::receipt_io::{
install_deployment_truth_receipt_path, write_install_deployment_truth_receipt,
};
use super::root_cycles::add_local_root_create_cycles_arg;
use super::timing::InstallTimingSummary;
use super::truth_check::current_install_deployment_truth_check_at;
use super::{
FleetActivationContinuationRequired, InstallRootBlockKind, InstallRootBlockedError,
InstallRootError, InstallRootOptions, InstallRootPhase, check_install_deployment_truth,
check_install_execution_preflight, latest_deployment_truth_receipt_path_from_root,
};
use crate::canister_build::{
CanisterArtifactBuildSpec, CanisterBuildProfile, WorkspaceBuildContext,
};
use crate::deployment_truth::{
CanisterControlClassV1, DeploymentCheckV1, DeploymentExecutionContextV1,
DeploymentExecutionPreflightStatusV1, DeploymentExecutionStatusV1, DeploymentExecutorBackendV1,
DeploymentExecutorCapabilityV1, DeploymentReceiptV1, ObservationStatusV1, ObservedCanisterV1,
SafetyFindingV1, SafetySeverityV1, SafetyStatusV1, artifact_gate_phase_receipt,
artifact_gate_role_phase_receipts, compare_plan_to_inventory, safety_report_from_diff,
};
use crate::icp::LocalReplicaTarget;
use crate::release_set::RootReleaseSetBuildSnapshot;
use crate::test_support::{temp_dir, write_local_network_authority};
use canic_core::{
dto::fleet_activation::FleetActivationIdentity,
ids::{
AppId, BuildNetwork, CanonicalNetworkId, FleetBinding, FleetId, FleetKey, ReleaseBuildId,
ReleaseBuildNonce,
},
};
use std::{
fs,
path::{Path, PathBuf},
time::Duration,
};
mod commands;
mod config_selection;
mod install_truth;
#[test]
fn public_install_error_preserves_phase_and_typed_source() {
let error = InstallRootError::new(
InstallRootPhase::Activation,
std::io::Error::other("activation failed"),
);
assert_eq!(error.phase(), InstallRootPhase::Activation);
assert!(
std::error::Error::source(&error)
.and_then(|source| source.downcast_ref::<std::io::Error>())
.is_some()
);
}
#[test]
fn activation_continuation_exposes_typed_recovery_identity() {
let continuation = FleetActivationContinuationRequired {
root_canister_id: "uxrrr-q7777-77774-qaaaq-cai".to_string(),
journal_path: PathBuf::from("/tmp/fleet-activation.json"),
sequence: 3,
};
assert_eq!(
continuation.root_canister_id(),
"uxrrr-q7777-77774-qaaaq-cai"
);
assert_eq!(
continuation.journal_path(),
Path::new("/tmp/fleet-activation.json")
);
assert_eq!(continuation.sequence(), 3);
}
#[test]
fn named_ic_environment_is_explicit_for_cargo_builds() {
let root = temp_dir("canic-install-build-environment");
fs::create_dir_all(&root).expect("create root");
fs::write(
root.join("icp.yaml"),
"environments:\n - name: staging\n network: ic\n",
)
.expect("write icp yaml");
let context = resolve_install_build_context(
&root,
&root,
&root.join("canic.toml"),
"staging",
"root",
Some(CanisterBuildProfile::Fast),
)
.expect("resolve build context");
let mut command = std::process::Command::new("cargo");
context.apply_to_command(&mut command);
assert_eq!(context.environment, "staging");
assert_eq!(context.build_network, BuildNetwork::Ic);
assert!(command.get_envs().any(|(key, value)| {
key == "ICP_ENVIRONMENT" && value.is_some_and(|value| value == "ic")
}));
fs::remove_dir_all(root).expect("remove temp root");
}
fn source_section<'a>(source: &'a str, start: &str, end: &str) -> &'a str {
let start_index = source.find(start).expect("source section start exists");
let end_index = source[start_index..]
.find(end)
.map(|offset| start_index + offset)
.expect("source section end exists");
&source[start_index..end_index]
}
fn assert_before(source: &str, before: &str, after: &str) {
let before_index = source.find(before).expect("before marker exists");
let after_index = source.find(after).expect("after marker exists");
assert!(
before_index < after_index,
"`{before}` must appear before `{after}`"
);
}
fn write_temp_workspace_config(config_source: &str) -> PathBuf {
let root = temp_dir("canic-install-test");
fs::create_dir_all(root.join("apps")).expect("temp apps dir must be created");
fs::write(root.join("apps/canic.toml"), config_source)
.expect("temp canic.toml must be written");
root
}
fn demo_config_source(attached: &str) -> String {
format!(
r#"
controllers = []
[services.fleet]
roles = []
[app]
name = "demo"
init_mode = "enabled"
[roles.root]
kind = "root"
package = "root"
[roles.app]
kind = "canister"
package = "app"
[roles.project_registry]
kind = "canister"
package = "project_registry"
[roles.oracle_pokemon]
kind = "canister"
package = "oracle_pokemon"
[roles.user_hub]
kind = "canister"
package = "user_hub"
[roles.user_shard]
kind = "canister"
package = "user_shard"
[roles.scale_hub]
kind = "canister"
package = "scale_hub"
[roles.scale_replica]
kind = "canister"
package = "scale"
[roles.worker]
kind = "canister"
package = "worker"
[app.whitelist]
{attached}
"#
)
}
fn local_demo_install_options(root: &Path) -> InstallRootOptions {
write_local_network_authority(root, "local");
InstallRootOptions {
root_canister: "root".to_string(),
root_build_target: "root".to_string(),
environment: "local".to_string(),
fleet_name: "demo".to_string(),
icp_root: Some(root.to_path_buf()),
build_profile: Some(CanisterBuildProfile::Fast),
config_path: Some("apps/demo/canic.toml".to_string()),
expected_app: Some("demo".to_string()),
interactive_config_selection: false,
deployment_plan_override: None,
}
}
fn write_demo_root_only_config(config_path: &Path) {
fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
fs::write(
config_path,
r#"
controllers = []
[services.fleet]
roles = []
[app]
name = "demo"
init_mode = "enabled"
[roles.root]
kind = "root"
package = "root"
[app.whitelist]
[subnets.default.canisters.root]
kind = "root"
"#,
)
.expect("write config");
}
fn write_wasm_gz_artifact(root: &Path, role: &str, bytes: &[u8]) {
let path = root
.join(".icp/local/canisters")
.join(role)
.join(format!("{role}.wasm.gz"));
fs::create_dir_all(path.parent().expect("artifact parent")).expect("create artifact dir");
fs::write(path, bytes).expect("write artifact");
}
fn demo_install_deployment_truth_check(root_name: &str) -> (PathBuf, DeploymentCheckV1) {
let root = temp_dir(root_name);
write_local_network_authority(&root, "local");
let config_path = root.join("apps/demo/canic.toml");
fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
fs::write(
&config_path,
r#"
controllers = []
[services.fleet]
roles = []
[app]
name = "demo"
init_mode = "enabled"
[roles.root]
kind = "root"
package = "root"
[roles.app]
kind = "canister"
package = "app"
[roles.project_registry]
kind = "canister"
package = "project_registry"
[roles.oracle_pokemon]
kind = "canister"
package = "oracle_pokemon"
[roles.user_hub]
kind = "canister"
package = "user_hub"
[roles.user_shard]
kind = "canister"
package = "user_shard"
[roles.scale_hub]
kind = "canister"
package = "scale_hub"
[roles.scale_replica]
kind = "canister"
package = "scale"
[roles.role_baseline]
kind = "canister"
package = "role_baseline"
[roles.worker]
kind = "canister"
package = "worker"
[app.whitelist]
[subnets.default.canisters.root]
kind = "root"
"#,
)
.expect("write config");
write_wasm_gz_artifact(&root, "root", b"root-artifact");
let options = InstallRootOptions {
root_canister: "root".to_string(),
root_build_target: "root".to_string(),
environment: "local".to_string(),
fleet_name: "demo".to_string(),
icp_root: Some(root.clone()),
build_profile: Some(CanisterBuildProfile::Fast),
config_path: Some("apps/demo/canic.toml".to_string()),
expected_app: Some("demo".to_string()),
interactive_config_selection: false,
deployment_plan_override: None,
};
let check = current_install_deployment_truth_check_at(
&options,
&root,
&root,
&config_path,
"demo",
"2026-05-22T00:00:00Z".to_string(),
)
.expect("deployment truth check");
(root, check)
}
fn sample_fleet_activation_identity() -> FleetActivationIdentity {
FleetActivationIdentity {
fleet: FleetBinding {
fleet: FleetKey {
network: CanonicalNetworkId::public_ic(),
fleet_id: FleetId::from_generated_bytes([7; 32]),
},
app: AppId::from("demo"),
},
operation_id: [8; 32],
release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes([9; 32])),
}
}