From cff3c6a2ce298117f36a20ae32a52d2a221af420 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 15 Mar 2024 15:02:07 +0100 Subject: [PATCH] test: satellite upgrade Signed-off-by: David Dal Busco --- .gitignore | 1 + src/tests/satellite.spec.ts | 90 +++++++++++----- src/tests/satellite.upgrade.spec.ts | 126 +++++++++++++++++++++++ src/tests/utils/satellite-tests.utils.ts | 61 +++++++++++ 4 files changed, 250 insertions(+), 28 deletions(-) create mode 100644 src/tests/satellite.upgrade.spec.ts create mode 100644 src/tests/utils/satellite-tests.utils.ts diff --git a/.gitignore b/.gitignore index df5396d61..35429f7f3 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ console.wasm mission_control.wasm observatory.wasm satellite.wasm +satellite-v0.0.15.wasm.gz out/ \ No newline at end of file diff --git a/src/tests/satellite.spec.ts b/src/tests/satellite.spec.ts index e2d8302df..dfd0ac366 100644 --- a/src/tests/satellite.spec.ts +++ b/src/tests/satellite.spec.ts @@ -4,27 +4,16 @@ import type { StorageConfig } from '$declarations/satellite/satellite.did'; import { idlFactory as idlFactorSatellite } from '$declarations/satellite/satellite.factory.did'; -import { IDL } from '@dfinity/candid'; +import { AnonymousIdentity } from '@dfinity/agent'; import { Ed25519KeyIdentity } from '@dfinity/identity'; import { arrayBufferToUint8Array, toNullable } from '@dfinity/utils'; import { PocketIc, type Actor } from '@hadronous/pic'; +import { toArray } from '@junobuild/utils'; import { parse } from '@ltd/j-toml'; -import { existsSync, readFileSync } from 'node:fs'; -import { join, resolve } from 'node:path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { afterAll, beforeAll, beforeEach, describe, expect } from 'vitest'; - -const WASM_PATH_LOCAL = resolve( - process.cwd(), - '.dfx', - 'local', - 'canisters', - 'satellite', - 'satellite.wasm' -); - -const WASM_PATH_CI = resolve(process.cwd(), 'satellite.wasm.gz'); - -const WASM_PATH = existsSync(WASM_PATH_CI) ? WASM_PATH_CI : WASM_PATH_LOCAL; +import { WASM_PATH, satelliteInitArgs } from './utils/satellite-tests.utils'; describe('Satellite', () => { let pic: PocketIc; @@ -35,19 +24,11 @@ describe('Satellite', () => { beforeAll(async () => { pic = await PocketIc.create(); - const arg = IDL.encode( - [ - IDL.Record({ - controllers: IDL.Vec(IDL.Principal) - }) - ], - [{ controllers: [controller.getPrincipal()] }] - ); - const { actor: c } = await pic.setupCanister({ idlFactory: idlFactorSatellite, wasm: WASM_PATH, - arg + arg: satelliteInitArgs(controller), + sender: controller.getPrincipal() }); actor = c; @@ -74,7 +55,7 @@ describe('Satellite', () => { }); it('should create a collection', async () => { - const { set_rule, list_rules, list_controllers } = actor; + const { set_rule, list_rules } = actor; await set_rule({ Db: null }, 'test', setRule); @@ -93,7 +74,7 @@ describe('Satellite', () => { }); it('should list collections', async () => { - const { list_rules, set_rule } = actor; + const { list_rules } = actor; const [ [collection, { updated_at, created_at, memory, mutable_permissions, read, write }], @@ -104,6 +85,7 @@ describe('Satellite', () => { expect(memory).toEqual(toNullable({ Heap: null })); expect(read).toEqual({ Managed: null }); expect(write).toEqual({ Managed: null }); + expect(mutable_permissions).toEqual([true]); expect(created_at).toBeGreaterThan(0n); expect(updated_at).toBeGreaterThan(0n); }); @@ -445,6 +427,58 @@ describe('Satellite', () => { }); }); + describe('user', () => { + const user = Ed25519KeyIdentity.generate(); + + beforeAll(() => { + actor.setIdentity(user); + }); + + it('should create a user', async () => { + const { set_doc, list_docs } = actor; + + await set_doc('#user', user.getPrincipal().toText(), { + data: await toArray({ + provider: 'internet_identity' + }), + description: toNullable(), + updated_at: toNullable() + }); + + const { items: users } = await list_docs('#user', { + matcher: toNullable(), + order: toNullable(), + owner: toNullable(), + paginate: toNullable() + }); + + expect(users).toHaveLength(1); + expect(users.find(([key]) => key === user.getPrincipal().toText())).not.toBeUndefined(); + }); + }); + + describe('anonymous', () => { + beforeAll(() => { + actor.setIdentity(new AnonymousIdentity()); + }); + + it('should create a user', async () => { + const { set_doc } = actor; + + const user = Ed25519KeyIdentity.generate(); + + await expect( + set_doc('#user', user.getPrincipal().toText(), { + data: await toArray({ + provider: 'internet_identity' + }), + description: toNullable(), + updated_at: toNullable() + }) + ).rejects.toThrow('Cannot write.'); + }); + }); + describe('hacking', () => { const hacker = Ed25519KeyIdentity.generate(); diff --git a/src/tests/satellite.upgrade.spec.ts b/src/tests/satellite.upgrade.spec.ts new file mode 100644 index 000000000..f9bd355c7 --- /dev/null +++ b/src/tests/satellite.upgrade.spec.ts @@ -0,0 +1,126 @@ +import type { _SERVICE as SatelliteActor } from '$declarations/satellite/satellite.did'; +import { idlFactory as idlFactorSatellite } from '$declarations/satellite/satellite.factory.did'; +import type { Identity } from '@dfinity/agent'; +import { Ed25519KeyIdentity } from '@dfinity/identity'; +import type { Principal } from '@dfinity/principal'; +import { toNullable } from '@dfinity/utils'; +import { PocketIc, type Actor } from '@hadronous/pic'; +import { toArray } from '@junobuild/utils'; +import { afterEach, beforeEach, describe, expect } from 'vitest'; +import { + WASM_PATH, + WASM_PATH_V0_0_15, + downloadSatelliteV0_0_15, + satelliteInitArgs +} from './utils/satellite-tests.utils'; + +describe('satellite upgrade v0.0.16', () => { + let pic: PocketIc; + let actor: Actor; + let canisterId: Principal; + + const controller = Ed25519KeyIdentity.generate(); + + beforeEach(async () => { + pic = await PocketIc.create(); + + await downloadSatelliteV0_0_15(); + + const { actor: c, canisterId: cId } = await pic.setupCanister({ + idlFactory: idlFactorSatellite, + wasm: WASM_PATH_V0_0_15, + arg: satelliteInitArgs(controller), + sender: controller.getPrincipal() + }); + + actor = c; + canisterId = cId; + actor.setIdentity(controller); + }); + + afterEach(async () => { + await pic?.tearDown(); + }); + + const upgrade = async () => { + // Prevent Error: Canister lxzze-o7777-77777-aaaaa-cai is rate limited because it executed too many instructions in the previous install_code messages. Please retry installation after several minutes. + for (let i = 0; i < 100; i++) { + await pic.tick(); + } + + await pic.upgradeCanister({ + canisterId, + wasm: WASM_PATH, + sender: controller.getPrincipal() + }); + }; + + const initUsers = async (): Promise => { + const { set_doc } = actor; + + const user1 = Ed25519KeyIdentity.generate(); + + await set_doc('#user', user1.getPrincipal().toText(), { + data: await toArray({ + provider: 'internet_identity' + }), + description: toNullable(), + updated_at: toNullable() + }); + + const user2 = Ed25519KeyIdentity.generate(); + + await set_doc('#user', user2.getPrincipal().toText(), { + data: await toArray({ + provider: 'internet_identity' + }), + description: toNullable(), + updated_at: toNullable() + }); + + return [user1, user2]; + }; + + const testUsers = async (users: Identity[]) => { + const { list_docs } = actor; + + const { items } = await list_docs('#user', { + matcher: toNullable(), + order: toNullable(), + owner: toNullable(), + paginate: toNullable() + }); + + expect(users).toHaveLength(users.length); + + for (const user of users) { + expect(items.find(([key]) => key === user.getPrincipal().toText())).not.toBeUndefined(); + } + }; + + it('should still list users from heap', async () => { + await initUsers(); + + const users = await initUsers(); + + await testUsers(users); + + await upgrade(); + + await testUsers(users); + }); + + it('should add users after upgrade and still list all users from heap', async () => { + await initUsers(); + + const users = await initUsers(); + + await testUsers(users); + + await upgrade(); + + const moreUsers = await initUsers(); + + await testUsers([...users, ...moreUsers]); + }); +}); diff --git a/src/tests/utils/satellite-tests.utils.ts b/src/tests/utils/satellite-tests.utils.ts new file mode 100644 index 000000000..0c6de9292 --- /dev/null +++ b/src/tests/utils/satellite-tests.utils.ts @@ -0,0 +1,61 @@ +import type { Identity } from '@dfinity/agent'; +import { IDL } from '@dfinity/candid'; +import { nonNullish } from '@dfinity/utils'; +import { existsSync, writeFileSync } from 'node:fs'; +import { get, type RequestOptions } from 'node:https'; +import { resolve } from 'node:path'; + +const WASM_PATH_LOCAL = resolve( + process.cwd(), + '.dfx', + 'local', + 'canisters', + 'satellite', + 'satellite.wasm' +); + +const WASM_PATH_CI = resolve(process.cwd(), 'satellite.wasm.gz'); + +export const WASM_PATH = existsSync(WASM_PATH_CI) ? WASM_PATH_CI : WASM_PATH_LOCAL; + +export const satelliteInitArgs = (controller: Identity): ArrayBuffer => + IDL.encode( + [ + IDL.Record({ + controllers: IDL.Vec(IDL.Principal) + }) + ], + [{ controllers: [controller.getPrincipal()] }] + ); + +const downloadFromURL = async (url: string | RequestOptions): Promise => { + return await new Promise((resolve, reject) => { + get(url, async (res) => { + if (nonNullish(res.statusCode) && [301, 302].includes(res.statusCode)) { + await downloadFromURL(res.headers.location!).then(resolve, reject); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data: any[] = []; + + res.on('data', (chunk) => data.push(chunk)); + res.on('end', () => { + resolve(Buffer.concat(data)); + }); + res.on('error', reject); + }); + }); +}; + +export const WASM_PATH_V0_0_15 = resolve(process.cwd(), 'satellite-v0.0.15.wasm.gz'); + +export const downloadSatelliteV0_0_15 = async () => { + if (existsSync(WASM_PATH_V0_0_15)) { + return; + } + + const buffer = await downloadFromURL( + 'https://github.com/junobuild/juno/releases/download/v0.0.26/satellite-v0.0.15.wasm.gz' + ); + writeFileSync(WASM_PATH_V0_0_15, buffer); +};