From 613da0fbcbb42ac540ed0057d06a11db5db23106 Mon Sep 17 00:00:00 2001 From: Jason I Date: Thu, 17 Oct 2024 12:12:41 -0700 Subject: [PATCH 1/7] chore: skip range check when the certificate comes from the management canister --- packages/agent/src/certificate.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index 8ddac8ca..d9ff728e 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -5,6 +5,7 @@ import { bufEquals, concat, fromHex, toHex } from './utils/buffer'; import { Principal } from '@dfinity/principal'; import * as bls from './utils/bls'; import { decodeTime } from './utils/leb'; +import { MANAGEMENT_CANISTER_ID } from "./agent"; /** * A certificate may fail verification with respect to the provided public key @@ -271,17 +272,19 @@ export class Certificate { await cert.verify(); - const canisterInRange = check_canister_ranges({ - canisterId: this._canisterId, - subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), - tree: cert.cert.tree, - }); - if (!canisterInRange) { - throw new CertificateVerificationError( - `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( - d.subnet_id, - )}`, - ); + if (this._canisterId.toString() !== MANAGEMENT_CANISTER_ID) { + const canisterInRange = check_canister_ranges({ + canisterId: this._canisterId, + subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), + tree: cert.cert.tree, + }); + if (!canisterInRange) { + throw new CertificateVerificationError( + `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( + d.subnet_id, + )}`, + ); + } } const publicKeyLookup = lookupResultToBuffer( cert.lookup(['subnet', d.subnet_id, 'public_key']), From edf0e510a068b7a7e7b25aa3ab1edb7084984784 Mon Sep 17 00:00:00 2001 From: Jason I Date: Fri, 18 Oct 2024 08:03:01 -0700 Subject: [PATCH 2/7] chore: revert certificate.ts. throw UpdateCallRejectedError for v2 reject messages --- packages/agent/src/actor.ts | 17 +++++++++++++++-- packages/agent/src/certificate.ts | 24 +++++++++++------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 72ed8c8e..cc588e41 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -6,9 +6,9 @@ import { QueryResponseRejected, QueryResponseStatus, ReplicaRejectCode, - SubmitResponse, + SubmitResponse, v2ResponseBody, v3ResponseBody, -} from './agent'; +} from "./agent"; import { AgentError } from './errors'; import { bufFromBufLike, IDL } from '@dfinity/candid'; import { pollForResponse, PollStrategyFactory, strategy } from './polling'; @@ -582,6 +582,19 @@ function _createActorMethod( } } } + + // handle v2 response errors by throwing an UpdateCallRejectedError object + if (!response.ok || response.body /* IC-1462 */) { + const { reject_code, reject_message, error_code } = response.body as v2ResponseBody; + throw new UpdateCallRejectedError(cid, + methodName, + requestId, + response, + reject_code, + reject_message, + error_code) + } + // Fall back to polling if we receive an Accepted response code if (response.status === 202) { const pollStrategy = pollingStrategyFactory(); diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index d9ff728e..1f537478 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -272,19 +272,17 @@ export class Certificate { await cert.verify(); - if (this._canisterId.toString() !== MANAGEMENT_CANISTER_ID) { - const canisterInRange = check_canister_ranges({ - canisterId: this._canisterId, - subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), - tree: cert.cert.tree, - }); - if (!canisterInRange) { - throw new CertificateVerificationError( - `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( - d.subnet_id, - )}`, - ); - } + const canisterInRange = check_canister_ranges({ + canisterId: this._canisterId, + subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), + tree: cert.cert.tree, + }); + if (!canisterInRange) { + throw new CertificateVerificationError( + `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( + d.subnet_id, + )}`, + ); } const publicKeyLookup = lookupResultToBuffer( cert.lookup(['subnet', d.subnet_id, 'public_key']), From db92356b29e339e1b9411b140368aeeb91502d98 Mon Sep 17 00:00:00 2001 From: Jason I Date: Fri, 18 Oct 2024 11:50:26 -0700 Subject: [PATCH 3/7] chore: skip delegation range check. refactor UpdateCallRejectedError throw --- packages/agent/src/actor.ts | 29 ++++++++++++----------- packages/agent/src/certificate.ts | 38 ++++++++++++++++--------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index cc588e41..8ea0f2b8 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer/'; +import { Buffer } from "buffer/"; import { Agent, getDefaultAgent, @@ -6,18 +6,19 @@ import { QueryResponseRejected, QueryResponseStatus, ReplicaRejectCode, - SubmitResponse, v2ResponseBody, + SubmitResponse, + v2ResponseBody, v3ResponseBody, } from "./agent"; -import { AgentError } from './errors'; -import { bufFromBufLike, IDL } from '@dfinity/candid'; -import { pollForResponse, PollStrategyFactory, strategy } from './polling'; -import { Principal } from '@dfinity/principal'; -import { RequestId } from './request_id'; -import { toHex } from './utils/buffer'; -import { Certificate, CreateCertificateOptions, lookupResultToBuffer } from './certificate'; -import managementCanisterIdl from './canisters/management_idl'; -import _SERVICE, { canister_install_mode, canister_settings } from './canisters/management_service'; +import { AgentError } from "./errors"; +import { bufFromBufLike, IDL } from "@dfinity/candid"; +import { pollForResponse, PollStrategyFactory, strategy } from "./polling"; +import { Principal } from "@dfinity/principal"; +import { RequestId } from "./request_id"; +import { toHex } from "./utils/buffer"; +import { Certificate, CreateCertificateOptions, lookupResultToBuffer } from "./certificate"; +import managementCanisterIdl from "./canisters/management_idl"; +import _SERVICE, { canister_install_mode, canister_settings } from "./canisters/management_service"; export class ActorCallError extends AgentError { constructor( @@ -581,10 +582,8 @@ function _createActorMethod( ); } } - } - - // handle v2 response errors by throwing an UpdateCallRejectedError object - if (!response.ok || response.body /* IC-1462 */) { + } else if (!response.ok || response.body /* IC-1462 */) { + // handle v2 response errors by throwing an UpdateCallRejectedError object const { reject_code, reject_message, error_code } = response.body as v2ResponseBody; throw new UpdateCallRejectedError(cid, methodName, diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index 1f537478..d20cb925 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -1,10 +1,10 @@ -import * as cbor from './cbor'; -import { AgentError } from './errors'; -import { hash } from './request_id'; -import { bufEquals, concat, fromHex, toHex } from './utils/buffer'; -import { Principal } from '@dfinity/principal'; -import * as bls from './utils/bls'; -import { decodeTime } from './utils/leb'; +import * as cbor from "./cbor"; +import { AgentError } from "./errors"; +import { hash } from "./request_id"; +import { bufEquals, concat, fromHex, toHex } from "./utils/buffer"; +import { Principal } from "@dfinity/principal"; +import * as bls from "./utils/bls"; +import { decodeTime } from "./utils/leb"; import { MANAGEMENT_CANISTER_ID } from "./agent"; /** @@ -272,17 +272,19 @@ export class Certificate { await cert.verify(); - const canisterInRange = check_canister_ranges({ - canisterId: this._canisterId, - subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), - tree: cert.cert.tree, - }); - if (!canisterInRange) { - throw new CertificateVerificationError( - `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( - d.subnet_id, - )}`, - ); + if (this._canisterId.toString() !== MANAGEMENT_CANISTER_ID) { + const canisterInRange = check_canister_ranges({ + canisterId: this._canisterId, + subnetId: Principal.fromUint8Array(new Uint8Array(d.subnet_id)), + tree: cert.cert.tree, + }); + if (!canisterInRange) { + throw new CertificateVerificationError( + `Canister ${this._canisterId} not in range of delegations for subnet 0x${toHex( + d.subnet_id, + )}`, + ); + } } const publicKeyLookup = lookupResultToBuffer( cert.lookup(['subnet', d.subnet_id, 'public_key']), From 1935898edc4b3d704ed9a9ddb2f8d02672174ad3 Mon Sep 17 00:00:00 2001 From: Kai Peacock Date: Mon, 21 Oct 2024 11:00:44 -0700 Subject: [PATCH 4/7] prettier --- packages/agent/src/actor.ts | 28 +++++++++++++++------------- packages/agent/src/certificate.ts | 16 ++++++++-------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 8ea0f2b8..305177f6 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -1,4 +1,4 @@ -import { Buffer } from "buffer/"; +import { Buffer } from 'buffer/'; import { Agent, getDefaultAgent, @@ -9,16 +9,16 @@ import { SubmitResponse, v2ResponseBody, v3ResponseBody, -} from "./agent"; -import { AgentError } from "./errors"; -import { bufFromBufLike, IDL } from "@dfinity/candid"; -import { pollForResponse, PollStrategyFactory, strategy } from "./polling"; -import { Principal } from "@dfinity/principal"; -import { RequestId } from "./request_id"; -import { toHex } from "./utils/buffer"; -import { Certificate, CreateCertificateOptions, lookupResultToBuffer } from "./certificate"; -import managementCanisterIdl from "./canisters/management_idl"; -import _SERVICE, { canister_install_mode, canister_settings } from "./canisters/management_service"; +} from './agent'; +import { AgentError } from './errors'; +import { bufFromBufLike, IDL } from '@dfinity/candid'; +import { pollForResponse, PollStrategyFactory, strategy } from './polling'; +import { Principal } from '@dfinity/principal'; +import { RequestId } from './request_id'; +import { toHex } from './utils/buffer'; +import { Certificate, CreateCertificateOptions, lookupResultToBuffer } from './certificate'; +import managementCanisterIdl from './canisters/management_idl'; +import _SERVICE, { canister_install_mode, canister_settings } from './canisters/management_service'; export class ActorCallError extends AgentError { constructor( @@ -585,13 +585,15 @@ function _createActorMethod( } else if (!response.ok || response.body /* IC-1462 */) { // handle v2 response errors by throwing an UpdateCallRejectedError object const { reject_code, reject_message, error_code } = response.body as v2ResponseBody; - throw new UpdateCallRejectedError(cid, + throw new UpdateCallRejectedError( + cid, methodName, requestId, response, reject_code, reject_message, - error_code) + error_code, + ); } // Fall back to polling if we receive an Accepted response code diff --git a/packages/agent/src/certificate.ts b/packages/agent/src/certificate.ts index d20cb925..813cd704 100644 --- a/packages/agent/src/certificate.ts +++ b/packages/agent/src/certificate.ts @@ -1,11 +1,11 @@ -import * as cbor from "./cbor"; -import { AgentError } from "./errors"; -import { hash } from "./request_id"; -import { bufEquals, concat, fromHex, toHex } from "./utils/buffer"; -import { Principal } from "@dfinity/principal"; -import * as bls from "./utils/bls"; -import { decodeTime } from "./utils/leb"; -import { MANAGEMENT_CANISTER_ID } from "./agent"; +import * as cbor from './cbor'; +import { AgentError } from './errors'; +import { hash } from './request_id'; +import { bufEquals, concat, fromHex, toHex } from './utils/buffer'; +import { Principal } from '@dfinity/principal'; +import * as bls from './utils/bls'; +import { decodeTime } from './utils/leb'; +import { MANAGEMENT_CANISTER_ID } from './agent'; /** * A certificate may fail verification with respect to the provided public key From 30ead315975ed78d64dca5ff767f3042ed59c6c6 Mon Sep 17 00:00:00 2001 From: Kai Peacock Date: Tue, 22 Oct 2024 15:39:08 -0700 Subject: [PATCH 5/7] test: unit tests for canister_status reject and reply from management canister --- .../http/__snapshots__/http.test.ts.snap | 75 ++++++++++ packages/agent/src/agent/http/http.test.ts | 139 +++++++++++++++++- 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/packages/agent/src/agent/http/__snapshots__/http.test.ts.snap b/packages/agent/src/agent/http/__snapshots__/http.test.ts.snap index 9ad5d26e..5daa43d2 100644 --- a/packages/agent/src/agent/http/__snapshots__/http.test.ts.snap +++ b/packages/agent/src/agent/http/__snapshots__/http.test.ts.snap @@ -1,5 +1,80 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`it should handle calls against the ic-management canister that succeed 1`] = ` +{ + "cycles": 3092219247033n, + "idle_cycles_burned_per_day": 1808810n, + "memory_size": 2301012n, + "module_hash": [ + Uint8Array [ + 254, + 155, + 232, + 199, + 49, + 146, + 52, + 52, + 57, + 201, + 131, + 209, + 77, + 162, + 243, + 122, + 89, + 50, + 105, + 40, + 93, + 49, + 15, + 210, + 193, + 29, + 73, + 112, + 229, + 241, + 110, + 182, + ], + ], + "query_stats": { + "num_calls_total": 0n, + "num_instructions_total": 0n, + "request_payload_bytes_total": 0n, + "response_payload_bytes_total": 0n, + }, + "reserved_cycles": 0n, + "settings": { + "compute_allocation": 0n, + "controllers": [ + { + "__principal__": "2vxsx-fae", + }, + { + "__principal__": "bnz7o-iuaaa-aaaaa-qaaaa-cai", + }, + { + "__principal__": "jhnlf-yu2dz-v7beb-c77gl-76tj7-shaqo-5qfvi-htvel-gzamb-bvzx6-yqe", + }, + ], + "freezing_threshold": 2592000n, + "log_visibility": { + "controllers": null, + }, + "memory_allocation": 0n, + "reserved_cycles_limit": 5000000000000n, + "wasm_memory_limit": 0n, + }, + "status": { + "running": null, + }, +} +`; + exports[`retry failures should succeed after multiple failures within the configured limit 1`] = ` { "requestDetails": undefined, diff --git a/packages/agent/src/agent/http/http.test.ts b/packages/agent/src/agent/http/http.test.ts index 386b6bda..7d83c2af 100644 --- a/packages/agent/src/agent/http/http.test.ts +++ b/packages/agent/src/agent/http/http.test.ts @@ -13,7 +13,14 @@ import { Principal } from '@dfinity/principal'; import { requestIdOf } from '../../request_id'; import { JSDOM } from 'jsdom'; -import { Actor, AnonymousIdentity, SignIdentity, toHex } from '../..'; +import { + Actor, + AnonymousIdentity, + fromHex, + getManagementCanister, + SignIdentity, + toHex, +} from '../..'; import { Ed25519KeyIdentity } from '@dfinity/identity'; import { AgentError } from '../../errors'; import { AgentHTTPResponseError } from './errors'; @@ -813,3 +820,133 @@ test('it should log errors to console if the option is set', async () => { await agent.syncTime(); }); +test('it should handle calls against the ic-management canister that are rejected', async () => { + const identity = new AnonymousIdentity(); + identity.getPrincipal().toString(); //? + + // Response generated by calling a locally deployed replica of the management canister + const mockResponse = { + headers: [ + ['access-control-allow-origin', '*'], + ['content-length', '178'], + ['content-type', 'application/cbor'], + ['date', 'Mon, 21 Oct 2024 23:35:59 GMT'], + ], + ok: true, + status: 200, + statusText: 'OK', + body: 'd9d9f7a46673746174757378186e6f6e5f7265706c6963617465645f72656a656374696f6e6a6572726f725f636f6465664943303531326b72656a6563745f636f6465056e72656a6563745f6d657373616765785d4f6e6c7920636f6e74726f6c6c657273206f662063616e697374657220626b797a322d666d6161612d61616161612d71616161712d6361692063616e2063616c6c2069633030206d6574686f642063616e69737465725f737461747573', + now: 1729553760128, + }; + + const mockFetch: jest.Mock = jest.fn(() => { + return Promise.resolve({ + ...mockResponse, + body: fromHex(mockResponse.body), + arrayBuffer: async () => fromHex(mockResponse.body), + }); + }); + + jest.useFakeTimers({ + legacyFakeTimers: false, + }); + + jest.setSystemTime(mockResponse.now); + + const agent = await HttpAgent.createSync({ + identity, + fetch: mockFetch, + host: 'http://localhost:4943', + }); + + const management = getManagementCanister({ agent }); + + expect( + management.canister_status({ + canister_id: Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai'), + }), + ).rejects.toThrow( + 'Only controllers of canister bkyz2-fmaaa-aaaaa-qaaaq-cai can call ic00 method canister_status', + ); +}); +test('it should handle calls against the ic-management canister that succeed', async () => { + const identity = new AnonymousIdentity(); + jest.createMockFromModule('./types'); + + const mockResponse = { + headers: [ + ['access-control-allow-origin', '*'], + ['content-length', '761'], + ['content-type', 'application/cbor'], + ['date', 'Tue, 22 Oct 2024 22:19:07 GMT'], + ], + ok: true, + status: 200, + statusText: 'OK', + body: 'd9d9f7a266737461747573677265706c6965646b63657274696669636174655902d7d9d9f7a26474726565830183018204582012dbb02955bd3e2987bbba491230b2bb4a593feb02b5bb2d08f5f861afa9cec28301820458202b60693266aeec370be9f54508af493f4dd740086476054c862fe5af17ab15c183024e726571756573745f73746174757383018301820458204bebdfa0327978bfb109f0e14b35e8d368bb62114628ae547386162e9ee3dad883025820cf1cd57f39dfbb40ca1c816c71407c8b1b2edfb5a632676c7917dc4aa8641c5283018302457265706c7982035901684449444c0a6c0b9cb1fa2568b2ceef2f01c0cff2717d9cbab69c0202ffdb81f7037d8daacd94087de3f9f5d90805e8fc8cec0908b0e4d2970a7d81cfaef40a0984aaa89e0f7d6b038da4879b047ff496e4910b7fffdba5db0e7f6d036c020004017d6d7b6c089cb1fa2568c0cff2717dd7e09b90020680ad988a047dedd9c8c90707f8e287cc0c7ddeebb5a90e7da882acc60f7d6d686b02d7e09b90027fa981ceb7067f6c04c1f8dc83037d83cac6e9057da1d0b8af0a7d8fd0cfd00f7d6e040100011100001945cd0f5904e6ce2e5ac91900fb0102809a9e01010100b9f384b5ff59d4b88c01b9f384b5ff59011100001945cd0f5904e6ce2e5ac91900fb01809a9e0103010104010a80000000001000000101011d9a1e6bf09022ffccbffa69fc8e083bb02d5079d48b3640c086b9bfb10280a0e5b9c291010000000000000000aab36e0120fe9be8c73192343439c983d14da2f37a593269285d310fd2c11d4970e5f16eb6008302467374617475738203477265706c69656482045820daeffcc5dadc3aca94e0dc470e429ad4e3bc08517b5776f6a71e7e6982883bef8301820458208e6c6a7c4ba444475de4f4cd2d6df9501873d3290693060faf92e6dc528ee08083024474696d65820349a88dd3f9dacbb98018697369676e6174757265583088040a8228ef3f428c61918c5fb356e74b2ab07aa19f960edbb1fdfcbbc115e35f2e6c3f33b5cf4752799619e67e2b22', + now: 1729635546372, + }; + + jest.useFakeTimers(); + jest.setSystemTime(mockResponse.now); + + const mockFetch: jest.Mock = jest.fn(() => { + return Promise.resolve({ + ...mockResponse, + body: fromHex(mockResponse.body), + arrayBuffer: async () => fromHex(mockResponse.body), + }); + }); + + const agent = await HttpAgent.createSync({ + identity, + fetch: mockFetch, + host: 'http://localhost:4943', + rootKey: fromHex( + '308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c050302010361008be882f1985cccb53fd551571a42818014835ed8f8a27767669b67dd4a836eb0d62b327e3368a80615b0e4f472c73f7917c036dc9317dcb64b319a1efa43dd7c656225c061de359db6fdf7033ac1bff24c944c145e46ebdce2093680b6209a13', + ), + }); + + const management = getManagementCanister({ agent }); + + // Important - override nonce when making request to ensure reproducible result + (Actor.agentOf(management) as HttpAgent).addTransform('update', async args => { + args.body.nonce = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]) as Nonce; + return args; + }); + + const status = await management.canister_status({ + canister_id: Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai'), + }); + + expect(status).toMatchSnapshot(); +}); + +/** + * Test utility to clone a fetch response for mocking purposes with the agent + * @param request - RequestInfo + * @param init - RequestInit + * @returns Promise + */ +export async function fetchCloner( + request: RequestInfo | URL, + init?: RequestInit, +): Promise { + const response = await fetch(request, init); + const cloned = response.clone(); + const responseBuffer = await cloned.arrayBuffer(); + + const mock = { + headers: [...response.headers.entries()], + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: toHex(responseBuffer), + now: Date.now(), + }; + + console.log(request); + console.log(JSON.stringify(mock)); + + return response; +} From 1502ed357ed0274347ab482054c80eb6efbcba88 Mon Sep 17 00:00:00 2001 From: Kai Peacock Date: Tue, 22 Oct 2024 15:47:40 -0700 Subject: [PATCH 6/7] cleaning up --- packages/agent/src/agent/http/http.test.ts | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/agent/src/agent/http/http.test.ts b/packages/agent/src/agent/http/http.test.ts index 7d83c2af..17b618ef 100644 --- a/packages/agent/src/agent/http/http.test.ts +++ b/packages/agent/src/agent/http/http.test.ts @@ -822,9 +822,9 @@ test('it should log errors to console if the option is set', async () => { test('it should handle calls against the ic-management canister that are rejected', async () => { const identity = new AnonymousIdentity(); - identity.getPrincipal().toString(); //? + identity.getPrincipal().toString(); - // Response generated by calling a locally deployed replica of the management canister + // Response generated by calling a locally deployed replica of the management canister, cloned using fetchCloner const mockResponse = { headers: [ ['access-control-allow-origin', '*'], @@ -839,6 +839,7 @@ test('it should handle calls against the ic-management canister that are rejecte now: 1729553760128, }; + // Mock the fetch implementation, resolving a pre-calculated response const mockFetch: jest.Mock = jest.fn(() => { return Promise.resolve({ ...mockResponse, @@ -847,10 +848,8 @@ test('it should handle calls against the ic-management canister that are rejecte }); }); - jest.useFakeTimers({ - legacyFakeTimers: false, - }); - + // Mock time so certificates can be accurately decoded + jest.useFakeTimers(); jest.setSystemTime(mockResponse.now); const agent = await HttpAgent.createSync({ @@ -859,8 +858,10 @@ test('it should handle calls against the ic-management canister that are rejecte host: 'http://localhost:4943', }); + // Use management canister call const management = getManagementCanister({ agent }); + // Call snapshot was made when the test canister was not authorized to be called by the anonymous identity. It should reject expect( management.canister_status({ canister_id: Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai'), @@ -871,8 +872,8 @@ test('it should handle calls against the ic-management canister that are rejecte }); test('it should handle calls against the ic-management canister that succeed', async () => { const identity = new AnonymousIdentity(); - jest.createMockFromModule('./types'); + // Response generated by calling a locally deployed replica of the management canister, cloned using fetchCloner const mockResponse = { headers: [ ['access-control-allow-origin', '*'], @@ -887,9 +888,7 @@ test('it should handle calls against the ic-management canister that succeed', a now: 1729635546372, }; - jest.useFakeTimers(); - jest.setSystemTime(mockResponse.now); - + // Mock the fetch implementation, resolving a pre-calculated response const mockFetch: jest.Mock = jest.fn(() => { return Promise.resolve({ ...mockResponse, @@ -898,6 +897,11 @@ test('it should handle calls against the ic-management canister that succeed', a }); }); + // Mock time so certificates can be accurately decoded + jest.useFakeTimers(); + jest.setSystemTime(mockResponse.now); + + // Pass in rootKey from replica (used because test was written using local replica) const agent = await HttpAgent.createSync({ identity, fetch: mockFetch, @@ -907,6 +911,7 @@ test('it should handle calls against the ic-management canister that succeed', a ), }); + // Use management canister call const management = getManagementCanister({ agent }); // Important - override nonce when making request to ensure reproducible result @@ -915,6 +920,7 @@ test('it should handle calls against the ic-management canister that succeed', a return args; }); + // Call snapshot was made after the test canister was authorized to be called by the anonymous identity. It should resolve the status const status = await management.canister_status({ canister_id: Principal.from('bkyz2-fmaaa-aaaaa-qaaaq-cai'), }); From 9142734d67671ffed0659ea658ca83c56599186a Mon Sep 17 00:00:00 2001 From: Kai Peacock Date: Tue, 22 Oct 2024 16:09:22 -0700 Subject: [PATCH 7/7] changing condition for handling error responses --- packages/agent/src/actor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/agent/src/actor.ts b/packages/agent/src/actor.ts index 305177f6..bf4f906c 100644 --- a/packages/agent/src/actor.ts +++ b/packages/agent/src/actor.ts @@ -582,7 +582,7 @@ function _createActorMethod( ); } } - } else if (!response.ok || response.body /* IC-1462 */) { + } else if (response.body && 'reject_message' in response.body) { // handle v2 response errors by throwing an UpdateCallRejectedError object const { reject_code, reject_message, error_code } = response.body as v2ResponseBody; throw new UpdateCallRejectedError(