Skip to content

Support pocket-ic v9#317

Merged
ZenVoich merged 8 commits into
mainfrom
new-pocket-ic
Sep 8, 2025
Merged

Support pocket-ic v9#317
ZenVoich merged 8 commits into
mainfrom
new-pocket-ic

Conversation

@ZenVoich

@ZenVoich ZenVoich commented Sep 7, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Support for PocketIC 9.x and 4.0.0 runtimes with automatic selection and unified deployment flow.
  • Refactor

    • Replaced DFINITY SDK imports with ICP SDK Core equivalents across CLI and declaration artifacts; CLI and public APIs remain compatible.
  • Chores

    • Updated dependencies (added @icp-sdk/core, bumped pic-js-mops, TypeScript update), adjusted tsconfig, and added pocket-ic 9.0.3 to toolchain.
  • Tests

    • Enabled and expanded storage actor tests and added a new Motoko test actor with runTests.

@coderabbitai

coderabbitai Bot commented Sep 7, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Swaps many @dfinity/* imports for @icp-sdk/core/*, updates CLI dependencies and tsconfig, adds runtime branching to support pocket‑ic 9.x (Mops) alongside 4.0.0 in Replica/BenchReplica, enables previously commented Motoko Mo tests, and updates toolchain pocket‑ic version.

Changes

Cohort / File(s) Summary
Core SDK import migration (CLI)
cli/api/actors.ts, cli/api/downloadPackageFiles.ts, cli/commands/maintainer.ts, cli/commands/owner.ts, cli/commands/test/test.ts, cli/mops.ts, cli/pem.ts
Replace imports from @dfinity/* with @icp-sdk/core/* equivalents (Actor/Agent/Identity/Principal/ActorMethod). No functional or signature changes.
Replica / BenchReplica pocket‑ic dual support
cli/commands/replica.ts, cli/commands/bench-replica.ts
Introduce runtime branching for pocket‑ic v9.x (pic‑js‑mops) and v4.0.0: version guard, alternate server/create calls, widened pocketIcServer/pocketIc field types, and unified setupCanister invocation via a compatible alias; external interfaces preserved.
Declarations migration (.d.ts / .js)
cli/declarations/bench/*, cli/declarations/main/*, cli/declarations/storage/*, cli/declarations/*/*.did.d.ts, cli/declarations/*/index.js
Update type and runtime imports and JSDoc from @dfinity/* to @icp-sdk/core/*. Signatures and runtime behavior unchanged; only type/import sources and JSDoc updated.
Dependencies
cli/package.json
Remove several @dfinity/* deps; add @icp-sdk/core@4.0.2; update pic-js-mops to 0.14.8; bump TypeScript devDependency.
TypeScript config
cli/tsconfig.json
Change compilerOptions: moduleES2022, moduleResolutionbundler.
Toolchain config
mops.toml
Add toolchain.pocket-ic = "9.0.3"; minor whitespace/newline churn on wasmtime entry.
Tests (Mo)
test/storage-actor.test.mo, test/storage-actor2.test.mo
Un-comment/enable Motoko Mo tests, add runTests actor in storage-actor2.test.mo, enable upload/download/upgrade test flows and assertions, add debug prints.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User
  participant Replica as CLI Replica
  participant PIC9 as pocket-ic 9.x (Mops)
  participant PIC4 as pocket-ic 4.0.0

  User->>Replica: start(version)
  alt version == 9.x.x
    Replica->>PIC9: PocketIcServerMops.start()
    Replica->>PIC9: PocketIcMops.create()
    Replica-->>User: ready (9.x)
  else version == 4.0.0
    Replica->>PIC4: PocketIcServer.start()
    Replica->>PIC4: PocketIc.create()
    Replica-->>User: ready (4.0.0)
  else unsupported
    Replica-->>User: error (unsupported version)
  end
Loading
sequenceDiagram
  autonumber
  participant Replica as CLI Replica
  participant PIC as pocket-ic (4.0.0 | 9.x)
  participant Agent as HttpAgent/Actor
  participant Canister as Deployed Canister

  Replica->>PIC: setupCanister({ wasm, idlFactory })  %% unified call
  PIC-->>Replica: { canisterId }
  Replica->>Agent: Actor.createActor(idlFactory, agent, canisterId)
  Agent-->>Replica: actor
  Replica->>Canister: attach logs & post-setup
  Replica-->>User: { canisterId, actor }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • Replica test runner #246 — Related replica startup/deploy modifications and pocket‑ic integration; overlaps runtime branching changes.
  • Verify build #249 — Touches the same cli/commands/replica.ts implementation; related to Replica behavior.
  • pocket-ic from dfx #259 — Overlaps CLI replica/bench/test changes and import migrations to ICP SDK core.

Poem

I hop through imports, light and fleet,
From dfinity lanes to icp’s neat street.
Two pockets now — I pick by version’s song,
Deploy and test — the code hums along.
Bun‑bun cheers: the CI’s moving strong. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-pocket-ic

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
cli/commands/owner.ts (1)

28-31: Validate principal input to avoid uncaught parse errors.
Wrap Principal.fromText(...) to give a friendly error instead of throwing.

Apply this diff in both addOwner and removeOwner:

-  let principal = Principal.fromText(owner);
+  let principal: Principal;
+  try {
+    principal = Principal.fromText(owner);
+  } catch {
+    console.error(chalk.red('Error: ') + `Invalid principal: ${owner}`);
+    process.exit(1);
+  }

Also applies to: 70-73

cli/api/downloadPackageFiles.ts (1)

67-75: Avoid O(n²) array copies when concatenating chunks.
Spreading into a new array on each iteration repeatedly copies data.

Apply:

-  let data : Array<number> = [];
+  let data: number[] = [];
   for (let i = 0n; i < fileMeta.chunkCount; i++) {
     let chunkRes = await storage.downloadChunk(fileId, i);
     if ('err' in chunkRes) {
       throw chunkRes.err;
     }
     let chunk = chunkRes.ok;
-    data = [...data, ...chunk];
+    data.push(...chunk);
   }
cli/declarations/bench/index.js (1)

15-16: Replace new HttpAgent with await HttpAgent.create in createActor
The HttpAgent constructor is deprecated in v4.x; switch to the async factory.

--- a/cli/declarations/bench/index.js
+++ b/cli/declarations/bench/index.js
@@ -1,6 +1,7 @@
-export const createActor = (canisterId, options = {}) => {
-  const agent = options.agent || new HttpAgent({ ...options.agentOptions });
+export const createActor = async (canisterId, options = {}) => {
+  const agent = options.agent || await HttpAgent.create({ ...options.agentOptions });
   const actor = Actor.createActor(idlFactory, {
     agent,
     canisterId,
@@
-};
+};

Update all call sites of createActor to handle its returned Promise.

cli/commands/test/test.ts (1)

58-65: Don’t swallow replica.start() rejections.
Wrapping in a Promise without catch can leave replicaStartPromise stuck pending on failure.

 async function startReplicaOnce(replica : Replica, type : ReplicaName) {
   if (!replicaStartPromise) {
-    replicaStartPromise = new Promise((resolve) => {
-      replica.start({type, silent: true}).then(resolve);
-    });
+    replicaStartPromise = replica
+      .start({type, silent: true})
+      .catch((e) => {
+        replicaStartPromise = undefined;
+        throw e;
+      });
   }
   return replicaStartPromise;
 }
cli/api/actors.ts (1)

21-28: Remove unsupported shouldSyncTime option from HttpAgent.create call.
The shouldSyncTime field isn’t defined in @icp-sdk/core v4.x’s HttpAgentOptions; retain only valid options like shouldFetchRootKey and verifyQuerySignatures.

cli/commands/maintainer.ts (2)

28-31: Validate and fail fast on invalid Principal text.

Wrap Principal.fromText in try/catch to provide a clear error and non-zero exit.

-	let principal = Principal.fromText(maintainer);
+	let principal: Principal;
+	try {
+		principal = Principal.fromText(maintainer);
+	} catch {
+		console.error(chalk.red('Invalid principal: ') + maintainer);
+		process.exit(1);
+	}

70-73: Do the same validation in removeMaintainer.

Mirror the error handling for symmetry.

-	let principal = Principal.fromText(maintainer);
+	let principal: Principal;
+	try {
+		principal = Principal.fromText(maintainer);
+	} catch {
+		console.error(chalk.red('Invalid principal: ') + maintainer);
+		process.exit(1);
+	}
cli/declarations/storage/index.d.ts (1)

15-28: Keep JS and d.ts in sync: agent?: Agent must be honored at runtime

The declaration allows passing a pre-configured agent?: Agent, but storage/index.js ignores it and always constructs a new HttpAgent. Please update the JS to respect options.agent (and warn if both agent and agentOptions are provided), mirroring main/index.js.

cli/declarations/storage/index.js (1)

13-15: Honor provided Agent; warn on mixed options

Constructing a new HttpAgent unconditionally breaks callers passing a custom Agent/identity.

-export const createActor = (canisterId, options) => {
-  const agent = new HttpAgent({ ...options?.agentOptions });
+export const createActor = (canisterId, options = {}) => {
+  const agent = options.agent || new HttpAgent({ ...options.agentOptions });
+  if (options.agent && options.agentOptions) {
+    console.warn(
+      "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent."
+    );
+  }
🧹 Nitpick comments (9)
cli/commands/owner.ts (1)

16-17: Guard against empty package name before calling the backend.
Right now '' may be sent to canister methods if name is missing.

Apply this pattern in printOwners/addOwner/removeOwner before actor calls:

-  let owners = await actor.getPackageOwners(config.package?.name || '');
+  const pkg = config.package?.name;
+  if (!pkg) {
+    console.error(chalk.red('Error: ') + `Package name is missing in mops.toml`);
+    process.exit(1);
+  }
+  let owners = await actor.getPackageOwners(pkg);

And similarly replace occurrences in addOwner/removeOwner:

-  let res = await actor.addOwner(config.package?.name || '', principal);
+  let res = await actor.addOwner(pkg, principal);
-  let res = await actor.removeOwner(config.package?.name || '', principal);
+  let res = await actor.removeOwner(pkg, principal);

Also applies to: 54-55, 96-97

cli/mops.ts (1)

49-58: ESM-safe __dirname fallback.
In ESM, __dirname is undefined; while the try branch should usually succeed, make the fallback ESM-safe to avoid surprises.

-  catch {
-    networkFile = path.join(__dirname, 'network.txt');
-  }
+  catch {
+    const {fileURLToPath} = await import('node:url');
+    const dirname = path.dirname(fileURLToPath(import.meta.url));
+    networkFile = path.join(dirname, 'network.txt');
+  }
cli/commands/replica.ts (3)

91-93: Use semver for the version guard and echo the offending version.

String checks can misfire (e.g., “9” or “^9.0.3”). Prefer semver.

-			if (config.toolchain?.['pocket-ic'] !== '4.0.0' && !config.toolchain?.['pocket-ic']?.startsWith('9.')) {
-				console.error('Current Mops CLI only supports pocket-ic 9.x.x and 4.0.0');
+			const picVer = config.toolchain?.['pocket-ic'] ?? '';
+			if (! (semver.eq(picVer, '4.0.0') || semver.satisfies(picVer, '>=9.0.0 <10'))) {
+				console.error(`Current Mops CLI only supports pocket-ic 9.x.x and 4.0.0; found "${picVer}"`);
 				process.exit(1);
 			}

Add this import at the top of the file:

import semver from 'semver';

96-115: Tie PIC runtime logs to the verbose flag.

Make logs visible only when requested.

-				this.pocketIcServer = await PocketIcServerMops.start({
-					showRuntimeLogs: false,
-					showCanisterLogs: false,
+				this.pocketIcServer = await PocketIcServerMops.start({
+					showRuntimeLogs: this.verbose,
+					showCanisterLogs: this.verbose,
 					binPath: pocketIcBin,
 					ttl: this.ttl,
 				});
@@
-				this.pocketIcServer = await PocketIcServer.start({
-					showRuntimeLogs: false,
-					showCanisterLogs: false,
+				this.pocketIcServer = await PocketIcServer.start({
+					showRuntimeLogs: this.verbose,
+					showCanisterLogs: this.verbose,
 					binPath: pocketIcBin,
 					ttl: this.ttl,
 				});

241-245: Avoid intersecting function types for setupCanister cast.

Use an explicit, minimal signature for stability across libs.

-			type PocketIcSetupCanister = PocketIcMops['setupCanister'] & PocketIc['setupCanister'];
-			let {canisterId, actor} = await (this.pocketIc.setupCanister as PocketIcSetupCanister)({
-				wasm,
-				idlFactory,
-			});
+			type SetupCanisterFn = (args: { wasm: string; idlFactory: IDL.InterfaceFactory }) =>
+				Promise<{ canisterId: { toText(): string } | string; actor: unknown }>;
+			const setup = this.pocketIc.setupCanister as unknown as SetupCanisterFn;
+			const { canisterId, actor } = await setup({ wasm, idlFactory });
cli/declarations/main/index.js (1)

24-33: Unify root-key fetch condition across declarations

Here you gate on DFX_NETWORK !== "ic", while storage/index.js uses NODE_ENV !== "production". Recommend standardizing on one (prefer DFX_NETWORK like dfx scaffolding) to avoid surprise root-key fetches.

-  if (process.env.DFX_NETWORK !== "ic") {
+  if (process.env.DFX_NETWORK !== "ic") {
     agent.fetchRootKey().catch((err) => {
       console.warn(
         "Unable to fetch root key. Check to ensure that your local replica is running"
       );
       console.error(err);
     });
   }
cli/declarations/storage/index.d.ts (1)

30-40: Doc typos and naming

Fix minor typos for polish and IDE hints:

  • “Intializes” → “Initializes”
  • “ActorSubClass” → “ActorSubclass”
  • “Intialized” → “Initialized”
  • “Supercedes” → “Supersedes”
- * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
- * @constructs {@link ActorSubClass}
+ * Initializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
+ * @constructs {@link ActorSubclass}
- * Intialized Actor using default settings, ready to talk to a canister using its candid interface
+ * Initialized Actor using default settings, ready to talk to a canister using its candid interface
- * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions
+ * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supersedes agentOptions
cli/declarations/main/index.d.ts (1)

30-40: Doc typos and naming

Same nitpicks as storage d.ts: “Intializes” → “Initializes”, “ActorSubClass” → “ActorSubclass”, “Supercedes” → “Supersedes”.

- * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
- * @constructs {@link ActorSubClass}
+ * Initializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister.
+ * @constructs {@link ActorSubclass}
- * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions
+ * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supersedes agentOptions
cli/declarations/storage/index.js (1)

16-22: Standardize dev root-key fetch condition

Use the same condition as main/index.js (DFX_NETWORK !== "ic") to avoid fetching root key on non-local setups when NODE_ENV=development.

-  if(process.env.NODE_ENV !== "production") {
+  if (process.env.DFX_NETWORK !== "ic") {
     agent.fetchRootKey().catch(err=>{
       console.warn("Unable to fetch root key. Check to ensure that your local replica is running");
       console.error(err);
     });
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3be72e4 and c0a553a.

⛔ Files ignored due to path filters (2)
  • cli/bun.lock is excluded by !**/*.lock
  • cli/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • cli/api/actors.ts (1 hunks)
  • cli/api/downloadPackageFiles.ts (1 hunks)
  • cli/commands/maintainer.ts (1 hunks)
  • cli/commands/owner.ts (1 hunks)
  • cli/commands/replica.ts (4 hunks)
  • cli/commands/test/test.ts (1 hunks)
  • cli/declarations/bench/bench.did.d.ts (1 hunks)
  • cli/declarations/bench/index.d.ts (1 hunks)
  • cli/declarations/bench/index.js (1 hunks)
  • cli/declarations/main/index.d.ts (1 hunks)
  • cli/declarations/main/index.js (1 hunks)
  • cli/declarations/main/main.did.d.ts (1 hunks)
  • cli/declarations/storage/index.d.ts (1 hunks)
  • cli/declarations/storage/index.js (1 hunks)
  • cli/declarations/storage/storage.did.d.ts (1 hunks)
  • cli/mops.ts (1 hunks)
  • cli/package.json (2 hunks)
  • cli/pem.ts (1 hunks)
  • cli/tsconfig.json (1 hunks)
  • mops.toml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: node 20, moc 0.14.13, ic-mops@1.0.0
🔇 Additional comments (24)
cli/commands/owner.ts (1)

5-5: Import migration looks good.
Principal import aligned with the repo-wide switch to @icp-sdk/core.

cli/tsconfig.json (1)

11-12: Module settings changed; confirm toolchain compatibility.
Switching to "module": "ES2022" and "moduleResolution": "bundler" can affect Node/ts-node and bundlers. Verify builds and runtime ESM behavior across CLI.

Run:

#!/bin/bash
set -euo pipefail
# Compile CLI with the updated tsconfig
tsc -p cli/tsconfig.json --noEmit
# Grep for CommonJS-only globals (__dirname/__filename) in ESM files
rg -nP '(__dirname|__filename)\b' cli --type=ts --type=js -g '!**/dist/**' -g '!**/bundle/**'
cli/api/downloadPackageFiles.ts (1)

1-1: Import migration looks good.
Principal import aligned with @icp-sdk/core.

cli/mops.ts (1)

4-4: Import migration looks good.
Identity import updated to @icp-sdk/core/agent.

cli/declarations/bench/index.js (1)

1-1: Import migration looks good.
Actor/HttpAgent imported from @icp-sdk/core/agent.

mops.toml (1)

21-22: Pocket-IC toolchain pinned.
Adding pocket-ic = "9.0.3" looks correct for PIC 9 support; wasmtime is already >=14.

cli/commands/test/test.ts (1)

13-13: Import migration looks good.
ActorMethod type now sourced from @icp-sdk/core/agent.

cli/api/actors.ts (1)

1-2: Import migration looks good.
Agent/Actor/Principal imports updated to @icp-sdk/core paths.

cli/commands/maintainer.ts (1)

5-5: SDK import migration looks good.

Principal import path change is correct and non-breaking.

cli/package.json (1)

48-49: Deps switch to @icp-sdk/core and pic-js-mops looks right; verify no stray @dfinity imports remain.

#!/bin/bash
# Ensure no remaining DFINITY imports in CLI sources
rg -nP '@dfinity/' -g 'cli/**' -g '!**/node_modules/**' || echo 'OK: no @dfinity imports in cli/'

# Sanity: confirm both PIC libs are referenced only in replica.ts
rg -nP '\bfrom\s+[\'"](pic-ic|pic-js-mops)[\'"]' -n cli/commands/replica.ts

# Confirm pinned ICP SDK version in package.json
jq -r '.dependencies["@icp-sdk/core"]' cli/package.json

Also applies to: 80-81

cli/pem.ts (1)

4-5: Identity import migration LGTM.

Compatible replacements for Ed25519KeyIdentity and Secp256k1KeyIdentity.

cli/declarations/storage/storage.did.d.ts (1)

1-3: Type import source updates are correct.

No surface/type changes beyond module paths.

cli/declarations/bench/bench.did.d.ts (1)

1-3: Type import migration looks good.

cli/declarations/bench/index.d.ts (1)

6-8: Agent/Principal/IDL type imports correctly moved to ICP SDK core.

cli/commands/replica.ts (3)

8-12: ICP SDK core + dual PIC imports look correct.

Import set aligns with the new runtime support.


29-31: Union-typing PIC server/client fields is fine.

Keeps the rest of the class unchanged at call sites.


251-251: ```shell

Search for addCycles method definitions across TypeScript files

rg -nP --type=ts 'addCycles(' -C3

Locate pocketIc instantiation or type import

rg -nP --type=ts 'pocketIc' -C3


</blockquote></details>
<details>
<summary>cli/declarations/main/main.did.d.ts (1)</summary><blockquote>

`1-3`: **Import path migration complete; IDL API parity confirmed**  
The `@icp-sdk/core/candid` package re-exports the Candid `IDL` namespace—including `IDL.InterfaceFactory`, `IDL.Type[]`, and `typeof IDL`—matching the previous API surface.

</blockquote></details>
<details>
<summary>cli/declarations/main/index.js (2)</summary><blockquote>

`1-1`: **LGTM: moved imports to ICP SDK Core**

Import path update to `@icp-sdk/core/agent` is correct; no behavior change.

---

`34-41`: **Type/runtime mismatch: `main` export declared in d.ts but not here**

`cli/declarations/main/index.d.ts` declares `export const main: ActorSubclass<_SERVICE>;` but this file does not export `main`. Either export it or remove from d.ts. If you want to keep it, append:

```diff
   return Actor.createActor(idlFactory, {
     agent,
     canisterId,
     ...options.actorOptions,
   });
 };
+
+// Optional: keep in sync with index.d.ts
+export const main = createActor(canisterId);

Run to find any imports relying on main:

#!/bin/bash
rg -nP -C2 'import\s+\{[^}]*\bmain\b[^}]*\}\s+from\s+["\'][^"\']*declarations/(main|main/index)["\']'
cli/declarations/storage/index.d.ts (1)

6-9: LGTM: type imports migrated to ICP SDK Core

The new import sources line up with ICP SDK Core typings.

cli/declarations/main/index.d.ts (2)

1-8: LGTM: migrated to ICP SDK Core type imports

Types point to @icp-sdk/core/*; signatures unchanged.


46-50: Type/runtime mismatch: main declared here but not exported in JS

index.js doesn’t export main. Either export it in JS or drop it here to prevent runtime import errors.

#!/bin/bash
# Confirm mismatch
rg -n 'export\s+const\s+main\b' cli/declarations/main/index.js
cli/declarations/storage/index.js (1)

1-6: Missing exports vs d.ts: canisterId and storage

cli/declarations/storage/index.d.ts declares canisterId and storage, but this file doesn’t export them. Export to match types (or adjust d.ts).

 import { idlFactory } from './storage.did.js';
 export { idlFactory } from './storage.did.js';
 
+/* CANISTER_ID is replaced by bundlers based on environment (dfx ≥0.15 standardizes naming) */
+export const canisterId = process.env.CANISTER_ID_STORAGE;
+
 /** 
  *
  * @param {string | import("@icp-sdk/core/principal").Principal} canisterId Canister ID of Agent
@@
   return Actor.createActor(idlFactory, {
     agent,
     canisterId,
     ...options?.actorOptions,
   });
 };
+
+// Keep JS in sync with d.ts
+export const storage = createActor(canisterId);

To see if anything imports these today:

#!/bin/bash
rg -nP -C2 'import\s+\{[^}]*\b(storage|canisterId)\b[^}]*\}\s+from\s+["\'][^"\']*declarations/storage' 

Comment on lines 7 to 12
/**
*
* @param {string | import("@dfinity/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@dfinity/agent").HttpAgentOptions; actorOptions?: import("@dfinity/agent").ActorConfig}} [options]
* @return {import("@dfinity/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
* @param {string | import("@icp-sdk/core/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@icp-sdk/core/agent").HttpAgentOptions; actorOptions?: import("@icp-sdk/core/agent").ActorConfig}} [options]
* @return {import("@icp-sdk/core/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

JSDoc and runtime options out of sync with d.ts

The JSDoc omits options.agent and the implementation ignores it. Align with d.ts and main/index.js: accept agent, warn if agentOptions is also provided, and use the provided agent.

- * @param {{agentOptions?: import("@icp-sdk/core/agent").HttpAgentOptions; actorOptions?: import("@icp-sdk/core/agent").ActorConfig}} [options]
- * @return {import("@icp-sdk/core/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
+ * @param {{agent?: import("@icp-sdk/core/agent").Agent; agentOptions?: import("@icp-sdk/core/agent").HttpAgentOptions; actorOptions?: import("@icp-sdk/core/agent").ActorConfig}} [options]
+ * @return {import("@icp-sdk/core/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
*
* @param {string | import("@dfinity/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@dfinity/agent").HttpAgentOptions; actorOptions?: import("@dfinity/agent").ActorConfig}} [options]
* @return {import("@dfinity/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
* @param {string | import("@icp-sdk/core/principal").Principal} canisterId Canister ID of Agent
* @param {{agentOptions?: import("@icp-sdk/core/agent").HttpAgentOptions; actorOptions?: import("@icp-sdk/core/agent").ActorConfig}} [options]
* @return {import("@icp-sdk/core/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
*/
/**
*
* @param {string | import("@icp-sdk/core/principal").Principal} canisterId Canister ID of Agent
* @param {{agent?: import("@icp-sdk/core/agent").Agent; agentOptions?: import("@icp-sdk/core/agent").HttpAgentOptions; actorOptions?: import("@icp-sdk/core/agent").ActorConfig}} [options]
* @return {import("@icp-sdk/core/agent").ActorSubclass<import("./storage.did.js")._SERVICE>}
*/
🤖 Prompt for AI Agents
In cli/declarations/storage/index.js around lines 7 to 12, the JSDoc and
implementation currently omit support for an `agent` option and ignore it at
runtime; update the JSDoc to include options.agent (typed to
import("@icp-sdk/core/agent").HttpAgent or similar) and modify the factory
implementation to accept an `agent` parameter: if `agent` is provided, use it to
create the actor; if both `agent` and `agentOptions` are provided, emit a
console.warn indicating `agent` takes precedence (for backward compatibility),
and only fall back to constructing a new agent from `agentOptions` when `agent`
is not supplied. Ensure types in JSDoc match the .d.ts and main/index.js
behavior and keep the existing actorOptions handling unchanged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/commands/replica.ts (1)

241-262: Remove unsafe any-cast, normalize canisterId, and pass BigInt cycles

addCycles typically requires bigint (and in legacy pic-ic a Principal). Normalize ID once and avoid any-cast.

-      type PocketIcSetupCanister = PocketIcMops['setupCanister'] & PocketIc['setupCanister'];
-      let {canisterId, actor} = await (this.pocketIc.setupCanister as PocketIcSetupCanister)({
-        wasm,
-        idlFactory,
-      });
+      type PocketIcSetupCanister = PocketIcMops['setupCanister'] & PocketIc['setupCanister'];
+      const {canisterId, actor} = await (this.pocketIc.setupCanister as PocketIcSetupCanister)({
+        wasm,
+        idlFactory,
+      });
+      const idText = typeof canisterId === 'string' ? canisterId : canisterId.toText();

-      await this.pocketIc.addCycles(canisterId as any, 1_000_000_000_000);
+      await this.pocketIc.addCycles(
+        // pic-js-mops accepts string; pic-ic 4.x accepts Principal
+        (typeof canisterId === 'string' ? canisterId : canisterId),
+        1_000_000_000_000n
+      );

       this.canisters[name] = {
         cwd,
-        canisterId: canisterId.toText(),
+        canisterId: idText,
         actor,
         stream: new PassThrough(),
       };

If needed for 4.x, import Principal and convert:

import {Principal} from '@icp-sdk/core/principal';
// then pass Principal.fromText(idText) for addCycles in the 4.x path.
🧹 Nitpick comments (2)
cli/commands/replica.ts (2)

29-30: Consider a discriminant to ease type narrowing for pocket-ic flavors

The unions are fine. Add a discriminant (e.g., pocketIcFlavor: 'mops' | 'legacy') during start() to avoid casts later (deploy/log handling).

 export class Replica {
   ...
-  pocketIcServer ?: PocketIcServer | PocketIcServerMops;
-  pocketIc ?: PocketIc | PocketIcMops;
+  pocketIcServer ?: PocketIcServer | PocketIcServerMops;
+  pocketIc ?: PocketIc | PocketIcMops;
+  pocketIcFlavor?: 'mops' | 'legacy';

And set it in the 9.x vs 4.x branches.


91-93: Version gate is too strict; allow 4.x and handle missing config via binary introspection

Today this hard-fails when mops.toml omits pocket-ic or if a 4.0.x patch appears.

- if (config.toolchain?.['pocket-ic'] !== '4.0.0' && !config.toolchain?.['pocket-ic']?.startsWith('9.')) {
-   console.error('Current Mops CLI only supports pocket-ic 9.x.x and 4.0.0');
+ const configured = config.toolchain?.['pocket-ic'];
+ if (configured && !/^9\./.test(configured) && !/^4\./.test(configured)) {
+   console.error('Current Mops CLI only supports pocket-ic 9.x.x and 4.x');
    process.exit(1);
  }

Optional: if configured is falsy, detect via ${pocketIcBin} --version and branch accordingly.

#!/bin/bash
"$POCKET_IC_BIN" --version || pocket-ic --version
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0a553a and 8fe57ce.

⛔ Files ignored due to path filters (2)
  • cli/bun.lock is excluded by !**/*.lock
  • cli/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • cli/commands/replica.ts (4 hunks)
  • cli/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: node 20, moc 0.14.13, ./cli/dist
🔇 Additional comments (3)
cli/commands/replica.ts (3)

8-12: Confirm @icp-sdk/core import surfaces and HttpAgent factory API

Validate that Actor and HttpAgent.create are exported from @icp-sdk/core/agent in your pinned version. Some builds expose new HttpAgent(...) instead of a create factory.

Run:

#!/bin/bash
npm ls @icp-sdk/core
rg -nP "from '@icp-sdk/core/agent'" -C2 --type ts
rg -nP '\bHttpAgent\.create\s*\(' --type ts

Or check docs:

Does @icp-sdk/core v4.0.2 export Actor and an HttpAgent.create factory from @icp-sdk/core/agent? Cite official docs.

96-105: Validate Mops server API parity (options, .getUrl(), .serverProcess presence)

Double-check start options (showRuntimeLogs, showCanisterLogs, ttl units) and that PocketIcServerMops exposes getUrl() and serverProcess used by the log handler.

#!/bin/bash
rg -nP 'class\s+PocketIcServer' "$(npm root)"/pic-js-mops/** -C3 || true
rg -nP 'getUrl\s*\(' "$(npm root)"/pic-js-mops/** -C1 || true
rg -nP 'serverProcess' "$(npm root)"/pic-js-mops/** -C1 || true

106-115: Parity check with legacy pic-ic 4.x

Ensure the option names and ttl units match pic-ic 4.x expectations; mismatches can silently disable logs or TTL.

#!/bin/bash
rg -nP 'class\s+PocketIcServer' "$(npm root)"/pic-ic/** -C3 || true
rg -nP 'start\s*\(\s*\{' "$(npm root)"/pic-ic/** -C3 || true

@github-actions

github-actions Bot commented Sep 8, 2025

Copy link
Copy Markdown
Contributor

Benchmark Results

bench/1-buffer-vector-add.bench.mo $({\color{green}-8.40\%})$

Add (second)

Add items one-by-one (second)

Instructions: ${\color{green}-8.40\%}$
Heap: ${\color{gray}0\%}$
Stable Memory: ${\color{gray}0\%}$
Garbage Collection: ${\color{gray}0\%}$

Instructions

10 10000 1000000
Buffer 9_493 $({\color{green}-30.14\%})$ 5_687_530 $({\color{green}-8.33\%})$ 525_783_824 $({\color{green}-8.25\%})$
Vector 13_461 $({\color{red}+17.91\%})$ 4_378_548 $({\color{green}-10.82\%})$ 417_864_434 $({\color{green}-10.74\%})$

Heap

10 10000 1000000
Buffer 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$
Vector 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$

Garbage Collection

10 10000 1000000
Buffer 1.09 KiB $({\color{gray}0\%})$ 143.28 KiB $({\color{gray}0\%})$ 12.02 MiB $({\color{gray}0\%})$
Vector 1.09 KiB $({\color{gray}0\%})$ 45.65 KiB $({\color{gray}0\%})$ 3.86 MiB $({\color{gray}0\%})$
bench/2-vector-buffer-add.bench.mo $({\color{green}-10.20\%})$

Add

Add items one-by-one

Instructions: ${\color{green}-10.20\%}$
Heap: ${\color{gray}0\%}$
Stable Memory: ${\color{gray}0\%}$
Garbage Collection: ${\color{gray}0\%}$

Instructions

10 10000 1000000
Vector 13_461 $({\color{green}-12.68\%})$ 4_378_902 $({\color{green}-10.82\%})$ 417_886_028 $({\color{green}-10.74\%})$
Buffer 9_493 $({\color{green}-10.35\%})$ 5_686_822 $({\color{green}-8.33\%})$ 525_780_992 $({\color{green}-8.25\%})$

Heap

10 10000 1000000
Vector 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$
Buffer 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$ 272 B $({\color{gray}0\%})$

Garbage Collection

10 10000 1000000
Vector 1.09 KiB $({\color{gray}0\%})$ 45.65 KiB $({\color{gray}0\%})$ 3.86 MiB $({\color{gray}0\%})$
Buffer 1.09 KiB $({\color{gray}0\%})$ 143.28 KiB $({\color{gray}0\%})$ 12.02 MiB $({\color{gray}0\%})$
bench/array.bench.mo $({\color{green}-18.10\%})$

Array

arr arr

Instructions: ${\color{green}-18.10\%}$
Heap: ${\color{gray}0\%}$
Stable Memory: ${\color{gray}0\%}$
Garbage Collection: ${\color{gray}0\%}$

Instructions

100k x1 reset1 100k x3 reset2 100k x4 reset3
Array 13_502_032 $({\color{green}-10.00\%})$ 3_271 $({\color{green}-26.58\%})$ 27_003_206 $({\color{green}-10.00\%})$ 3_745 $({\color{green}-26.18\%})$ 54_004_063 $({\color{green}-10.00\%})$ 4_219 $({\color{green}-25.87\%})$

Heap

100k x1 reset1 100k x3 reset2 100k x4 reset3
Array 390.9 KiB $({\color{gray}0\%})$ -390.37 KiB $({\color{gray}0\%})$ 390.9 KiB $({\color{gray}0\%})$ -390.37 KiB $({\color{gray}0\%})$ 390.9 KiB $({\color{gray}0\%})$ -390.37 KiB $({\color{gray}0\%})$

Garbage Collection

100k x1 reset1 100k x3 reset2 100k x4 reset3
Array 360 B $({\color{gray}0\%})$ 390.97 KiB $({\color{gray}0\%})$ 391 KiB $({\color{gray}0\%})$ 390.97 KiB $({\color{gray}0\%})$ 1.14 MiB $({\color{gray}0\%})$ 390.97 KiB $({\color{gray}0\%})$
bench/removeLast.bench.mo $({\color{green}-10.90\%})$

Remove items using removeLast

Vector and buffer are initialized with 100k items and then 70k items are removed one-by-one.

Instructions: ${\color{green}-10.90\%}$
Heap: ${\color{gray}0\%}$
Stable Memory: ${\color{gray}0\%}$
Garbage Collection: ${\color{gray}0\%}$

Instructions

remove 70k
Vector 27_707_652 $({\color{green}-13.98\%})$
Buffer 29_236_913 $({\color{green}-7.82\%})$

Heap

remove 70k
Vector -136.8 KiB $({\color{gray}0\%})$
Buffer -269.76 KiB $({\color{gray}0\%})$

Garbage Collection

remove 70k
Vector 139.45 KiB $({\color{gray}0\%})$
Buffer 540.43 KiB $({\color{gray}0\%})$
bench/stable-memory.bench.mo $({\color{green}-134.72\%})$

Stable Memory and Region

Grow Region and store blobs in it

Instructions: ${\color{green}-67.65\%}$
Heap: ${\color{green}-6.10\%}$
Stable Memory: ${\color{gray}0\%}$
Garbage Collection: ${\color{green}-60.97\%}$

Instructions

Region (fill 1/100) Region (fill 1/50) StableMemory
10 pages 2_626_901 $({\color{green}-95.97\%})$ 10_496_214 $({\color{green}-95.97\%})$ 2_629 $({\color{green}-8.11\%})$
100 pages 52_466_697 $({\color{green}-95.97\%})$ 104_914_690 $({\color{green}-95.97\%})$ 2_634 $({\color{green}-10.83\%})$
256 pages 134_273_582 $({\color{green}-95.97\%})$ 268_575_103 $({\color{green}-95.97\%})$ 3_182 $({\color{green}-14.09\%})$

Heap

Region (fill 1/100) Region (fill 1/50) StableMemory
10 pages 272 B $({\color{green}-8.11\%})$ 272 B $({\color{gray}0\%})$ 276 B $({\color{gray}0\%})$
100 pages 272 B $({\color{green}-11.69\%})$ 272 B $({\color{green}-11.69\%})$ 272 B $({\color{gray}0\%})$
256 pages 272 B $({\color{green}-11.69\%})$ 272 B $({\color{green}-11.69\%})$ 276 B $({\color{gray}0\%})$

Garbage Collection

Region (fill 1/100) Region (fill 1/50) StableMemory
10 pages 208.34 KiB $({\color{green}-91.44\%})$ 832.38 KiB $({\color{green}-91.45\%})$ 336 B $({\color{gray}0\%})$
100 pages 4.06 MiB $({\color{green}-91.46\%})$ 8.13 MiB $({\color{green}-91.46\%})$ 340 B $({\color{gray}0\%})$
256 pages 10.4 MiB $({\color{green}-91.46\%})$ 20.8 MiB $({\color{green}-91.46\%})$ 340 B $({\color{gray}0\%})$

Stable Memory

Region (fill 1/100) Region (fill 1/50) StableMemory
10 pages 8 MiB $({\color{gray}0\%})$ 8 MiB $({\color{gray}0\%})$ 8 MiB $({\color{gray}0\%})$
100 pages 8 MiB $({\color{gray}0\%})$ 8 MiB $({\color{gray}0\%})$ 0 B $({\color{gray}0\%})$
256 pages 16 MiB $({\color{gray}0\%})$ 16 MiB $({\color{gray}0\%})$ 16 MiB $({\color{gray}0\%})$

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
test/storage-actor.test.mo (5)

1-1: Remove unused import skip.

It’s not referenced.

-import {test; suite; skip} "mo:test/async";
+import {test; suite} "mo:test/async";

2-5: Import Array to perform safe array checks later.

Needed for robust owners checks below.

 import Result "mo:base/Result";
 import Blob "mo:base/Blob";
 import Debug "mo:base/Debug";
 import ExperimentalCycles "mo:base/ExperimentalCycles";
+import Array "mo:base/Array";

16-16: Drop noisy debug print.

Keeps test output clean.

-    Debug.print("lalalalalalala");

20-27: Strengthen negative assertions by matching the error variant.

Result.isErr hides the reason; asserting on #err _ makes failures clearer.

-      await test("try to finish upload before upload start", func() : async () {
-        let res = await storage.finishUploads([fileId]);
-        assert Result.isErr(res);
-      });
+      await test("try to finish upload before upload start", func() : async () {
+        let res = await storage.finishUploads([fileId]);
+        switch (res) { case (#err _) {}; case (#ok _) { assert false } };
+      });

-      await test("try to upload chunk before upload start", func() : async () {
-        assert Result.isErr(await storage.uploadChunk(fileId, 0, Blob.fromArray([])));
-      });
+      await test("try to upload chunk before upload start", func() : async () {
+        let res = await storage.uploadChunk(fileId, 0, Blob.fromArray([]));
+        switch (res) { case (#err _) {}; case (#ok _) { assert false } };
+      });

29-37: Cover the happy path: upload a chunk before finishing.

Right now the upload is finished without uploading any chunk; add a positive chunk upload to validate the main path.

       await test("start upload", func() : async () {
         assert Result.isOk(await storage.startUpload({
           id = fileId;
           path = "test/test.mo";
           chunkCount = 1;
           owners = [];
         }));
       });
+
+      await test("upload a chunk after start", func() : async () {
+        assert Result.isOk(await storage.uploadChunk(fileId, 0, Blob.fromArray([1])));
+      });

       await test("finish upload", func() : async () {
         assert Result.isOk(await storage.finishUploads([fileId]));
       });

Also applies to: 42-44

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fe57ce and bfa6066.

⛔ Files ignored due to path filters (2)
  • cli/bun.lock is excluded by !**/*.lock
  • cli/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • cli/package.json (3 hunks)
  • test/storage-actor.test.mo (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Benchmarks and Comment
  • GitHub Check: node 20, moc 0.14.13, ic-mops@1.0.0
🔇 Additional comments (2)
test/storage-actor.test.mo (2)

80-82: Add cycles before upgrade (some setups require it).

Safer for pocket-ic v9/system upgrades.

       await test("upgrade storage canister", func() : async () {
+        ExperimentalCycles.add<system>(1_000_000_000_000);
         storage := await (system Storage.Storage)(#upgrade(storage))();
       });

If upgrades in your env don’t require cycles, this is a no-op; otherwise it prevents transient failures.


10-13: Confirm test entrypoint naming
Tests in test/storage-actor.test.mo (importing mo:test/async) currently define

public func runTests() : async () { … }

Many Mocha-inspired or motoko test runners look for

public func run() : async ()

as the suite entrypoint—if your harness doesn’t invoke runTests, these tests will be skipped. Verify the mo:test/async runner’s docs and, if needed, rename:

- public func runTests() : async () {
+ public func run()      : async () {

Comment on lines +62 to +73
await test("get file meta", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert fileMeta.owners == [];
};
case (_) {};
};
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use array-size check; array equality is unreliable.

Comparing arrays with == is not robust in Motoko. Check length (or element-wise) instead.

         switch (res) {
           case (#ok(fileMeta)) {
             assert fileMeta.path == "test/test.mo";
             assert fileMeta.chunkCount == 1;
-            assert fileMeta.owners == [];
+            assert Array.size(fileMeta.owners) == 0;
           };
           case (_) {};
         };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await test("get file meta", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert fileMeta.owners == [];
};
case (_) {};
};
});
await test("get file meta", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert Array.size(fileMeta.owners) == 0;
};
case (_) {};
};
});
🤖 Prompt for AI Agents
In test/storage-actor.test.mo around lines 62 to 73, the test compares
fileMeta.owners to [] using == which is unreliable for arrays in Motoko; replace
that equality check with a length check (e.g., assert
Array.size(fileMeta.owners) == 0) or an explicit element-wise check so the test
verifies the owners array is empty reliably.

Comment on lines +84 to +95
await test("get file meta after upgrade", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert fileMeta.owners == [];
};
case (_) {};
};
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Repeat owners check fix after upgrade.

Same reason as earlier.

         switch (res) {
           case (#ok(fileMeta)) {
             assert fileMeta.path == "test/test.mo";
             assert fileMeta.chunkCount == 1;
-            assert fileMeta.owners == [];
+            assert Array.size(fileMeta.owners) == 0;
           };
           case (_) {};
         };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await test("get file meta after upgrade", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert fileMeta.owners == [];
};
case (_) {};
};
});
await test("get file meta after upgrade", func() : async () {
let res = await storage.getFileMeta(fileId);
assert Result.isOk(res);
switch (res) {
case (#ok(fileMeta)) {
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert Array.size(fileMeta.owners) == 0;
};
case (_) {};
};
});
🤖 Prompt for AI Agents
In test/storage-actor.test.mo around lines 84 to 95, the post-upgrade assertion
still expects fileMeta.owners == [] which is incorrect; update the test to
assert the owners match the expected owners used earlier in the test (e.g.,
replace the empty-array check with a comparison to the previously stored
expectedOwners/ownerId variable or the original owners list) so the ownership
info is verified consistently after upgrade.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
cli/commands/bench-replica.ts (3)

7-7: Defer Mops flavor to runtime (type-only import).

Avoid eagerly loading both PIC flavors. Keep types, load Mops impl only when needed.

-import {PocketIc as PocketIcMops, PocketIcServer as PocketIcServerMops} from 'pic-js-mops';
+import type {PocketIc as PocketIcMops, PocketIcServer as PocketIcServerMops} from 'pic-js-mops';

17-18: Add a discriminant to track which PIC API is active.

This will let you avoid unsafe intersection casts later and branch cleanly.

 	pocketIcServer ?: PocketIcServer | PocketIcServerMops;
 	pocketIc ?: PocketIc | PocketIcMops;
+	pocketIcMajor?: 4 | 9;

39-41: Harden version check (accept “9”, “9.0”, “v9.0.0”, etc.).

String prefix “9.” misses common semver notations. Use a small regex and reuse the parsed version below.

-			if (config.toolchain?.['pocket-ic'] !== '4.0.0' && !config.toolchain?.['pocket-ic']?.startsWith('9.')) {
-				console.error('Current Mops CLI only supports pocket-ic 9.x.x and 4.0.0');
+			const picVersion = config.toolchain?.['pocket-ic'] ?? '';
+			if (picVersion !== '4.0.0' && !/^v?9(\.|$)/.test(picVersion)) {
+				console.error('Current Mops CLI only supports pocket-ic 9.x and 4.0.0');
 				process.exit(1);
 			}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfa6066 and d04ebda.

📒 Files selected for processing (3)
  • cli/commands/bench-replica.ts (4 hunks)
  • cli/commands/replica.ts (4 hunks)
  • mops.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • mops.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/commands/replica.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: node 20, moc 0.14.13, ic-mops@1.0.0
🔇 Additional comments (1)
cli/commands/bench-replica.ts (1)

83-89: Request pic-js-mops v9.x type definitions
I can confirm pic-ic v4.0.0’s setupCanister(options: SetupCanisterOptions) ⇒ Promise<CanisterFixture> signature, but without the pic-js-mops v9.x source or its TypeScript declarations I can’t verify if its setupCanister signature/return differs. Please share the pic-js-mops v9.x repository link or paste the relevant setupCanister type definition (e.g. from its index.d.ts) so I can complete the API comparison.

Comment on lines +43 to +56
// pocket-ic 9.x.x
if (config.toolchain?.['pocket-ic']?.startsWith('9.')) {
this.pocketIcServer = await PocketIcServerMops.start({
binPath: pocketIcBin,
});
this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
}
// pocket-ic 4.0.0
else {
this.pocketIcServer = await PocketIcServer.start({
binPath: pocketIcBin,
});
this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Close server on client init failure and load Mops flavor dynamically.

Prevents orphaned processes if create() throws and avoids hard dependency on pic-js-mops when using v4.

-			// pocket-ic 9.x.x
-			if (config.toolchain?.['pocket-ic']?.startsWith('9.')) {
-				this.pocketIcServer = await PocketIcServerMops.start({
-					binPath: pocketIcBin,
-				});
-				this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
-			}
-			// pocket-ic 4.0.0
-			else {
-				this.pocketIcServer = await PocketIcServer.start({
-					binPath: pocketIcBin,
-				});
-				this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
-			}
+			// pocket-ic 9.x
+			if (/^v?9(\.|$)/.test(picVersion)) {
+				const { PocketIc: PocketIcMops, PocketIcServer: PocketIcServerMops } = await import('pic-js-mops');
+				this.pocketIcServer = await PocketIcServerMops.start({ binPath: pocketIcBin });
+				this.pocketIcMajor = 9;
+				try {
+					this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
+				} catch (e) {
+					await this.pocketIcServer.stop().catch(() => {});
+					throw e;
+				}
+			}
+			// pocket-ic 4.0.0
+			else {
+				this.pocketIcServer = await PocketIcServer.start({ binPath: pocketIcBin });
+				this.pocketIcMajor = 4;
+				try {
+					this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
+				} catch (e) {
+					await this.pocketIcServer.stop().catch(() => {});
+					throw e;
+				}
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// pocket-ic 9.x.x
if (config.toolchain?.['pocket-ic']?.startsWith('9.')) {
this.pocketIcServer = await PocketIcServerMops.start({
binPath: pocketIcBin,
});
this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
}
// pocket-ic 4.0.0
else {
this.pocketIcServer = await PocketIcServer.start({
binPath: pocketIcBin,
});
this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
}
// pocket-ic 9.x
if (/^v?9(\.|$)/.test(picVersion)) {
// Dynamically load the Mops flavor only for v9
const { PocketIc: PocketIcMops, PocketIcServer: PocketIcServerMops } = await import('pic-js-mops');
this.pocketIcServer = await PocketIcServerMops.start({ binPath: pocketIcBin });
this.pocketIcMajor = 9;
try {
this.pocketIc = await PocketIcMops.create(this.pocketIcServer.getUrl());
} catch (e) {
// Ensure we always stop the server if client init fails
await this.pocketIcServer.stop().catch(() => {});
throw e;
}
}
// pocket-ic 4.0.0
else {
this.pocketIcServer = await PocketIcServer.start({ binPath: pocketIcBin });
this.pocketIcMajor = 4;
try {
this.pocketIc = await PocketIc.create(this.pocketIcServer.getUrl());
} catch (e) {
// Ensure we always stop the server if client init fails
await this.pocketIcServer.stop().catch(() => {});
throw e;
}
}
🤖 Prompt for AI Agents
In cli/commands/bench-replica.ts around lines 43–56, starting the pocket-ic
server then calling the client factory can leave an orphaned server if the
client create() throws and the current code also hard-depends on the Mops
package; wrap the server start + client create in a try/catch so that if
PocketIcMops.create or PocketIc.create throws you call
this.pocketIcServer?.stop() (or the server shutdown API) before rethrowing, and
instead of importing Mops statically require/import the Mops module dynamically
only when the version check selects the 9.x path (e.g. dynamic
import('pic-js-mops') vs import('pic-js') fallback) so the code doesn’t
hard-fail for v4 users.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
test/storage-actor2.test.mo (3)

11-12: Right-size minted cycles and document intent.

1e12 cycles is excessive for a unit test and can mask resource issues. Parameterize and lower to what install/upgrade actually needs (tune as required under pocket‑ic v9).

-    ExperimentalCycles.add<system>(1_000_000_000_000);
+    // Mint only what is needed for canister install/upgrade in tests.
+    let CYCLES_FOR_TEST : Nat = 100_000_000_000;
+    ExperimentalCycles.add<system>(CYCLES_FOR_TEST);

16-16: Remove stray debug print.

This noisy log adds no assertion value.

-        Debug.print("lalalalalalala");

42-44: Finishing upload without any chunk: make the invariant explicit.

With chunkCount = 1, finishing without uploading chunk 0 may pass today but is fragile across implementations. Upload a zero-length chunk before finishing.

-            await test("finish upload", func() : async () {
-                assert Result.isOk(await storage.finishUploads([fileId]));
-            });
+            await test("finish upload", func() : async () {
+                // Ensure required chunk exists before finishing.
+                ignore await storage.uploadChunk(fileId, 0, Blob.fromArray([]));
+                assert Result.isOk(await storage.finishUploads([fileId]));
+            });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d04ebda and e44e567.

📒 Files selected for processing (1)
  • test/storage-actor2.test.mo (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: node 20, moc 0.14.13, ./cli/dist
🔇 Additional comments (2)
test/storage-actor2.test.mo (2)

80-82: Upgrade pattern: confirm compatibility across pocket‑ic 4.x and 9.x.

await (system Storage.Storage)(#upgrade(storage))() is subtle and has changed in past toolchains. Please verify this works under both runtime branches targeted in this PR; otherwise gate by version or introduce a helper.


10-10: Do not add a run() wrapper. The Mops/mo:test replica-mode runner automatically invokes your public runTests() entrypoint.

Likely an incorrect or invalid review comment.

Comment on lines +1 to +6
import {test; suite; skip} "mo:test/async";
import Result "mo:base/Result";
import Blob "mo:base/Blob";
import Debug "mo:base/Debug";
import ExperimentalCycles "mo:base/ExperimentalCycles";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Import Array for size/equality helpers.

 import Result "mo:base/Result";
 import Blob "mo:base/Blob";
 import Debug "mo:base/Debug";
 import ExperimentalCycles "mo:base/ExperimentalCycles";
+import Array "mo:base/Array";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {test; suite; skip} "mo:test/async";
import Result "mo:base/Result";
import Blob "mo:base/Blob";
import Debug "mo:base/Debug";
import ExperimentalCycles "mo:base/ExperimentalCycles";
import {test; suite; skip} "mo:test/async";
import Result "mo:base/Result";
import Blob "mo:base/Blob";
import Debug "mo:base/Debug";
import ExperimentalCycles "mo:base/ExperimentalCycles";
import Array "mo:base/Array";
🤖 Prompt for AI Agents
In test/storage-actor2.test.mo around lines 1 to 6, the file is missing the
import for Array which provides size/equality helpers used later; add an import
statement for Array from the appropriate module (e.g., import Array
"mo:base/Array";) alongside the other imports so the test can use Array.size /
Array.equals helpers without runtime errors.

Comment on lines +67 to +71
assert fileMeta.path == "test/test.mo";
assert fileMeta.chunkCount == 1;
assert fileMeta.owners == [];
};
case (_) {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid == on arrays; use Array.size/Array.equal.

Array equality with == is unreliable/unsupported; assert emptiness or structural equality explicitly.

-                        assert fileMeta.owners == [];
+                        assert Array.size(fileMeta.owners) == 0;

Apply the same change to the post-upgrade check.

Also applies to: 89-93

🤖 Prompt for AI Agents
In test/storage-actor2.test.mo around lines 67 to 71 (and similarly update the
post-upgrade check at lines 89 to 93), replace the use of `==` to compare arrays
(e.g., `fileMeta.owners == []`) with an explicit emptiness or structural check
such as `Array.size(fileMeta.owners) == 0` or `Array.equal(fileMeta.owners,
[])`; update both occurrences so the test asserts emptiness or uses Array.equal
for structural equality instead of `==`.

@ZenVoich ZenVoich merged commit 6bf4cbd into main Sep 8, 2025
17 of 19 checks passed
@ZenVoich ZenVoich deleted the new-pocket-ic branch September 8, 2025 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

I'm getting super odd errors when running mops test. Wall of text error - Process exited with code 255

1 participant