Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Gracefully handle CardanoTokenRegistry errors during getAsset request #412

Merged
merged 1 commit into from
Sep 13, 2022
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
10 changes: 9 additions & 1 deletion packages/cardano-services/src/Asset/CardanoTokenRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,15 @@ export class CardanoTokenRegistry implements TokenMetadataService {
}
}
} catch (error) {
throw toProviderError(error, 'while fetching metadata from token registry');
if (axios.isAxiosError(error)) {
throw new ProviderError(
ProviderFailure.ConnectionFailure,
error,
'CardanoTokenRegistry failed to fetch asset metatada from the token registry server'
);
}

throw error;
}

return tokenMetadata;
Expand Down
13 changes: 11 additions & 2 deletions packages/cardano-services/src/Asset/DbSyncAssetProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,17 @@ export class DbSyncAssetProvider extends DbSyncProvider implements AssetProvider
if (extraData?.history) await this.loadHistory(assetInfo);
if (extraData?.nftMetadata)
assetInfo.nftMetadata = await this.#dependencies.ntfMetadataService.getNftMetadata(assetInfo);
if (extraData?.tokenMetadata)
assetInfo.tokenMetadata = (await this.#dependencies.tokenMetadataService.getTokenMetadata([assetId]))[0];
if (extraData?.tokenMetadata) {
try {
assetInfo.tokenMetadata = (await this.#dependencies.tokenMetadataService.getTokenMetadata([assetId]))[0];
} catch (error) {
if (error instanceof ProviderError && error.reason === ProviderFailure.ConnectionFailure) {
assetInfo.tokenMetadata = undefined;
} else {
throw error;
}
}
}

return assetInfo;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/cardano-services/src/Asset/openApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@
}
},
"GetAssetRequest": {
"required": ["assetId"],
"required": [
"assetId"
],
"type": "object",
"properties": {
"assetId": {
Expand Down
61 changes: 40 additions & 21 deletions packages/cardano-services/test/Asset/CardanoTokenRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Asset, Cardano, ProviderError } from '@cardano-sdk/core';
import { Cardano, ProviderError, ProviderFailure } from '@cardano-sdk/core';
import { CardanoTokenRegistry, toCoreTokenMetadata } from '../../src/Asset';
import { InMemoryCache, Key } from '../../src/InMemoryCache';
import { IncomingMessage, createServer } from 'http';
Expand Down Expand Up @@ -135,11 +135,11 @@ describe('CardanoTokenRegistry', () => {
});

it('returns metadata when subject exists', async () => {
const [metadata] = await tokenRegistry.getTokenMetadata([validAssetId]);
const metadata = await tokenRegistry.getTokenMetadata([validAssetId]);

expect(metadata).not.toBeNull();
expect(metadata![0]).not.toBeNull();

const result: Asset.TokenMetadata = {
const result = {
decimals: 8,
desc: 'SingularityNET',
icon: 'testLogo',
Expand All @@ -148,17 +148,17 @@ describe('CardanoTokenRegistry', () => {
url: 'https://singularitynet.io/'
};

expect(metadata).toEqual(result);
expect(metadata![0]).toEqual(result);
});

it('correctly returns null or metadata for request with good and bad assetIds', async () => {
const firstResult = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]);
const secondResult = await tokenRegistry.getTokenMetadata([validAssetId, invalidAssetId]);

expect(firstResult[0]).toBeNull();
expect(firstResult[1]).not.toBeNull();
expect(secondResult[0]).not.toBeNull();
expect(secondResult[1]).toBeNull();
expect(firstResult![0]).toBeNull();
expect(firstResult![1]).not.toBeNull();
expect(secondResult![0]).not.toBeNull();
expect(secondResult![1]).toBeNull();
});
});

Expand Down Expand Up @@ -193,12 +193,12 @@ describe('CardanoTokenRegistry', () => {
});

it('metadata are cached', async () => {
const [firstResult] = await tokenRegistry.getTokenMetadata([validAssetId]);
const [secondResult] = await tokenRegistry.getTokenMetadata([validAssetId]);
const firstResult = await tokenRegistry.getTokenMetadata([validAssetId]);
const secondResult = await tokenRegistry.getTokenMetadata([validAssetId]);

expect(gotValues[0]).toBeUndefined();
expect(gotValues[1]).toEqual(firstResult);
expect(firstResult).toEqual(secondResult);
expect(gotValues[1]).toEqual(firstResult![0]);
expect(firstResult![0]).toEqual(secondResult![0]);
});
});

Expand All @@ -214,18 +214,33 @@ describe('CardanoTokenRegistry', () => {
({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(() => ({ body: { subjects: [null] } })));
const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl });

await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(ProviderError);
await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(
new ProviderError(
ProviderFailure.Unknown,
undefined,
"Cannot destructure property 'subject' of 'record' as it is null. while evaluating metatada record null"
)
);
});

it('record without the subject property', async () => {
const record = { test: 'test' };
({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(() => ({ body: { subjects: [record] } })));
const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl });

await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(ProviderError);
await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(
new ProviderError(
ProviderFailure.InvalidResponse,
undefined,
'Missing \'subject\' property in metadata record {"test":"test"}'
)
);
});

it('internal server error', async () => {
const failedMetadata = null;
const succeededMetadata = { name: 'test' };

let alreadyCalled = false;
const record = () => {
if (alreadyCalled) return { body: {}, code: 500 };
Expand All @@ -243,12 +258,16 @@ describe('CardanoTokenRegistry', () => {

({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(record));
const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl });
const result = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]);

await expect(tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId])).rejects.toThrow(ProviderError);

expect(result[0]).toBeNull();
expect(result[1]).toStrictEqual({ name: 'test' });
const firstSucceedResult = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]);
expect(firstSucceedResult).toEqual([failedMetadata, succeededMetadata]);

await expect(tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId])).rejects.toThrow(
new ProviderError(
ProviderFailure.ConnectionFailure,
null,
'CardanoTokenRegistry failed to fetch asset metatada from the token registry server'
)
);
});
});
});
23 changes: 21 additions & 2 deletions packages/cardano-services/test/Asset/DbSyncAssetProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Asset, Cardano, ProviderError } from '@cardano-sdk/core';
/* eslint-disable @typescript-eslint/no-shadow */
import { Asset, Cardano, ProviderError, ProviderFailure } from '@cardano-sdk/core';
import {
CardanoTokenRegistry,
DbSyncAssetProvider,
Expand Down Expand Up @@ -43,7 +44,9 @@ describe('DbSyncAssetProvider', () => {
});

it('rejects for not found assetId', async () => {
await expect(provider.getAsset({ assetId: notValidAssetId })).rejects.toThrow(ProviderError);
await expect(provider.getAsset({ assetId: notValidAssetId })).rejects.toThrow(
new ProviderError(ProviderFailure.NotFound, undefined, 'No entries found in multi_asset table')
);
});

it('returns an AssetInfo without extra data', async () => {
Expand Down Expand Up @@ -80,4 +83,20 @@ describe('DbSyncAssetProvider', () => {
name: 'macaron cake token'
});
});

it('returns undefined asset token metadata if the token registry throws a network error', async () => {
const { tokenMetadataServerUrl, closeMock } = await mockTokenRegistry(() => ({ body: {}, code: 500 }));
const tokenMetadataService = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl });

provider = new DbSyncAssetProvider({ db, logger, ntfMetadataService, tokenMetadataService });

const asset = await provider.getAsset({
assetId: validAssetId,
extraData: { tokenMetadata: true }
});

expect(asset.tokenMetadata).toBeUndefined();
tokenMetadataService.shutdown();
await closeMock();
});
});