From d5faf67dd94e40e463ecdf836089aafd51072559 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 24 Jul 2024 16:52:47 -0400 Subject: [PATCH] PorterClient: test and fix for 'tryAndCall' --- packages/shared/package.json | 1 + packages/shared/src/porter.ts | 15 ++++- packages/shared/test/porter.test.ts | 91 +++++++++++++++++++++++++++++ packages/shared/tsconfig.json | 5 ++ packages/test-utils/src/utils.ts | 17 ------ pnpm-lock.yaml | 3 + 6 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 packages/shared/test/porter.test.ts diff --git a/packages/shared/package.json b/packages/shared/package.json index 7b3ee3832..7ad14db95 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -51,6 +51,7 @@ "zod": "*" }, "devDependencies": { + "@nucypher/test-utils": "workspace:*", "@typechain/ethers-v5": "^11.1.2", "@types/deep-equal": "^1.0.3", "@types/qs": "^6.9.15", diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index c9432d0db..0d6d5d16c 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -145,14 +145,23 @@ export class PorterClient { protected async tryAndCall(config: AxiosRequestConfig): Promise> { let resp!: AxiosResponse; + let lastError = undefined; for (const porterUrl of this.porterUrls) { - config.baseURL = porterUrl.toString(); - resp = await axios.request(config); + const localConfig = { ...config, baseURL: porterUrl.toString() } + try { + resp = await axios.request(localConfig); + } catch (e) { + lastError = e; + continue; + } if (resp.status === HttpStatusCode.Ok) { return resp; } } - return resp; + if (lastError !== undefined) { + throw lastError; + } + throw new Error("Porter returns bad response"); } public async getUrsulas( diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts new file mode 100644 index 000000000..113f3012a --- /dev/null +++ b/packages/shared/test/porter.test.ts @@ -0,0 +1,91 @@ +import { + initialize, + GetUrsulasResult, + PorterClient, + toHexString, + Ursula, +} from '../src'; +import { fakeUrsulas } from '@nucypher/test-utils'; +import { beforeAll, describe, expect, SpyInstance, it, vi } from 'vitest'; +import axios, { HttpStatusCode } from 'axios'; + +const fakePorterUris = ['https://_this_should_crash.com/', 'https://2_this_should_crash.com/', 'https://_this_should_work.com/']; + +const mockGetUrsulas = ( + ursulas: Ursula[] = fakeUrsulas(), +): SpyInstance => { + const fakePorterUrsulas = ( + mockUrsulas: readonly Ursula[], + ): GetUrsulasResult => { + return { + result: { + ursulas: mockUrsulas.map(({ encryptingKey, uri, checksumAddress }) => ({ + encrypting_key: toHexString(encryptingKey.toCompressedBytes()), + uri: uri, + checksum_address: checksumAddress, + })), + }, + version: '5.2.0', + }; + }; + + return vi.spyOn(axios, 'request').mockImplementation(async (config) => { + switch (config.baseURL) { + case fakePorterUris[2]: + return Promise.resolve({ status: HttpStatusCode.Ok, data: fakePorterUrsulas(ursulas) }); + case fakePorterUris[0]: + throw new Error(); + default: + throw Promise.resolve({ status: HttpStatusCode.BadRequest }); + } + }); +}; + +describe('PorterClient', () => { + beforeAll(async () => { + await initialize(); + }); + + it('Get Ursulas', async () => { + const ursulas = fakeUrsulas(); + const getUrsulasSpy = mockGetUrsulas(ursulas); + const porterClient = new PorterClient(fakePorterUris); + const result = await porterClient.getUrsulas(ursulas.length); + + expect(result.every((u: Ursula, index: number) => { + const expectedUrsula = ursulas[index]; + return u.checksumAddress === expectedUrsula.checksumAddress && + u.uri === expectedUrsula.uri && + u.encryptingKey.equals(expectedUrsula.encryptingKey); + })).toBeTruthy(); + const params = { + method: 'get', + url: "/get_ursulas", + params: { + exclude_ursulas: [], + include_ursulas: [], + quantity: ursulas.length, + } + }; + + expect(getUrsulasSpy).toBeCalledTimes(fakePorterUris.length); + expect(getUrsulasSpy).toHaveBeenNthCalledWith(1, expect.objectContaining( + { ...params, baseURL: fakePorterUris[0] }) + ); + expect(getUrsulasSpy).toHaveBeenNthCalledWith(2, expect.objectContaining( + { ...params, baseURL: fakePorterUris[1] }) + ); + expect(getUrsulasSpy).toHaveBeenNthCalledWith(3, expect.objectContaining( + { ...params, baseURL: fakePorterUris[2] })); + }); + + it('returns error in case all porters fail', async () => { + const ursulas = fakeUrsulas(); + mockGetUrsulas(ursulas); + let porterClient = new PorterClient([fakePorterUris[0], fakePorterUris[1]]); + expect(porterClient.getUrsulas(ursulas.length)).rejects.toThrowError(); + porterClient = new PorterClient([fakePorterUris[1], fakePorterUris[0]]); + expect(porterClient.getUrsulas(ursulas.length)).rejects.toThrowError(); + }); + +}); diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 0c3f3ec10..e17152bcf 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -6,4 +6,9 @@ "skipLibCheck": true, "resolveJsonModule": true, }, + "references": [ + { + "path": "../test-utils/tsconfig.es.json", + }, + ], } diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 06095b125..07ed9dc1c 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -31,11 +31,9 @@ import { import { ChecksumAddress, DkgCoordinatorAgent, - GetUrsulasResult, PorterClient, RetrieveCFragsResult, TacoDecryptResult, - toHexString, Ursula, zip, } from '@nucypher/shared'; @@ -132,21 +130,6 @@ export const fakeUrsulas = (n = 4): Ursula[] => export const mockGetUrsulas = ( ursulas: Ursula[] = fakeUrsulas(), ): SpyInstance => { - // const fakePorterUrsulas = ( - // mockUrsulas: readonly Ursula[], - // ): GetUrsulasResult => { - // return { - // result: { - // ursulas: mockUrsulas.map(({ encryptingKey, uri, checksumAddress }) => ({ - // encrypting_key: toHexString(encryptingKey.toCompressedBytes()), - // uri: uri, - // checksum_address: checksumAddress, - // })), - // }, - // version: '5.2.0', - // }; - // }; - return vi.spyOn(PorterClient.prototype, 'getUrsulas').mockImplementation(async () => { return Promise.resolve(ursulas); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23365a723..05c6fe293 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -516,6 +516,9 @@ importers: specifier: '*' version: 3.23.8 devDependencies: + '@nucypher/test-utils': + specifier: workspace:* + version: link:../test-utils '@typechain/ethers-v5': specifier: ^11.1.2 version: 11.1.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typechain@8.3.2(typescript@5.4.5))(typescript@5.4.5)