Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 63 additions & 17 deletions crates/icp-cli/tests/canister_create_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,68 @@ async fn canister_create() {
);
}

/// Verifies that `canister create --subnet <id>` creates the canister on the requested subnet.
///
/// The network is configured with multiple application subnets so the placement is an actual
/// choice: if `--subnet` were ignored (and a subnet picked by default instead), the canister
/// could land on a different one and the assertion would fail.
#[tokio::test]
async fn canister_create_on_requested_subnet() {
let ctx = TestContext::new();
let project_dir = ctx.create_project_dir("icp");

let pm = formatdoc! {r#"
canisters:
- name: my-canister
build:
steps:
- type: script
command: echo hi

networks:
- name: random-network
mode: managed
gateway:
port: 0
subnets: [application, application]
{ENVIRONMENT_RANDOM_PORT}
"#};

write_string(&project_dir.join("icp.yaml"), &pm).expect("failed to write project manifest");

let _g = ctx.start_network_in(&project_dir, "random-network").await;
ctx.ping_until_healthy(&project_dir, "random-network");

let subnet_id = ctx.application_subnet_id().await;

let icp_client = clients::icp(&ctx, &project_dir, Some("random-environment".to_string()));
icp_client.mint_cycles(10 * TRILLION);

ctx.icp()
.current_dir(&project_dir)
.args([
"canister",
"create",
"my-canister",
"--subnet",
&subnet_id,
"--environment",
"random-environment",
])
.assert()
.success();

// The canister must be created on exactly the subnet we requested.
let actual_subnet = clients::registry(&ctx)
.get_subnet_for_canister(icp_client.get_canister_id("my-canister"))
.await;
assert_eq!(
actual_subnet.to_string(),
subnet_id,
"canister should be created on the requested subnet"
);
}

#[tokio::test]
async fn canister_create_with_settings() {
let ctx = TestContext::new();
Expand Down Expand Up @@ -979,23 +1041,7 @@ async fn canister_create_cloud_engine() {

// Find the CloudEngine subnet by querying the topology endpoint
// TODO replace with a subnet selection parameter once we have one
let topology_url = ctx.gateway_url().join("/_/topology").unwrap();
let topology: serde_json::Value = reqwest::get(topology_url)
.await
.expect("failed to fetch topology")
.json()
.await
.expect("failed to parse topology");

let subnet_configs = topology["subnet_configs"]
.as_object()
.expect("subnet_configs should be an object");
let cloud_engine_subnet_id = subnet_configs
.iter()
.find_map(|(id, config)| {
(config["subnet_kind"].as_str()? == "CloudEngine").then_some(id.clone())
})
.expect("no CloudEngine subnet found in topology");
let cloud_engine_subnet_id = ctx.cloud_engine_subnet_id().await;

// Create the canister on the CloudEngine subnet
// Only the admin can do this. In local envs, the admin is the anonymous principal
Expand Down
8 changes: 1 addition & 7 deletions crates/icp-cli/tests/canister_delete_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ async fn canister_delete() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
8 changes: 1 addition & 7 deletions crates/icp-cli/tests/canister_info_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ async fn canister_status() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
16 changes: 2 additions & 14 deletions crates/icp-cli/tests/canister_metadata_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ async fn canister_metadata() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -109,13 +103,7 @@ async fn canister_metadata_not_found() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
58 changes: 7 additions & 51 deletions crates/icp-cli/tests/canister_settings_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,7 @@ async fn canister_settings_update_controllers() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -425,13 +419,7 @@ async fn canister_settings_update_log_visibility() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -731,8 +719,6 @@ async fn canister_settings_update_miscellaneous() {
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--cycles",
"120t", // 120T cycles because compute allocation is expensive
"--environment",
Expand Down Expand Up @@ -859,13 +845,7 @@ async fn canister_settings_update_environment_variables() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -1036,13 +1016,7 @@ async fn canister_settings_sync() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -1220,13 +1194,7 @@ async fn canister_settings_sync_log_visibility() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -1458,13 +1426,7 @@ async fn canister_settings_show() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down Expand Up @@ -1555,13 +1517,7 @@ async fn canister_settings_show_not_a_controller() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
8 changes: 1 addition & 7 deletions crates/icp-cli/tests/canister_start_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,7 @@ async fn canister_start() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
8 changes: 1 addition & 7 deletions crates/icp-cli/tests/canister_status_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,7 @@ async fn canister_status() {

ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
8 changes: 1 addition & 7 deletions crates/icp-cli/tests/canister_stop_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ async fn canister_stop() {
.mint_cycles(10 * TRILLION);
ctx.icp()
.current_dir(&project_dir)
.args([
"deploy",
"--subnet",
common::SUBNET_ID,
"--environment",
"random-environment",
])
.args(["deploy", "--environment", "random-environment"])
.assert()
.success();

Expand Down
42 changes: 42 additions & 0 deletions crates/icp-cli/tests/common/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,48 @@ impl TestContext {
self.http_gateway_url.get().unwrap()
}

/// Returns the ID of an application subnet of the running network.
///
/// See [`subnet_id_by_kind`](Self::subnet_id_by_kind) for why discovery happens at runtime.
pub(crate) async fn application_subnet_id(&self) -> String {
self.subnet_id_by_kind("Application").await
}

/// Returns the ID of a CloudEngine subnet of the running network.
///
/// See [`subnet_id_by_kind`](Self::subnet_id_by_kind) for why discovery happens at runtime.
pub(crate) async fn cloud_engine_subnet_id(&self) -> String {
self.subnet_id_by_kind("CloudEngine").await
}

/// Returns the ID of a subnet with the given `subnet_kind`, querying the running network's
/// `/_/topology` endpoint. A topology may contain more than one subnet of a kind; this
/// returns one of them and panics if none exist.
///
/// Subnet IDs are derived deterministically from the launcher topology, so adding or
/// removing subnets shifts them. Discovering the ID at runtime keeps the tests robust
/// against launcher topology changes instead of hard-coding an ID.
///
/// The network must be started and healthy before calling this.
async fn subnet_id_by_kind(&self, subnet_kind: &str) -> String {
let topology_url = self.gateway_url().join("/_/topology").unwrap();
let topology: serde_json::Value = reqwest::get(topology_url)
.await
.expect("failed to fetch topology")
.json()
.await
.expect("failed to parse topology");

topology["subnet_configs"]
.as_object()
.expect("subnet_configs should be an object")
.iter()
.find_map(|(id, config)| {
(config["subnet_kind"].as_str()? == subnet_kind).then_some(id.clone())
})
.unwrap_or_else(|| panic!("no {subnet_kind} subnet found in topology"))
}

pub(crate) fn agent(&self) -> Agent {
let agent = Agent::builder()
.with_url(self.http_gateway_url.get().unwrap().as_str())
Expand Down
8 changes: 0 additions & 8 deletions crates/icp-cli/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ environments:
network: docker-engine-network
"#;

/// This ID is dependent on the toplogy being served by pocket-ic
/// NOTE: If the topology is changed (another subnet is added, etc) the ID may change.
/// References:
/// - http://localhost:8000/_/topology
/// - http://localhost:8000/_/dashboard
pub(crate) const SUBNET_ID: &str =
"cok7q-nnbiu-4xwf6-7gpqg-kwzft-mqypn-uepxh-mx2hy-q4wuy-5s5my-eae";

// Spawns a test server that expects a single request and responds with a 200 status code and the given body
pub(crate) fn spawn_test_server(method: &str, path: &str, body: &[u8]) -> httptest::Server {
// Run the server
Expand Down
Loading
Loading