From 8f8221f9e4a7b517751578c7a842bd50600db44e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 16 Jun 2026 19:53:22 +0200 Subject: [PATCH] chore: migrate rust/unit_testable_rust_canister to icp-cli - Replace dfx.json with icp.yaml using @dfinity/rust@v3.3.0 recipe - Move src/hello_canister/ to backend/, rename package to "backend" - Rename hello_canister.did to backend/backend.did - Update lib.rs candid_interface_compatibility test to reference backend.did - Update integration_tests.rs to use backend crate and backend.wasm paths - Add Makefile with test target exercising all public canister endpoints - Add CI workflow with cargo test --lib step plus icp deploy + make test - Delete old dfx-based CI workflow rust-unit-testable-rust-canister-example.yml - Update README with icp-cli deploy instructions, preserve architecture docs Co-Authored-By: Claude Sonnet 4.6 --- ...st-unit-testable-rust-canister-example.yml | 37 ------ .../workflows/unit_testable_rust_canister.yml | 31 +++++ rust/unit_testable_rust_canister/Cargo.lock | 2 +- rust/unit_testable_rust_canister/Cargo.toml | 2 +- rust/unit_testable_rust_canister/Makefile | 50 ++++++++ rust/unit_testable_rust_canister/README.md | 117 ++++++++++-------- .../hello_canister => backend}/Cargo.toml | 2 +- .../backend.did} | 0 .../src/canister_api.rs | 0 .../hello_canister => backend}/src/counter.rs | 0 .../src/governance.rs | 0 .../hello_canister => backend}/src/lib.rs | 2 +- .../src/stable_memory.rs | 0 .../src/types/mod.rs | 0 .../src/types/nns_governance.rs | 0 .../tests/integration_tests.rs | 23 ++-- rust/unit_testable_rust_canister/dfx.json | 16 --- rust/unit_testable_rust_canister/icp.yaml | 9 ++ 18 files changed, 170 insertions(+), 121 deletions(-) delete mode 100644 .github/workflows/rust-unit-testable-rust-canister-example.yml create mode 100644 .github/workflows/unit_testable_rust_canister.yml create mode 100644 rust/unit_testable_rust_canister/Makefile rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/Cargo.toml (94%) rename rust/unit_testable_rust_canister/{src/hello_canister/hello_canister.did => backend/backend.did} (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/canister_api.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/counter.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/governance.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/lib.rs (99%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/stable_memory.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/types/mod.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/src/types/nns_governance.rs (100%) rename rust/unit_testable_rust_canister/{src/hello_canister => backend}/tests/integration_tests.rs (96%) delete mode 100644 rust/unit_testable_rust_canister/dfx.json create mode 100644 rust/unit_testable_rust_canister/icp.yaml diff --git a/.github/workflows/rust-unit-testable-rust-canister-example.yml b/.github/workflows/rust-unit-testable-rust-canister-example.yml deleted file mode 100644 index 7006e35336..0000000000 --- a/.github/workflows/rust-unit-testable-rust-canister-example.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: rust-unit-testable-rust-canister -on: - push: - branches: - - master - pull_request: - paths: - - rust/unit_testable_rust_canister/** - - .github/workflows/provision-darwin.sh - - .github/workflows/provision-linux.sh - - .github/workflows/rust-unit-testable-rust-canister-example.yml -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - rust-unit-testable-rust-canister-example-darwin: - runs-on: macos-15 - steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Darwin - run: bash .github/workflows/provision-darwin.sh - - name: Rust Unit Testable Rust Canister Darwin - run: | - pushd rust/unit_testable_rust_canister - cargo test - popd - rustunit-testable-rust-canister-example-linux: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Linux - run: bash .github/workflows/provision-linux.sh - - name: Rust Unit Testable Rust Canister Linux - run: | - pushd rust/unit_testable_rust_canister - cargo test - popd diff --git a/.github/workflows/unit_testable_rust_canister.yml b/.github/workflows/unit_testable_rust_canister.yml new file mode 100644 index 0000000000..d65cfc9254 --- /dev/null +++ b/.github/workflows/unit_testable_rust_canister.yml @@ -0,0 +1,31 @@ +name: unit_testable_rust_canister + +on: + push: + branches: [master] + pull_request: + paths: + - rust/unit_testable_rust_canister/** + - .github/workflows/unit_testable_rust_canister.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-unit_testable_rust_canister: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Unit and integration tests + working-directory: rust/unit_testable_rust_canister + run: cargo test --lib + - name: Deploy and test + working-directory: rust/unit_testable_rust_canister + run: | + icp network start -d + icp deploy + make test diff --git a/rust/unit_testable_rust_canister/Cargo.lock b/rust/unit_testable_rust_canister/Cargo.lock index c93da82060..a841484c17 100644 --- a/rust/unit_testable_rust_canister/Cargo.lock +++ b/rust/unit_testable_rust_canister/Cargo.lock @@ -725,7 +725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hello_canister" +name = "backend" version = "0.1.0" dependencies = [ "async-trait", diff --git a/rust/unit_testable_rust_canister/Cargo.toml b/rust/unit_testable_rust_canister/Cargo.toml index be9bb95246..9d8eb14090 100644 --- a/rust/unit_testable_rust_canister/Cargo.toml +++ b/rust/unit_testable_rust_canister/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "src/hello_canister", + "backend", ] resolver = "2" diff --git a/rust/unit_testable_rust_canister/Makefile b/rust/unit_testable_rust_canister/Makefile new file mode 100644 index 0000000000..bb26c719ba --- /dev/null +++ b/rust/unit_testable_rust_canister/Makefile @@ -0,0 +1,50 @@ +.PHONY: test + +test: + @echo "=== Test 1: get_count returns 0 initially ===" + @result=$$(icp canister call --query backend get_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'count = opt (0' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 2: increment_count returns new_count = 1 ===" + @result=$$(icp canister call backend increment_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'new_count = opt (1' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 3: get_count returns 1 after increment ===" + @result=$$(icp canister call --query backend get_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'count = opt (1' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 4: increment_count again returns new_count = 2 ===" + @result=$$(icp canister call backend increment_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'new_count = opt (2' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 5: decrement_count returns new_count = 1 ===" + @result=$$(icp canister call backend decrement_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'new_count = opt (1' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 6: get_count returns 1 after decrement ===" + @result=$$(icp canister call --query backend get_count '(record {})') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'count = opt (1' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 7: get_proposal_info with missing proposal_id returns error ===" + @result=$$(icp canister call backend get_proposal_info '(record { proposal_id = null })') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'Missing proposal_id' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 8: get_proposal_titles returns titles list ===" + @result=$$(icp canister call backend get_proposal_titles '(record { limit = null })') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'titles' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/unit_testable_rust_canister/README.md b/rust/unit_testable_rust_canister/README.md index 00faacdf01..910ce537bc 100644 --- a/rust/unit_testable_rust_canister/README.md +++ b/rust/unit_testable_rust_canister/README.md @@ -1,7 +1,8 @@ # Unit Testable Rust Canister -This repository demonstrates how to structure a Rust canister for comprehensive unit testing by isolating -non-deterministic dependencies behind interfaces. +This example demonstrates how to structure a Rust canister for comprehensive unit testing by isolating +non-deterministic dependencies behind interfaces. It uses dependency injection so that inter-canister +calls and stable memory operations can all be mocked in fast pure-Rust unit tests. ## Architecture @@ -11,9 +12,8 @@ The canister uses a dependency injection pattern that avoids complex generics th ```rust pub struct CanisterApi { - pub governance: Box, - pub storage: Box, - // other dependencies... + governance: Arc, + counter: Arc, } ``` @@ -42,18 +42,16 @@ fn complex_function(api: &CanisterApi) -> Result { Non-deterministic operations are abstracted behind traits: -- **Inter-canister calls** → `GovernanceApiTrait` -- **Stable memory operations** → `StorageApiTrait` -- **Time-based operations** → `TimeApiTrait` +- **Inter-canister calls** → `GovernanceApi` +- **Stable memory operations** → `Counter` (backed by `StableMemoryCounter`) **Benefit**: The entire dependency tree can be mocked, allowing you to test all canister logic in pure Rust unit tests without any IC integration. -Technically Stable Memory can be fully test in Rust, but in cases where more complex logic is needed to update the -contents -of stable memory in a way that works for tests, you can simplify your testing by putting it behind an interface that -abstracts away the actual storage implementation. This makes it easier to evolve your storage layer without -needing to update tests. +Technically stable memory can be fully tested in Rust, but in cases where more complex logic is needed to update the +contents of stable memory in a way that works for tests, you can simplify your testing by putting it behind an +interface that abstracts away the actual storage implementation. This makes it easier to evolve your storage layer +without needing to update tests. ## Testing Strategy @@ -63,32 +61,32 @@ Unit tests run in milliseconds and can test complex business logic by mocking al ```rust #[test] -fn test_complex_governance_logic() { - let mut mock_governance = MockGovernanceApi::new(); - mock_governance.expect_get_proposal_info() - .returning(|_| Ok(mock_proposal())); +fn test_counter_endpoints() { + let governance = Arc::new(MockGovernanceApi::new()); + let counter = Arc::new(TestCounter::new()); + let api = CanisterApi::new(governance, counter); - let api = CanisterApi::new_with_mocks(mock_governance, /* other mocks */); + let response = api.get_count(); + assert_eq!(response.count, Some(0)); - // Test complex logic without any IC integration - let result = complex_function(&api); - assert_eq!(result, expected_result); + let response = api.increment_count(); + assert_eq!(response.new_count, Some(1)); } ``` ### Integration Tests (Slower, End-to-End) -Integration tests use PocketIC to verify the complete system works together: +Integration tests use PocketIC to verify the complete system works together, including actual +inter-canister calls to a locally deployed NNS Governance canister: ```rust #[test] -fn test_end_to_end_workflow() { - let pic = PocketIc::new(); - let canister_id = deploy_canister(&pic); +fn test_counter_functionality() { + let pic = PocketIcBuilder::new().with_nns_subnet().build(); + let canister_id = deploy_backend_canister(&pic); - // Test actual inter-canister calls - let response = pic.update_call(canister_id, "method", args); - // assertions... + let response: GetCountResponse = query(&pic, canister_id, "get_count", encode_one(GetCountRequest {}).unwrap()); + assert_eq!(response.count, Some(0)); } ``` @@ -102,7 +100,7 @@ verify system integration. ## Keeping Up With Mainnet Canister Changes -Additionally, in the PocketIC tests, we rely on setting up Governance proposals via init arguments. That capability +In the PocketIC integration tests, we rely on setting up Governance proposals via init arguments. That capability could be removed in the future, as it's not part of the stable interface of the canister. In that case, mocking out canisters would become harder, as you would need to also create a ledger and neurons and proposals. This setup can be error-prone, and would need to be kept in sync with mainnet. @@ -116,17 +114,20 @@ minimal testing. ## Project Structure ``` -src/ -├── lib.rs # Canister entry points and initialization -├── canister_api.rs # Main API struct and dependency injection -├── counter.rs # Counter trait and implementation (abstraction over storage) -├── governance.rs # NNS Governance trait and implementations -├── stable_memory.rs # Storage operations and trait definitions -├── types/ -│ ├── mod.rs # Request/response types and external canister types -│ └── nns_governance.rs # NNS Governance canister type definitions, generated from governance candid. -└── tests/ - └── integration_tests.rs # Slower end-to-end tests +backend/ +├── Cargo.toml +├── backend.did # Candid interface +└── src/ + ├── lib.rs # Canister entry points and initialization + ├── canister_api.rs # Main API struct and dependency injection + ├── counter.rs # Counter trait and implementation (abstraction over storage) + ├── governance.rs # NNS Governance trait and implementations + ├── stable_memory.rs # Storage operations and trait definitions + └── types/ + ├── mod.rs # Request/response types + └── nns_governance.rs # NNS Governance canister type definitions + tests/ + └── integration_tests.rs # Slower end-to-end tests using PocketIC ``` ## Type Generation @@ -146,26 +147,38 @@ For automatic type generation from Candid files, see the `candid-type-generation 4. **Easy Debugging**: Unit tests can isolate specific scenarios without IC complexity 5. **Maintainable Code**: Clear separation between business logic and IC integration -## Running Tests +## Build and deploy from the command line + +### Prerequisites +- [icp-cli](https://cli.internetcomputer.org): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Rust toolchain with `wasm32-unknown-unknown` target + +### Install + +```bash +git clone https://github.com/dfinity/examples +cd examples/rust/unit_testable_rust_canister +``` + +### Run unit and integration tests ```bash # Fast unit tests (recommended for development) cargo test --lib -# All tests (including integration tests) +# All tests including PocketIC integration tests cargo test ``` -The unit tests demonstrate testing the same functionality as integration tests but with significantly better performance -and easier setup. - -## Deployment - -To deploy locally, but without NNS governance canister. +### Deploy and test ```bash -dfx start --background -dfx create canister hello_canister -dfx deploy +icp network start -d +icp deploy +make test +icp network stop +``` + +## Security considerations and best practices -``` \ No newline at end of file +For information on security best practices when developing on ICP, see the [security overview](https://docs.internetcomputer.org/guides/security/overview). diff --git a/rust/unit_testable_rust_canister/src/hello_canister/Cargo.toml b/rust/unit_testable_rust_canister/backend/Cargo.toml similarity index 94% rename from rust/unit_testable_rust_canister/src/hello_canister/Cargo.toml rename to rust/unit_testable_rust_canister/backend/Cargo.toml index 0d295ad278..a7c9a81264 100644 --- a/rust/unit_testable_rust_canister/src/hello_canister/Cargo.toml +++ b/rust/unit_testable_rust_canister/backend/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_canister" +name = "backend" version = "0.1.0" edition = "2021" diff --git a/rust/unit_testable_rust_canister/src/hello_canister/hello_canister.did b/rust/unit_testable_rust_canister/backend/backend.did similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/hello_canister.did rename to rust/unit_testable_rust_canister/backend/backend.did diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/canister_api.rs b/rust/unit_testable_rust_canister/backend/src/canister_api.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/canister_api.rs rename to rust/unit_testable_rust_canister/backend/src/canister_api.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/counter.rs b/rust/unit_testable_rust_canister/backend/src/counter.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/counter.rs rename to rust/unit_testable_rust_canister/backend/src/counter.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/governance.rs b/rust/unit_testable_rust_canister/backend/src/governance.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/governance.rs rename to rust/unit_testable_rust_canister/backend/src/governance.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/lib.rs b/rust/unit_testable_rust_canister/backend/src/lib.rs similarity index 99% rename from rust/unit_testable_rust_canister/src/hello_canister/src/lib.rs rename to rust/unit_testable_rust_canister/backend/src/lib.rs index b50aadfc2c..6c51b80874 100644 --- a/rust/unit_testable_rust_canister/src/hello_canister/src/lib.rs +++ b/rust/unit_testable_rust_canister/backend/src/lib.rs @@ -78,7 +78,7 @@ mod tests { // Get the directory where this crate's Cargo.toml is located let manifest_dir = env::var("CARGO_MANIFEST_DIR") .expect("CARGO_MANIFEST_DIR environment variable not set"); - let candid_file_path = PathBuf::from(&manifest_dir).join("hello_canister.did"); + let candid_file_path = PathBuf::from(&manifest_dir).join("backend.did"); // Read the declared interface from the .did file let declared_interface_str = diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/stable_memory.rs b/rust/unit_testable_rust_canister/backend/src/stable_memory.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/stable_memory.rs rename to rust/unit_testable_rust_canister/backend/src/stable_memory.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/types/mod.rs b/rust/unit_testable_rust_canister/backend/src/types/mod.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/types/mod.rs rename to rust/unit_testable_rust_canister/backend/src/types/mod.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/src/types/nns_governance.rs b/rust/unit_testable_rust_canister/backend/src/types/nns_governance.rs similarity index 100% rename from rust/unit_testable_rust_canister/src/hello_canister/src/types/nns_governance.rs rename to rust/unit_testable_rust_canister/backend/src/types/nns_governance.rs diff --git a/rust/unit_testable_rust_canister/src/hello_canister/tests/integration_tests.rs b/rust/unit_testable_rust_canister/backend/tests/integration_tests.rs similarity index 96% rename from rust/unit_testable_rust_canister/src/hello_canister/tests/integration_tests.rs rename to rust/unit_testable_rust_canister/backend/tests/integration_tests.rs index b1e93b15f4..49acc7d459 100644 --- a/rust/unit_testable_rust_canister/src/hello_canister/tests/integration_tests.rs +++ b/rust/unit_testable_rust_canister/backend/tests/integration_tests.rs @@ -8,10 +8,10 @@ use std::time::SystemTime; use walkdir::WalkDir; // Import all request/response types from the library -use hello_canister::types::nns_governance::{ +use backend::types::nns_governance::{ Followees, Governance, NetworkEconomics, NeuronId, Proposal, ProposalData, ProposalId, }; -use hello_canister::types::*; +use backend::types::*; // IC commit used for downloading official NNS WASM files // This should match what's currently deployed in production @@ -22,7 +22,7 @@ const NNS_GOVERNANCE_CANISTER_ID: &str = "rrkah-fqaaa-aaaaa-aaaaq-cai"; const NNS_ROOT_CANISTER_ID: &str = "r7inp-6aaaa-aaaaa-aaabq-cai"; // WASM will be loaded dynamically with smart rebuilding -fn get_hello_canister_wasm() -> Vec { +fn get_backend_wasm() -> Vec { let wasm_path = ensure_wasm_built(); std::fs::read(&wasm_path) .unwrap_or_else(|e| panic!("Failed to read WASM file at {:?}: {}", wasm_path, e)) @@ -30,8 +30,7 @@ fn get_hello_canister_wasm() -> Vec { /// Ensures the WASM is built and up-to-date, returns path to the WASM file fn ensure_wasm_built() -> PathBuf { - let wasm_path = - PathBuf::from("../../target/wasm32-unknown-unknown/release/hello_canister.wasm"); + let wasm_path = PathBuf::from("../../target/wasm32-unknown-unknown/release/backend.wasm"); let src_dir = Path::new("src"); let cargo_toml = Path::new("Cargo.toml"); @@ -203,7 +202,7 @@ fn setup_nns_governance(pic: &PocketIc) -> Principal { #[test] fn test_counter_functionality() { let pic = setup_pocket_ic(); - let canister_id = deploy_hello_canister(&pic); + let canister_id = deploy_backend_canister(&pic); // Initial counter should be 0 let request = GetCountRequest {}; @@ -243,7 +242,7 @@ fn test_counter_functionality() { query(&pic, canister_id, "get_count", encode_one(request).unwrap()); assert_eq!(response.count, Some(2)); - // Increment counter + // Decrement counter let request = IncrementCountRequest {}; let response: IncrementCountResponse = update( &pic, @@ -264,7 +263,7 @@ fn test_get_proposal_titles() { let pic = setup_pocket_ic(); setup_nns_governance(&pic); - let canister_id = deploy_hello_canister(&pic); + let canister_id = deploy_backend_canister(&pic); // Test listing proposals (should return mock data) let request = GetProposalTitlesRequest { limit: None }; @@ -310,7 +309,7 @@ fn test_get_proposal_titles() { #[test] fn test_get_proposal_info() { let pic = setup_pocket_ic(); - let canister_id = deploy_hello_canister(&pic); + let canister_id = deploy_backend_canister(&pic); setup_nns_governance(&pic); // Test with a proposal ID @@ -352,12 +351,12 @@ fn setup_pocket_ic() -> PocketIc { .build() } -fn deploy_hello_canister(pic: &PocketIc) -> Principal { +fn deploy_backend_canister(pic: &PocketIc) -> Principal { let canister_id = pic.create_canister(); pic.add_cycles(canister_id, 2_000_000_000_000); // Use smart WASM rebuilding - will only rebuild if source files changed - let wasm_binary = get_hello_canister_wasm(); + let wasm_binary = get_backend_wasm(); pic.install_canister(canister_id, wasm_binary, vec![], None); @@ -384,7 +383,7 @@ fn update Deserialize<'de>>( } } -/// Generic query call helper +/// Generic query call helper fn query Deserialize<'de>>( pic: &PocketIc, canister_id: Principal, diff --git a/rust/unit_testable_rust_canister/dfx.json b/rust/unit_testable_rust_canister/dfx.json deleted file mode 100644 index 3e377c04f0..0000000000 --- a/rust/unit_testable_rust_canister/dfx.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": 1, - "canisters": { - "hello_canister": { - "type": "rust", - "package": "hello_canister", - "candid": "src/hello_canister/hello_canister.did" - } - }, - "networks": { - "local": { - "bind": "127.0.0.1:4943", - "type": "ephemeral" - } - } -} diff --git a/rust/unit_testable_rust_canister/icp.yaml b/rust/unit_testable_rust_canister/icp.yaml new file mode 100644 index 0000000000..316aa4e9f2 --- /dev/null +++ b/rust/unit_testable_rust_canister/icp.yaml @@ -0,0 +1,9 @@ +networks: + - name: local + mode: managed +canisters: + - name: backend + recipe: + type: "@dfinity/rust@v3.3.0" + configuration: + package: backend