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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* feat: `icp canister metadata <canister> <metadata section>` now fetches metadata sections from specified canisters
* fix: Validate explicit canister paths and throw an error if `canister.yaml` is not found
* feat!: Rename the implicit "mainnet" network to "ic"
* The corresponding environment "ic" is defined implicitly which can be overwritten by user configuration
* The corresponding environment "ic" is defined implicitly which can be overwritten by user configuration.
* The `--mainnet` and `--ic` flags are removed. Use `-n/--network ic`, `-e/--environment ic` instead.
* feat: Allow overriding the implicit `local` network and environment.

# v0.1.0-beta.3

Expand Down
166 changes: 166 additions & 0 deletions crates/icp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,3 +510,169 @@ impl ProjectLoad for NoProjectLoader {
Ok(false)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::canister::recipe::{Resolve, ResolveError};
use crate::manifest::{
ProjectRootLocate, ProjectRootLocateError,
canister::{BuildSteps, SyncSteps},
recipe::Recipe,
};
use camino_tempfile::Utf8TempDir;
use indoc::indoc;

struct MockProjectRootLocate {
path: PathBuf,
}

impl MockProjectRootLocate {
fn new(path: PathBuf) -> Self {
Self { path }
}
}

impl ProjectRootLocate for MockProjectRootLocate {
fn locate(&self) -> Result<PathBuf, ProjectRootLocateError> {
Ok(self.path.clone())
}
}

struct MockRecipeResolver;

#[async_trait]
impl Resolve for MockRecipeResolver {
async fn resolve(&self, _recipe: &Recipe) -> Result<(BuildSteps, SyncSteps), ResolveError> {
use crate::manifest::adapter::prebuilt::{
Adapter as PrebuiltAdapter, LocalSource, SourceField,
};
use crate::manifest::canister::BuildStep;

// Create a minimal BuildSteps with a dummy prebuilt step
let build_steps = BuildSteps {
steps: vec![BuildStep::Prebuilt(PrebuiltAdapter {
source: SourceField::Local(LocalSource {
path: "dummy.wasm".into(),
}),
sha256: None,
})],
};

Ok((build_steps, SyncSteps::default()))
}
}

#[tokio::test]
async fn test_load_minimal_project() {
// Create temp directory with icp.yaml
let temp_dir = Utf8TempDir::new().unwrap();
let project_dir = temp_dir.path();

// Write a minimal icp.yaml
let manifest_content = indoc! {r#"
canisters:
- name: backend
build:
steps:
- type: pre-built
path: backend.wasm
"#};
std::fs::write(project_dir.join("icp.yaml"), manifest_content).unwrap();

// Create ProjectLoadImpl with mocks
let loader = ProjectLoadImpl {
project_root_locate: Arc::new(MockProjectRootLocate::new(project_dir.to_path_buf())),
recipe: Arc::new(MockRecipeResolver),
};

// Call load
let result = loader.load().await;

// Assert success and check project contents
assert!(result.is_ok());
let project = result.unwrap();
assert_eq!(project.dir, project_dir);
assert!(
project.canisters.contains_key("backend"),
"The backend canister was not found"
);
assert!(
project.environments.contains_key("local"),
"The default `local` environment was not injected"
);
assert!(
project.environments.contains_key("ic"),
"The default `ic` environment was not injected"
);
assert!(
project.networks.contains_key("local"),
"The default `local` network was not injected"
);
assert!(
project.networks.contains_key("ic"),
"The default `ic` network was not injected"
);
}

#[tokio::test]
async fn test_load_project_local_override() {
// Create temp directory with icp.yaml
let temp_dir = Utf8TempDir::new().unwrap();
let project_dir = temp_dir.path();

// Write a minimal icp.yaml
let manifest_content = indoc! {r#"
networks:
- name: test-network
mode: connected
url: https://somenetwork.icp
environments:
- name: local
network: test-network
canisters:
- name: backend
build:
steps:
- type: pre-built
path: backend.wasm
"#};
std::fs::write(project_dir.join("icp.yaml"), manifest_content).unwrap();

// Create ProjectLoadImpl with mocks
let loader = ProjectLoadImpl {
project_root_locate: Arc::new(MockProjectRootLocate::new(project_dir.to_path_buf())),
recipe: Arc::new(MockRecipeResolver),
};

// Call load
let result = loader.load().await;

// Assert success and check project contents
assert!(result.is_ok(), "The project did not load: {:?}", result);
let project = result.unwrap();
assert_eq!(project.dir, project_dir);
assert!(
project.canisters.contains_key("backend"),
"The backend canister was not found"
);
assert!(
project.environments.contains_key("local"),
"The default `local` environment was not injected"
);
let e = project.environments.get("local").unwrap();
assert_eq!(e.network.name, "test-network");
assert!(
project.environments.contains_key("ic"),
"The default `ic` environment was not injected"
);
assert!(
project.networks.contains_key("local"),
"The default `local` network was not injected"
);
assert!(
project.networks.contains_key("ic"),
"The default `ic` network was not injected"
);
}
}
41 changes: 5 additions & 36 deletions crates/icp/src/manifest/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::collections::HashMap;

use schemars::JsonSchema;
use serde::{Deserialize, Deserializer};
use snafu::prelude::*;

use crate::{canister::Settings, prelude::LOCAL};

Expand Down Expand Up @@ -52,16 +51,8 @@ pub struct EnvironmentManifest {
pub init_args: Option<HashMap<String, String>>,
}

#[derive(Debug, Snafu)]
pub enum ParseError {
#[snafu(display("Overriding the local environment is not supported."))]
OverrideLocal,
}

impl TryFrom<EnvironmentInner> for EnvironmentManifest {
type Error = ParseError;

fn try_from(v: EnvironmentInner) -> Result<Self, Self::Error> {
impl From<EnvironmentInner> for EnvironmentManifest {
fn from(v: EnvironmentInner) -> Self {
let EnvironmentInner {
name,
network,
Expand All @@ -70,11 +61,6 @@ impl TryFrom<EnvironmentInner> for EnvironmentManifest {
init_args,
} = v;

// Name
if name == LOCAL {
return OverrideLocalSnafu.fail();
}

// Network
let network = network.unwrap_or(LOCAL.to_string());

Expand All @@ -93,22 +79,22 @@ impl TryFrom<EnvironmentInner> for EnvironmentManifest {
None => CanisterSelection::Everything,
};

Ok(Self {
Self {
name,
network,
canisters,

// Keep as-is, setting overrides is optional
settings,
init_args,
})
}
}
}

impl<'de> Deserialize<'de> for EnvironmentManifest {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let inner: EnvironmentInner = Deserialize::deserialize(d)?;
inner.try_into().map_err(serde::de::Error::custom)
Ok(inner.into())
}
}

Expand All @@ -134,21 +120,4 @@ mod tests {
},
);
}

#[test]
fn override_local() {
match serde_yaml::from_str::<EnvironmentManifest>(r#"name: local"#) {
// No Error
Ok(_) => {
panic!("an environment named local should result in an error");
}

// Wrong Error
Err(err) => {
if !format!("{err}").starts_with("Overriding the local environment") {
panic!("an environment named local resulted in the wrong error: {err}");
};
}
};
}
}