Skip to content

Commit 5391e11

Browse files
authored
fix: error if explicit canister path is bad (#276)
1 parent 60ce4fc commit 5391e11

File tree

4 files changed

+154
-35
lines changed

4 files changed

+154
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
* fix: Validate explicit canister paths and throw an error if `canister.yaml` is not found
4+
35
# v0.1.0-beta.3
46

57
* feat: Remove requirement that the user install `icp-cli-network-launcher`, auto-install it on first use

crates/icp-cli/src/operations/settings.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use snafu::{ResultExt, Snafu};
1212
use crate::progress::{ProgressManager, ProgressManagerSettings};
1313

1414
#[derive(Debug, Snafu)]
15+
#[allow(clippy::enum_variant_names)]
1516
pub(crate) enum SyncSettingsOperationError {
1617
#[snafu(display("failed to fetch current canister settings for canister {canister}"))]
1718
FetchCurrentSettings {

crates/icp-cli/tests/project_tests.rs

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -139,34 +139,132 @@ fn glob_path() {
139139
.success();
140140
}
141141

142-
// TODO(or.ricon): This test is currently not passing, fix it.
143-
// #[test]
144-
// fn explicit_path_missing() {
145-
// let ctx = TestContext::new();
146-
147-
// // Setup project
148-
// let project_dir = ctx.create_project_dir("icp");
149-
150-
// // Project manifest
151-
// let pm = r#"
152-
// canisters:
153-
// - my-canister
154-
// "#;
155-
156-
// write_string(
157-
// &project_dir.join("icp.yaml"), // path
158-
// pm, // contents
159-
// )
160-
// .expect("failed to write project manifest");
161-
162-
// // Invoke build
163-
// ctx.icp()
164-
// .current_dir(project_dir)
165-
// .args(["build"])
166-
// .assert()
167-
// .failure()
168-
// .stderr(eq("Error: canister path must exist and be a directory \'my-canister\'").trim());
169-
// }
142+
#[test]
143+
fn explicit_path_missing() {
144+
let ctx = TestContext::new();
145+
146+
// Setup project
147+
let project_dir = ctx.create_project_dir("icp");
148+
149+
// Project manifest
150+
let pm = r#"
151+
canisters:
152+
- my-canister
153+
"#;
154+
155+
write_string(
156+
&project_dir.join("icp.yaml"), // path
157+
pm, // contents
158+
)
159+
.expect("failed to write project manifest");
160+
161+
// Invoke project show
162+
ctx.icp()
163+
.current_dir(project_dir)
164+
.args(["project", "show"])
165+
.assert()
166+
.failure()
167+
.stderr(contains(
168+
"could not locate a canister manifest at: 'my-canister'",
169+
));
170+
}
171+
172+
#[test]
173+
fn explicit_path_missing_canister_yaml() {
174+
let ctx = TestContext::new();
175+
176+
// Setup project
177+
let project_dir = ctx.create_project_dir("icp");
178+
179+
// Project manifest
180+
let pm = r#"
181+
canisters:
182+
- my-canister
183+
"#;
184+
185+
write_string(
186+
&project_dir.join("icp.yaml"), // path
187+
pm, // contents
188+
)
189+
.expect("failed to write project manifest");
190+
191+
// Create directory but no canister.yaml
192+
create_dir_all(&project_dir.join("my-canister")).expect("failed to create canister directory");
193+
194+
// Invoke project show
195+
ctx.icp()
196+
.current_dir(project_dir)
197+
.args(["project", "show"])
198+
.assert()
199+
.failure()
200+
.stderr(contains(
201+
"could not locate a canister manifest at: 'my-canister'",
202+
));
203+
}
204+
205+
#[test]
206+
fn explicit_path_with_subdirectory() {
207+
let ctx = TestContext::new();
208+
209+
// Setup project
210+
let project_dir = ctx.create_project_dir("icp");
211+
212+
// Project manifest
213+
let pm = r#"
214+
canisters:
215+
- canisters/backend
216+
- canisters/frontend
217+
"#;
218+
219+
write_string(
220+
&project_dir.join("icp.yaml"), // path
221+
pm, // contents
222+
)
223+
.expect("failed to write project manifest");
224+
225+
// Backend canister manifest
226+
let backend_cm = indoc! {r#"
227+
name: backend
228+
build:
229+
steps:
230+
- type: script
231+
command: echo "build"
232+
"#};
233+
234+
create_dir_all(&project_dir.join("canisters/backend"))
235+
.expect("failed to create backend directory");
236+
237+
write_string(
238+
&project_dir.join("canisters/backend/canister.yaml"),
239+
backend_cm,
240+
)
241+
.expect("failed to write backend manifest");
242+
243+
// Frontend canister manifest
244+
let frontend_cm = indoc! {r#"
245+
name: frontend
246+
build:
247+
steps:
248+
- type: script
249+
command: echo "build"
250+
"#};
251+
252+
create_dir_all(&project_dir.join("canisters/frontend"))
253+
.expect("failed to create frontend directory");
254+
255+
write_string(
256+
&project_dir.join("canisters/frontend/canister.yaml"),
257+
frontend_cm,
258+
)
259+
.expect("failed to write frontend manifest");
260+
261+
// Invoke project show - should succeed
262+
ctx.icp()
263+
.current_dir(&project_dir)
264+
.args(["project", "show"])
265+
.assert()
266+
.success();
267+
}
170268

171269
#[test]
172270
fn redefine_mainnet_network_disallowed() {
@@ -189,7 +287,7 @@ fn redefine_mainnet_network_disallowed() {
189287
// Any command that loads the project should fail
190288
ctx.icp()
191289
.current_dir(project_dir)
192-
.args(["build"])
290+
.args(["project", "show"])
193291
.assert()
194292
.failure()
195293
.stderr(contains("`mainnet` is a reserved network name"));

crates/icp/src/project.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ pub async fn consolidate_manifest(
143143
for i in &m.canisters {
144144
let ms = match i {
145145
Item::Path(pattern) => {
146-
let paths = match is_glob(pattern) {
146+
let is_glob_pattern = is_glob(pattern);
147+
let paths = match is_glob_pattern {
147148
// Explicit path
148149
false => vec![pdir.join(pattern)],
149150

@@ -163,11 +164,28 @@ pub async fn consolidate_manifest(
163164
}
164165
};
165166

166-
let paths = paths
167-
.into_iter()
168-
.filter(|p| p.is_dir()) // Skip missing directories
169-
.filter(|p| p.join(CANISTER_MANIFEST).exists()) // Skip non-canister directories
170-
.collect::<Vec<_>>();
167+
let paths = if is_glob_pattern {
168+
// For glob patterns, filter out non-directories and non-canister directories
169+
paths
170+
.into_iter()
171+
.filter(|p| p.is_dir())
172+
.filter(|p| p.join(CANISTER_MANIFEST).exists())
173+
.collect::<Vec<_>>()
174+
} else {
175+
// For explicit paths, validate that they exist and contain canister.yaml
176+
let mut validated_paths = vec![];
177+
for p in paths {
178+
if !p.join(CANISTER_MANIFEST).is_file() {
179+
return NotFoundSnafu {
180+
kind: "canister".to_string(),
181+
path: pattern.to_string(),
182+
}
183+
.fail();
184+
}
185+
validated_paths.push(p);
186+
}
187+
validated_paths
188+
};
171189

172190
let mut ms = vec![];
173191

0 commit comments

Comments
 (0)