From 6309d0619a46b68e78748e25fa16d34fedc2d0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rard=20Dethier?= Date: Wed, 12 Jun 2024 16:52:23 +0200 Subject: [PATCH] fix: client failed to connect with guest LLO. logion-network/logion-internal#1294 --- packages/client/package.json | 2 +- packages/client/src/DirectoryClient.ts | 10 +- packages/client/test/DirectoryClient.spec.ts | 109 +++++++++++++++++++ packages/client/test/Utils.ts | 25 ++++- 4 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 packages/client/test/DirectoryClient.spec.ts diff --git a/packages/client/package.json b/packages/client/package.json index 874fba9a..852b08f2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@logion/client", - "version": "0.45.0", + "version": "0.45.1-1", "description": "logion SDK for client applications", "main": "dist/index.js", "packageManager": "yarn@3.2.0", diff --git a/packages/client/src/DirectoryClient.ts b/packages/client/src/DirectoryClient.ts index 0b4c6ae9..333439ea 100644 --- a/packages/client/src/DirectoryClient.ts +++ b/packages/client/src/DirectoryClient.ts @@ -96,10 +96,18 @@ export class DirectoryClient { private getHost(address: string, data: Record): PalletLoAuthorityListLegalOfficerData { const hostOrGuest = data[address]; + if(!hostOrGuest) { + throw new Error(`No data for address ${ address }`); + } if(hostOrGuest.isHost) { return hostOrGuest; } else { - return data[hostOrGuest.asGuest.toString()]; + const hostAddress = hostOrGuest.asGuest.hostId.toString(); + const host = data[hostAddress]; + if(!host) { + throw new Error(`No host with address ${ hostAddress }`); + } + return host; } } diff --git a/packages/client/test/DirectoryClient.spec.ts b/packages/client/test/DirectoryClient.spec.ts new file mode 100644 index 00000000..6644e14e --- /dev/null +++ b/packages/client/test/DirectoryClient.spec.ts @@ -0,0 +1,109 @@ +import { LogionNodeApiClass, Region, ValidAccountId } from "@logion/node-api"; +import { AxiosInstance, AxiosResponse } from "axios"; +import { Mock } from "moq.ts"; +import { AccountId32 } from "@polkadot/types/interfaces/types.js"; +import type { Bytes } from '@polkadot/types-codec'; +import { PalletLoAuthorityListLegalOfficerData, PalletLoAuthorityListHostData, PalletLoAuthorityListGuestData, LogionRuntimeRegion } from "@polkadot/types/lookup"; +import { AxiosFactory, DirectoryClient, DirectoryLegalOfficer } from "../src/index.js"; +import { EMPTY_POSTAL_ADDRESS, EMPTY_USER_IDENTITY, mockCodecWithToString, mockCodecWithToUtf8, mockOption, mockStorageKey } from "./Utils.js"; + +describe("DirectoryClient", () => { + + it("handles guest LLOs", async () => { + const api = mockApi(); + const axiosFactory = mockAxiosFactory(); + const client = new DirectoryClient(api, ENDPOINT, axiosFactory); + + const legalOfficers = await client.getLegalOfficers(); + + expect(legalOfficers.length).toBe(2); + + expect(legalOfficers[0].node).toBe(BASE_URL); + expect(legalOfficers[0].account).toEqual(ValidAccountId.polkadot(HOST_ADDRESS)); + expect(legalOfficers[0].region).toBe(REGION_TYPE); + expect(legalOfficers[0].nodeId).toBe(PEER_ID); + + expect(legalOfficers[1].node).toBe(BASE_URL); + expect(legalOfficers[1].account).toEqual(ValidAccountId.polkadot(GUEST_ADDRESS)); + expect(legalOfficers[1].region).toBe(REGION_TYPE); + expect(legalOfficers[1].nodeId).toBe(PEER_ID); + }); +}); + +const ENDPOINT = "https://test-directory.logion.network"; + +function mockApi(): LogionNodeApiClass { + const api = new Mock(); + api.setup(instance => instance.polkadot.query.loAuthorityList.legalOfficerSet.entries()).returnsAsync([ + [ + mockStorageKey([mockCodecWithToString(HOST_ADDRESS)]), + mockOption(mockHost()), + ], + [ + mockStorageKey([mockCodecWithToString(GUEST_ADDRESS)]), + mockOption(mockGuest()), + ] + ]); + api.setup(instance => instance.adapters.fromLogionRuntimeRegion(REGION)).returns("Europe"); + return api.object(); +} + +const HOST_ADDRESS = "vQvWaxNDdzuX5N3qSvGMtjdHcQdw1TAcPNgx4S1Utd3MTxYeN"; +const GUEST_ADDRESS = "vQvZF2YMgKuQhzfF7T3xDjHjuEmcPSUVEoUDPy1mzuSXzFgca"; +const REGION_TYPE: Region = "Europe"; +const REGION = mockCodecWithToString(REGION_TYPE); + +function mockAxiosFactory(): AxiosFactory { + const axios = mockAxios(); + const factory = new Mock(); + factory.setup(instance => instance.buildAxiosInstance(ENDPOINT, undefined)).returns(axios); + return factory.object(); +} + +function mockAxios(): AxiosInstance { + const axios = new Mock(); + const response = new Mock(); + const legalOfficers: DirectoryLegalOfficer[] = [ + { + address: HOST_ADDRESS, + additionalDetails: "", + postalAddress: EMPTY_POSTAL_ADDRESS, + userIdentity: EMPTY_USER_IDENTITY, + }, + { + address: GUEST_ADDRESS, + additionalDetails: "", + postalAddress: EMPTY_POSTAL_ADDRESS, + userIdentity: EMPTY_USER_IDENTITY, + }, + ]; + response.setup(instance => instance.data.legalOfficers).returns(legalOfficers); + axios.setup(instance => instance.get("/api/legal-officer")).returnsAsync(response.object()); + return axios.object(); +} + +function mockHost(): PalletLoAuthorityListLegalOfficerData { + const host = new Mock(); + host.setup(instance => instance.isHost).returns(true); + const hostData = new Mock(); + hostData.setup(instance => instance.baseUrl) + .returns(mockOption(mockCodecWithToUtf8(BASE_URL))); + hostData.setup(instance => instance.nodeId) + .returns(mockOption(mockCodecWithToString(PEER_ID))); + hostData.setup(instance => instance.region) + .returns(REGION); + host.setup(instance => instance.asHost).returns(hostData.object()); + return host.object(); +} + +const BASE_URL = "https://test-node.logion.network"; +const PEER_ID = "12D3KooWJvyP3VJYymTqG7eH4PM5rN4T2agk5cdNCfNymAqwqcvZ"; + +function mockGuest(): PalletLoAuthorityListLegalOfficerData { + const guest = new Mock(); + guest.setup(instance => instance.isHost).returns(false); + const guestData = new Mock(); + guestData.setup(instance => instance.hostId).returns(mockCodecWithToString(HOST_ADDRESS)); + guest.setup(instance => instance.asGuest).returns(guestData.object()); + return guest.object(); +} diff --git a/packages/client/test/Utils.ts b/packages/client/test/Utils.ts index 9abe38d2..7ff736c7 100644 --- a/packages/client/test/Utils.ts +++ b/packages/client/test/Utils.ts @@ -1,8 +1,9 @@ import { DateTime } from "luxon"; import { ApiPromise } from "@polkadot/api"; +import type { StorageKey } from '@polkadot/types'; import { SubmittableExtrinsic } from '@polkadot/api/promise/types'; import { Option, Vec, bool } from "@polkadot/types-codec"; -import type { Codec } from '@polkadot/types-codec/types'; +import type { Codec, AnyTuple } from '@polkadot/types-codec/types'; import { AccountTokens, @@ -285,3 +286,25 @@ export class MockFile extends File { } export const MOCK_FILE = new MockFile(); + +export function mockStorageKey(args: T): StorageKey { + const key = new Mock>(); + key.setup(instance => instance.args).returns(args); + return key.object(); +} + +export const EMPTY_POSTAL_ADDRESS: LegalOfficerPostalAddress = { + city: "", + company: "", + country: "", + line1: "", + line2: "", + postalCode: "", +}; + +export const EMPTY_USER_IDENTITY: UserIdentity = { + email: "", + firstName: "", + lastName: "", + phoneNumber: "", +};