Skip to content

Commit e39431c

Browse files
committed
add unit tests for context
1 parent 503ff38 commit e39431c

File tree

6 files changed

+320
-15
lines changed

6 files changed

+320
-15
lines changed

crates/icp-cli/src/commands/mod.rs

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,24 @@ impl Context {
243243
let agent = self.agent.create(id, url).await?;
244244
Ok(agent)
245245
}
246+
247+
#[cfg(test)]
248+
/// Creates a test context with all mocks
249+
pub(crate) fn mocked() -> Context {
250+
Context {
251+
term: Term::stderr(),
252+
dirs: Arc::new(icp::directories::UnimplementedMockDirs),
253+
ids: Arc::new(crate::store_id::MockInMemoryIdStore::new()),
254+
artifacts: Arc::new(crate::store_artifact::MockInMemoryArtifactStore::new()),
255+
project: Arc::new(icp::MockProjectLoader::minimal()),
256+
identity: Arc::new(icp::identity::MockIdentityLoader::anonymous()),
257+
network: Arc::new(icp::network::MockNetworkAccessor::localhost()),
258+
agent: Arc::new(icp::agent::Creator),
259+
builder: Arc::new(icp::canister::build::UnimplementedMockBuilder),
260+
syncer: Arc::new(icp::canister::sync::UnimplementedMockSyncer),
261+
debug: false,
262+
}
263+
}
246264
}
247265

248266
#[derive(Debug, Snafu)]
@@ -337,3 +355,290 @@ pub(crate) enum GetAgentForUrlError {
337355
#[snafu(transparent)]
338356
AgentCreate { source: icp::agent::CreateError },
339357
}
358+
359+
#[cfg(test)]
360+
mod context_tests {
361+
use super::*;
362+
use crate::store_id::MockInMemoryIdStore;
363+
use icp::{MockProjectLoader, identity::MockIdentityLoader, network::MockNetworkAccessor};
364+
365+
#[tokio::test]
366+
async fn test_get_identity_default() {
367+
let ctx = Context::mocked();
368+
369+
let result = ctx.get_identity(&IdentitySelection::Default).await;
370+
371+
assert!(result.is_ok());
372+
}
373+
374+
#[tokio::test]
375+
async fn test_get_identity_anonymous() {
376+
let ctx = Context::mocked();
377+
378+
let result = ctx.get_identity(&IdentitySelection::Anonymous).await;
379+
380+
assert!(result.is_ok());
381+
}
382+
383+
#[tokio::test]
384+
async fn test_get_identity_named() {
385+
let alice_identity: Arc<dyn Identity> = Arc::new(ic_agent::identity::AnonymousIdentity);
386+
387+
let ctx = Context {
388+
identity: Arc::new(
389+
MockIdentityLoader::anonymous().with_identity("alice", Arc::clone(&alice_identity)),
390+
),
391+
..Context::mocked()
392+
};
393+
394+
let result = ctx
395+
.get_identity(&IdentitySelection::Named("alice".to_string()))
396+
.await;
397+
398+
assert!(result.is_ok());
399+
}
400+
401+
#[tokio::test]
402+
async fn test_get_identity_named_not_found() {
403+
let ctx = Context::mocked();
404+
405+
let result = ctx
406+
.get_identity(&IdentitySelection::Named("nonexistent".to_string()))
407+
.await;
408+
409+
assert!(matches!(
410+
result,
411+
Err(GetIdentityError::IdentityLoad {
412+
identity: IdentitySelection::Named(_),
413+
source: icp::identity::LoadError::LoadIdentity(_)
414+
})
415+
));
416+
}
417+
418+
#[tokio::test]
419+
async fn test_get_environment_success() {
420+
let ctx = Context {
421+
project: Arc::new(MockProjectLoader::complex()),
422+
..Context::mocked()
423+
};
424+
425+
let env = ctx.get_environment("dev").await.unwrap();
426+
427+
assert_eq!(env.name, "dev");
428+
}
429+
430+
#[tokio::test]
431+
async fn test_get_environment_not_found() {
432+
let ctx = Context::mocked();
433+
434+
let result = ctx.get_environment("nonexistent").await;
435+
436+
assert!(matches!(
437+
result,
438+
Err(GetEnvironmentError::EnvironmentNotFound { ref name }) if name == "nonexistent"
439+
));
440+
}
441+
442+
#[tokio::test]
443+
async fn test_get_network_success() {
444+
let ctx = Context {
445+
project: Arc::new(MockProjectLoader::complex()),
446+
..Context::mocked()
447+
};
448+
449+
let network = ctx.get_network("local").await.unwrap();
450+
451+
assert_eq!(network.name, "local");
452+
}
453+
454+
#[tokio::test]
455+
async fn test_get_network_not_found() {
456+
let ctx = Context::mocked();
457+
458+
let result = ctx.get_network("nonexistent").await;
459+
460+
assert!(matches!(
461+
result,
462+
Err(GetNetworkError::NetworkNotFound { ref name }) if name == "nonexistent"
463+
));
464+
}
465+
466+
#[tokio::test]
467+
async fn test_get_canister_id_for_env_success() {
468+
use crate::store_id::{Access as IdAccess, Key};
469+
use candid::Principal;
470+
471+
let ids_store = Arc::new(MockInMemoryIdStore::new());
472+
473+
// Register a canister ID for the dev environment
474+
let canister_id = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
475+
ids_store
476+
.register(
477+
&Key {
478+
network: "local".to_string(),
479+
environment: "dev".to_string(),
480+
canister: "backend".to_string(),
481+
},
482+
&canister_id,
483+
)
484+
.unwrap();
485+
486+
let ctx = Context {
487+
project: Arc::new(MockProjectLoader::complex()),
488+
ids: ids_store,
489+
..Context::mocked()
490+
};
491+
492+
let cid = ctx.get_canister_id_for_env("backend", "dev").await.unwrap();
493+
494+
assert_eq!(cid, canister_id);
495+
}
496+
497+
#[tokio::test]
498+
async fn test_get_canister_id_for_env_canister_not_in_env() {
499+
let ctx = Context {
500+
project: Arc::new(MockProjectLoader::complex()),
501+
..Context::mocked()
502+
};
503+
504+
// "database" is only in "dev" environment, not in "test"
505+
let result = ctx.get_canister_id_for_env("database", "test").await;
506+
507+
assert!(matches!(
508+
result,
509+
Err(GetCanisterIdForEnvError::CanisterNotFoundInEnv {
510+
ref canister_name,
511+
ref environment_name,
512+
}) if canister_name == "database" && environment_name == "test"
513+
));
514+
}
515+
516+
#[tokio::test]
517+
async fn test_get_canister_id_for_env_id_not_registered() {
518+
let ctx = Context {
519+
project: Arc::new(MockProjectLoader::complex()),
520+
..Context::mocked()
521+
};
522+
523+
// Environment exists and canister is in it, but ID not registered
524+
let result = ctx.get_canister_id_for_env("backend", "dev").await;
525+
526+
assert!(matches!(
527+
result,
528+
Err(GetCanisterIdForEnvError::CanisterIdLookup {
529+
ref canister_name,
530+
ref environment_name,
531+
..
532+
}) if canister_name == "backend" && environment_name == "dev"
533+
));
534+
}
535+
536+
#[tokio::test]
537+
async fn test_get_agent_for_env_uses_environment_network() {
538+
use icp::network::access::NetworkAccess;
539+
540+
let staging_root_key = vec![1, 2, 3];
541+
542+
// Complex project has "test" environment which uses "staging" network
543+
let ctx = Context {
544+
project: Arc::new(MockProjectLoader::complex()),
545+
network: Arc::new(
546+
MockNetworkAccessor::localhost()
547+
.with_network(
548+
"local",
549+
NetworkAccess {
550+
default_effective_canister_id: None,
551+
root_key: None,
552+
url: "http://localhost:8000".to_string(),
553+
},
554+
)
555+
.with_network(
556+
"staging",
557+
NetworkAccess {
558+
default_effective_canister_id: None,
559+
root_key: Some(staging_root_key.clone()),
560+
url: "http://staging:9000".to_string(),
561+
},
562+
),
563+
),
564+
..Context::mocked()
565+
};
566+
567+
let agent = ctx
568+
.get_agent_for_env(&IdentitySelection::Anonymous, "test")
569+
.await
570+
.unwrap();
571+
572+
assert_eq!(agent.read_root_key(), staging_root_key);
573+
}
574+
575+
#[tokio::test]
576+
async fn test_get_agent_for_env_environment_not_found() {
577+
let ctx = Context::mocked();
578+
579+
let result = ctx
580+
.get_agent_for_env(&IdentitySelection::Anonymous, "nonexistent")
581+
.await;
582+
583+
assert!(matches!(
584+
result,
585+
Err(GetAgentForEnvError::GetEnvironment {
586+
source: GetEnvironmentError::EnvironmentNotFound { .. }
587+
})
588+
));
589+
}
590+
591+
#[tokio::test]
592+
async fn test_get_agent_for_network_success() {
593+
use icp::network::access::NetworkAccess;
594+
595+
let root_key = vec![1, 2, 3];
596+
597+
let ctx = Context {
598+
project: Arc::new(MockProjectLoader::complex()),
599+
network: Arc::new(MockNetworkAccessor::localhost().with_network(
600+
"local",
601+
NetworkAccess {
602+
default_effective_canister_id: None,
603+
root_key: Some(root_key.clone()),
604+
url: "http://localhost:8000".to_string(),
605+
},
606+
)),
607+
..Context::mocked()
608+
};
609+
610+
let agent = ctx
611+
.get_agent_for_network(&IdentitySelection::Anonymous, "local")
612+
.await
613+
.unwrap();
614+
615+
assert_eq!(agent.read_root_key(), root_key);
616+
}
617+
618+
#[tokio::test]
619+
async fn test_get_agent_for_network_network_not_found() {
620+
let ctx = Context::mocked();
621+
622+
let result = ctx
623+
.get_agent_for_network(&IdentitySelection::Anonymous, "nonexistent")
624+
.await;
625+
626+
assert!(matches!(
627+
result,
628+
Err(GetAgentForNetworkError::GetNetwork {
629+
source: GetNetworkError::NetworkNotFound { .. }
630+
})
631+
));
632+
}
633+
634+
#[tokio::test]
635+
async fn test_get_agent_for_url_success() {
636+
let ctx = Context::mocked();
637+
638+
let result = ctx
639+
.get_agent_for_url(&IdentitySelection::Anonymous, "http://localhost:8000")
640+
.await;
641+
642+
assert!(result.is_ok());
643+
}
644+
}

crates/icp/src/canister/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ impl Build for Builder {
108108
}
109109
}
110110

111-
#[cfg(test)]
111+
#[cfg(any(test, feature = "test-features"))]
112112
/// Unimplemented mock implementation of `Build` for testing purposes.
113113
///
114114
/// All methods panic with `unimplemented!()` when called.
115115
/// This is useful for tests that need to construct a context but don't
116116
/// actually use the build functionality.
117117
pub struct UnimplementedMockBuilder;
118118

119-
#[cfg(test)]
119+
#[cfg(any(test, feature = "test-features"))]
120120
#[async_trait]
121121
impl Build for UnimplementedMockBuilder {
122122
async fn build(

crates/icp/src/canister/sync.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ impl Synchronize for Syncer {
100100
}
101101
}
102102

103-
#[cfg(test)]
103+
#[cfg(any(test, feature = "test-features"))]
104104
/// Unimplemented mock implementation of `Synchronize` for testing purposes.
105105
///
106106
/// All methods panic with `unimplemented!()` when called.
107107
/// This is useful for tests that need to construct a context but don't
108108
/// actually use the sync functionality.
109109
pub struct UnimplementedMockSyncer;
110110

111-
#[cfg(test)]
111+
#[cfg(any(test, feature = "test-features"))]
112112
#[async_trait]
113113
impl Synchronize for UnimplementedMockSyncer {
114114
async fn sync(

crates/icp/src/identity/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,10 @@ impl Load for Loader {
112112
}
113113
}
114114

115-
#[cfg(test)]
115+
#[cfg(any(test, feature = "test-features"))]
116116
use std::collections::HashMap;
117117

118-
#[cfg(test)]
118+
#[cfg(any(test, feature = "test-features"))]
119119
/// Mock identity loader for testing.
120120
///
121121
/// Allows configuring multiple identities that can be selected by name.
@@ -128,7 +128,7 @@ pub struct MockIdentityLoader {
128128
named: HashMap<String, Arc<dyn Identity>>,
129129
}
130130

131-
#[cfg(test)]
131+
#[cfg(any(test, feature = "test-features"))]
132132
impl MockIdentityLoader {
133133
/// Creates a new mock identity loader with the given default identity.
134134
pub fn new(default: Arc<dyn Identity>) -> Self {
@@ -156,7 +156,7 @@ impl MockIdentityLoader {
156156
}
157157
}
158158

159-
#[cfg(test)]
159+
#[cfg(any(test, feature = "test-features"))]
160160
#[async_trait]
161161
impl Load for MockIdentityLoader {
162162
async fn load(&self, id: IdentitySelection) -> Result<Arc<dyn Identity>, LoadError> {

0 commit comments

Comments
 (0)