Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions e2e/node/basic/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { LookupPathResultFound } from '@icp-sdk/core/agent';
import { Certificate, LookupPathStatus } from '@icp-sdk/core/agent';
import { IDL, PipeArrayBuffer } from '@icp-sdk/core/candid';
import { Principal } from '@icp-sdk/core/principal';
import agent from '../utils/agent.ts';
import agent, { gatewayPort } from '../utils/agent.ts';
import { test, expect } from 'vitest';
import { utf8ToBytes } from '@noble/hashes/utils';

Expand All @@ -11,7 +11,7 @@ import { utf8ToBytes } from '@noble/hashes/utils';
* @returns the default effective canister id
*/
export async function getDefaultEffectiveCanisterId() {
const res = await fetch(`http://127.0.0.1:${process.env.REPLICA_PORT}/_/topology`);
const res = await fetch(`http://127.0.0.1:${gatewayPort}/_/topology`);
const data = (await res.json()) as Record<string, Record<string, string>>;
const id = data['default_effective_canister_id']['canister_id'];
// decode from base64
Expand Down
5 changes: 1 addition & 4 deletions e2e/node/basic/canisterStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ describe('canister status', () => {
});
it('should throw an error if fetchRootKey has not been called', async () => {
const counterCanisterId = Principal.fromText(requireEnv('CANISTER_ID_COUNTER'));
const agent = HttpAgent.createSync({
host: `http://127.0.0.1:${process.env.REPLICA_PORT ?? 4943}`,
verifyQuerySignatures: false,
});
const agent = await makeAgent({ verifyQuerySignatures: false, shouldFetchRootKey: false });
expect.assertions(1);
try {
await CanisterStatus.request({
Expand Down
24 changes: 7 additions & 17 deletions e2e/node/basic/counter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ActorMethod } from '@icp-sdk/core/agent';
import { Actor, HttpAgent } from '@icp-sdk/core/agent';
import { Actor } from '@icp-sdk/core/agent';
import { counterActor, counter2Actor, counterCanisterId, idl } from '../canisters/counter.ts';
import { it, expect, describe, vi, beforeAll, beforeEach, afterAll } from 'vitest';
import { makeAgent } from '../utils/agent.ts';

describe.sequential('counter', () => {
beforeAll(async () => {
Expand Down Expand Up @@ -58,10 +59,7 @@ describe.sequential('counter', () => {
// Create counter actor with preSignReadStateRequest enabled
const counter = await Actor.createActor(idl, {
canisterId: counterCanisterId,
agent: await HttpAgent.create({
host: `http://127.0.0.1:${process.env.REPLICA_PORT}`,
shouldFetchRootKey: true,
}),
agent: await makeAgent(),
pollingOptions: {
preSignReadStateRequest: true,
},
Expand All @@ -83,12 +81,9 @@ describe.sequential('counter', () => {

it('should allow method-specific pollingOptions override', async () => {
// Create counter actor without preSignReadStateRequest
const counter = await Actor.createActor(idl, {
const counter = Actor.createActor(idl, {
canisterId: counterCanisterId,
agent: await HttpAgent.create({
host: `http://127.0.0.1:${process.env.REPLICA_PORT}`,
shouldFetchRootKey: true,
}),
agent: await makeAgent(),
});

// Reset counter to 0
Expand Down Expand Up @@ -131,14 +126,9 @@ describe('retrytimes', () => {
return fetch.apply(null, args as [input: string | Request, init?: RequestInit | undefined]);
});

const counter = await Actor.createActor(idl, {
const counter = Actor.createActor(idl, {
canisterId: counterCanisterId,
agent: await HttpAgent.create({
fetch: fetchMock as typeof fetch,
retryTimes: 3,
host: `http://127.0.0.1:${process.env.REPLICA_PORT}`,
shouldFetchRootKey: true,
}),
agent: await makeAgent({ fetch: fetchMock as typeof fetch, retryTimes: 3 }),
});

const result = await counter.greet('counter');
Expand Down
13 changes: 4 additions & 9 deletions e2e/node/basic/trap.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, it, expect } from 'vitest';
import type { ActorMethod } from '@icp-sdk/core/agent';
import { Actor, HttpAgent, AgentError, CertifiedRejectErrorCode } from '@icp-sdk/core/agent';
import { Actor, AgentError, CertifiedRejectErrorCode } from '@icp-sdk/core/agent';
import type { IDL } from '@icp-sdk/core/candid';
import { requireEnv } from '../test-setup.ts';
import { makeAgent } from '../utils/agent.ts';

const trapCanisterId = requireEnv('CANISTER_ID_TRAP');

Expand All @@ -20,10 +21,7 @@ export interface _SERVICE {

describe('trap', () => {
it('should trap', async () => {
const agent = await HttpAgent.create({
host: `http://127.0.0.1:${process.env.REPLICA_PORT}`,
shouldFetchRootKey: true,
});
const agent = await makeAgent();
const actor = Actor.createActor<_SERVICE>(idlFactory, { canisterId: trapCanisterId, agent });
expect.assertions(3);
try {
Expand All @@ -36,10 +34,7 @@ describe('trap', () => {
}
});
it('should trap', async () => {
const agent = await HttpAgent.create({
host: `http://127.0.0.1:${process.env.REPLICA_PORT}`,
shouldFetchRootKey: true,
});
const agent = await makeAgent();
const actor = Actor.createActor<_SERVICE>(idlFactory, { canisterId: trapCanisterId, agent });
expect.assertions(3);
try {
Expand Down
6 changes: 3 additions & 3 deletions e2e/node/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ let pic: PocketIc;

/** Start PocketIC and deploy canisters, or reuse an existing instance. */
export async function setup(): Promise<void> {
// If a .env file already exists with REPLICA_PORT set (e.g., from setup-pic.ts),
// If a .env file already exists with GATEWAY_PORT set (e.g., from setup-pic.ts),
// verify the server is actually reachable before reusing it.
if (existsSync(ENV_PATH)) {
const existing = dotenv.parse(readFileSync(ENV_PATH));
if (existing.REPLICA_PORT && existing.CANISTER_ID_COUNTER) {
if (existing.GATEWAY_PORT && existing.CANISTER_ID_COUNTER) {
try {
const res = await fetch(`http://127.0.0.1:${existing.REPLICA_PORT}/api/v2/status`);
const res = await fetch(`http://127.0.0.1:${existing.GATEWAY_PORT}/api/v2/status`);
if (res.ok) {
Object.assign(process.env, existing);
return;
Expand Down
3 changes: 2 additions & 1 deletion e2e/node/pic-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export async function startPocketIC({
const port = gwData.Created.port;

const envVars: Record<string, string> = {
REPLICA_PORT: String(port),
GATEWAY_PORT: String(port),
SERVER_URL: serverUrl,
CANISTER_ID_COUNTER: counterCanisterId,
CANISTER_ID_COUNTER2: counter2CanisterId,
CANISTER_ID_COUNTER3: counter3CanisterId,
Expand Down
6 changes: 3 additions & 3 deletions e2e/node/setup-pic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* npx tsx setup-pic.ts [--gateway-port PORT] Start PocketIC and keep alive
* npx tsx setup-pic.ts --wait [--timeout SEC] Wait for a running instance to be ready
*
* Writes a .env file with REPLICA_PORT and canister IDs.
* Writes a .env file with GATEWAY_PORT, SERVER_URL, and canister IDs.
* The PocketIC server stays running until this process is killed.
*/
import { existsSync, readFileSync, writeFileSync } from 'fs';
Expand All @@ -27,7 +27,7 @@ if (args.includes('--wait')) {
while (Date.now() < deadline) {
if (existsSync(ENV_PATH)) {
const content = readFileSync(ENV_PATH, 'utf-8');
if (content.includes('REPLICA_PORT')) {
if (content.includes('GATEWAY_PORT')) {
// eslint-disable-next-line no-console
console.log(content.trim());
process.exit(0);
Expand All @@ -51,7 +51,7 @@ const envContent = Object.entries(envVars)
writeFileSync(ENV_PATH, `${envContent}\n`);

// eslint-disable-next-line no-console
console.log(`PocketIC gateway listening on port ${envVars.REPLICA_PORT}`);
console.log(`PocketIC gateway listening on port ${envVars.GATEWAY_PORT}`);
// eslint-disable-next-line no-console
console.log(`Canister IDs written to .env`);

Expand Down
10 changes: 6 additions & 4 deletions e2e/node/utils/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { Ed25519KeyIdentity } from '@icp-sdk/core/identity';
export const identity = Ed25519KeyIdentity.generate();
export const principal = identity.getPrincipal();

export const port = parseInt(process.env['REPLICA_PORT'] || '4943', 10);
if (Number.isNaN(port)) {
throw new Error('The environment variable REPLICA_PORT is not a number.');
export const gatewayPort = parseInt(process.env['GATEWAY_PORT'] || '4943', 10);
if (Number.isNaN(gatewayPort)) {
throw new Error('The environment variable GATEWAY_PORT is not a number.');
}

export const makeAgent = async (options?: HttpAgentOptions) => {
return await HttpAgent.create({
host: `http://127.0.0.1:${process.env.REPLICA_PORT ?? 4943}`,
host: process.env.SERVER_URL
? new URL('/instances/0/', process.env.SERVER_URL).toString()
: `http://127.0.0.1:${gatewayPort}`,
shouldFetchRootKey: true,
...options,
});
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/agent/agent/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ export class HttpAgent implements Agent {

const host = determineHost(options.host);
this.host = new URL(host);
// Ensure trailing slash so relative API path construction preserves any base path prefix
if (!this.host.pathname.endsWith('/')) {
this.host.pathname += '/';
}
// Rewrite to avoid redirects and normalize the host before using it for namespacing caches
if (this.host.hostname.endsWith(IC0_SUB_DOMAIN)) {
this.host.hostname = IC0_DOMAIN;
Expand Down Expand Up @@ -563,7 +567,7 @@ export class HttpAgent implements Agent {
try {
// Attempt v4 sync call
const requestSync = () => {
const url = new URL(`/api/v4/canister/${ecid.toText()}/call`, this.host);
const url = new URL(`api/v4/canister/${ecid.toText()}/call`, this.host);
this.log.print(`fetching "${url.pathname}" with request:`, transformedRequest);
return this.#fetch(url, {
...this.#callOptions,
Expand All @@ -573,7 +577,7 @@ export class HttpAgent implements Agent {
};

const requestAsync = () => {
const url = new URL(`/api/v2/canister/${ecid.toText()}/call`, this.host);
const url = new URL(`api/v2/canister/${ecid.toText()}/call`, this.host);
this.log.print(`fetching "${url.pathname}" with request:`, transformedRequest);
return this.#fetch(url, {
...this.#callOptions,
Expand Down Expand Up @@ -808,7 +812,7 @@ export class HttpAgent implements Agent {

const delay = tries === 0 ? 0 : backoff.next();

const url = new URL(`/api/v3/canister/${ecid.toString()}/query`, this.host);
const url = new URL(`api/v3/canister/${ecid.toString()}/query`, this.host);

this.log.print(`fetching "${url.pathname}" with tries:`, {
tries,
Expand Down Expand Up @@ -1275,7 +1279,7 @@ export class HttpAgent implements Agent {
transformedRequest = await this.createReadStateRequest(fields, identity);
}

const url = new URL(`/api/v3/canister/${canister.toString()}/read_state`, this.host);
const url = new URL(`api/v3/canister/${canister.toString()}/read_state`, this.host);

return await this.#readStateInner(url, { canisterId: canister }, transformedRequest, requestId);
}
Expand All @@ -1293,7 +1297,7 @@ export class HttpAgent implements Agent {
await this.#rootKeyGuard();
const subnet = Principal.from(subnetId);

const url = new URL(`/api/v3/subnet/${subnet.toString()}/read_state`, this.host);
const url = new URL(`api/v3/subnet/${subnet.toString()}/read_state`, this.host);
const transformedRequest: ReadStateRequest = await this.createReadStateRequest(
options,
this.#identity ?? undefined,
Expand Down Expand Up @@ -1522,7 +1526,7 @@ export class HttpAgent implements Agent {
}
: {};

const url = new URL(`/api/v2/status`, this.host);
const url = new URL(`api/v2/status`, this.host);

this.log.print(`fetching "${url.pathname}"`);
const backoff = this.#backoffStrategy();
Expand Down
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading