Skip to content

Commit

Permalink
chore(portfolio): refactor token discovery single endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
stackchain committed May 7, 2024
1 parent 8a4d8ec commit e185dab
Show file tree
Hide file tree
Showing 19 changed files with 263 additions and 222 deletions.
2 changes: 2 additions & 0 deletions packages/common/src/api/getApiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const getApiError = (error: Api.ResponseError) => {
return new Api.Errors.Network(error.message)
case -2:
return new Api.Errors.InvalidState(error.message)
case -3:
return new Api.Errors.ResponseMalformed(error.message)
case 400:
return new Api.Errors.BadRequest(error.message)
case 401:
Expand Down
18 changes: 9 additions & 9 deletions packages/portfolio/src/adapters/dullahan-api/api-maker.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {Api, Portfolio} from '@yoroi/types'
import {tokenMocks} from '../token.mocks'

export const responseTokenDiscoveriesMocks = asyncBehavior.maker<
Api.Response<Portfolio.Api.TokenDiscoveriesResponse>
Api.Response<Portfolio.Api.TokenDiscoveryResponse>
>({
data: {
tag: 'right',
value: {status: 200, data: tokenMocks.apiResponse.tokenDiscoveries},
value: {status: 200, data: tokenMocks.apiResponse.tokenDiscovery},
},
emptyRepresentation: [],
emptyRepresentation: null,
})

export const responseTokenInfosMocks = asyncBehavior.maker<
Expand All @@ -21,26 +21,26 @@ export const responseTokenInfosMocks = asyncBehavior.maker<
tag: 'right',
value: {status: 200, data: tokenMocks.apiResponse.tokenInfos},
},
emptyRepresentation: [],
emptyRepresentation: null,
})

const success: Portfolio.Api.Api = {
tokenDiscoveries: responseTokenDiscoveriesMocks.success,
tokenDiscovery: responseTokenDiscoveriesMocks.success,
tokenInfos: responseTokenInfosMocks.success,
}

const delayed: Portfolio.Api.Api = {
tokenDiscoveries: responseTokenDiscoveriesMocks.delayed,
tokenDiscovery: responseTokenDiscoveriesMocks.delayed,
tokenInfos: responseTokenInfosMocks.delayed,
}

const loading: Portfolio.Api.Api = {
tokenDiscoveries: responseTokenDiscoveriesMocks.loading,
tokenDiscovery: responseTokenDiscoveriesMocks.loading,
tokenInfos: responseTokenInfosMocks.loading,
}

const error: Portfolio.Api.Api = {
tokenDiscoveries: () =>
tokenDiscovery: () =>
Promise.resolve({
tag: 'left',
error: {
Expand All @@ -61,7 +61,7 @@ const error: Portfolio.Api.Api = {
}

const empty: Portfolio.Api.Api = {
tokenDiscoveries: responseTokenDiscoveriesMocks.empty,
tokenDiscovery: responseTokenDiscoveriesMocks.empty,
tokenInfos: responseTokenInfosMocks.empty,
}

Expand Down
140 changes: 122 additions & 18 deletions packages/portfolio/src/adapters/dullahan-api/api-maker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Api, Chain, Portfolio} from '@yoroi/types'

import {apiConfig, portfolioApiMaker} from './api-maker'
import {DullahanApiCachedIdsRequest} from './types'
import {tokenDiscoveryMocks} from '../token-discovery.mocks'

describe('portfolioApiMaker', () => {
const mockNetwork: Chain.Network = Chain.Network.Mainnet
Expand All @@ -19,7 +20,7 @@ describe('portfolioApiMaker', () => {

expect(Object.isFrozen(api)).toBe(true)
expect(api).toBeDefined()
expect(api).toHaveProperty('tokenDiscoveries')
expect(api).toHaveProperty('tokenDiscovery')
expect(api).toHaveProperty('tokenInfos')
})

Expand All @@ -29,7 +30,7 @@ describe('portfolioApiMaker', () => {
})

expect(api).toBeDefined()
expect(api).toHaveProperty('tokenDiscoveries')
expect(api).toHaveProperty('tokenDiscovery')
expect(api).toHaveProperty('tokenInfos')
})

Expand All @@ -52,14 +53,16 @@ describe('portfolioApiMaker', () => {
'token.id:etag-hash',
]

await api.tokenDiscoveries(mockTokenIdsWithCache)
await api.tokenDiscovery(tokenDiscoveryMocks.nftCryptoKitty.id)
await api.tokenInfos(mockTokenIdsWithCache)

expect(mockRequest).toHaveBeenCalledTimes(2)
expect(mockRequest).toHaveBeenCalledWith({
method: 'post',
url: apiConfig[Chain.Network.Mainnet].tokenDiscoveries,
data: mockTokenIdsWithCacheRequest,
method: 'get',
url:
apiConfig[Chain.Network.Mainnet].tokenDiscovery +
'/' +
tokenDiscoveryMocks.nftCryptoKitty.id,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
Expand Down Expand Up @@ -97,19 +100,23 @@ describe('portfolioApiMaker', () => {
'token.id:etag-hash',
]

await api.tokenDiscoveries(mockTokenIdsWithCache)
const result = await api.tokenInfos(mockTokenIdsWithCache)
await api.tokenDiscovery(tokenDiscoveryMocks.nftCryptoKitty.id)

expect(mockRequest).toHaveBeenCalledTimes(2)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith({
method: 'post',
url: apiConfig[Chain.Network.Mainnet].tokenDiscoveries,
data: mockTokenIdsWithCacheRequest,
method: 'get',
url:
apiConfig[Chain.Network.Mainnet].tokenDiscovery +
'/' +
tokenDiscoveryMocks.nftCryptoKitty.id,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})

const result = await api.tokenInfos(mockTokenIdsWithCache)
expect(mockRequest).toHaveBeenCalledTimes(2)
expect(mockRequest).toHaveBeenCalledWith({
method: 'post',
url: apiConfig[Chain.Network.Mainnet].tokenInfos,
Expand Down Expand Up @@ -152,27 +159,124 @@ describe('portfolioApiMaker', () => {
'token.id:etag-hash',
]

await api.tokenDiscoveries(mockTokenIdsWithCache)
await api.tokenInfos(mockTokenIdsWithCache)

expect(mockRequest).toHaveBeenCalledTimes(2)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith({
method: 'post',
url: apiConfig[Chain.Network.Mainnet].tokenDiscoveries,
url: apiConfig[Chain.Network.Mainnet].tokenInfos,
data: mockTokenIdsWithCacheRequest,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})

await api.tokenDiscovery(tokenDiscoveryMocks.nftCryptoKitty.id)
expect(mockRequest).toHaveBeenCalledTimes(2)
expect(mockRequest).toHaveBeenCalledWith({
method: 'post',
url: apiConfig[Chain.Network.Mainnet].tokenInfos,
data: mockTokenIdsWithCacheRequest,
method: 'get',
url:
apiConfig[Chain.Network.Mainnet].tokenDiscovery +
'/' +
tokenDiscoveryMocks.nftCryptoKitty.id,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
})

it('should return error when returning data is malformed token-discovery', async () => {
mockRequest.mockResolvedValue({
tag: 'right',
value: {
status: 200,
data: 0,
},
})
const api = portfolioApiMaker({
network: mockNetwork,
request: mockRequest,
})

const wrong = await api.tokenDiscovery(
tokenDiscoveryMocks.nftCryptoKitty.id,
)

expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith({
method: 'get',
url:
apiConfig[Chain.Network.Mainnet].tokenDiscovery +
'/' +
tokenDiscoveryMocks.nftCryptoKitty.id,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})

expect(wrong).toEqual({
tag: 'left',
error: {
status: -3,
message: 'Failed to transform token discovery response',
responseData: 0,
},
})

mockRequest.mockResolvedValue({
tag: 'right',
value: {
status: 200,
data: {
...tokenDiscoveryMocks.nftCryptoKitty,
supply: undefined,
},
},
})

const missing = await api.tokenDiscovery(
tokenDiscoveryMocks.nftCryptoKitty.id,
)

expect(missing).toEqual({
tag: 'left',
error: {
status: -3,
message: 'Failed to transform token discovery response',
responseData: {
...tokenDiscoveryMocks.nftCryptoKitty,
supply: undefined,
},
},
})
expect(mockRequest).toHaveBeenCalledTimes(2)

mockRequest.mockResolvedValue({
tag: 'right',
value: {
status: 200,
data: {
...tokenDiscoveryMocks.nftCryptoKitty,
supply: '1234',
},
},
})

const right = await api.tokenDiscovery(
tokenDiscoveryMocks.nftCryptoKitty.id,
)
expect(right).toEqual({
tag: 'right',
value: {
status: 200,
data: {
...tokenDiscoveryMocks.nftCryptoKitty,
supply: 1234n,
},
},
})
})
})
76 changes: 60 additions & 16 deletions packages/portfolio/src/adapters/dullahan-api/api-maker.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {FetchData, fetchData, isLeft} from '@yoroi/common'
import {FetchData, fetchData, isLeft, isRecord, isRight} from '@yoroi/common'
import {Api, Chain, Portfolio} from '@yoroi/types'
import {freeze} from 'immer'

import {ApiConfig} from '../../types'
import {toDullahanRequest, toSecondaryTokenInfos} from './transformers'
import {
DullahanApiCachedIdsRequest,
DullahanApiTokenDiscoveryResponse,
DullahanApiTokenInfosResponse,
DullahanTokenDiscovery,
} from './types'
import {parseTokenDiscovery} from '../../validators/token-discovery'

export const portfolioApiMaker = ({
network,
Expand All @@ -19,19 +22,47 @@ export const portfolioApiMaker = ({
const config = apiConfig[network]
return freeze(
{
async tokenDiscoveries(idsWithCache) {
return request<
Portfolio.Api.TokenDiscoveriesResponse,
DullahanApiCachedIdsRequest
>({
method: 'post',
url: config.tokenDiscoveries,
data: toDullahanRequest(idsWithCache),
async tokenDiscovery(id) {
const response = await request<DullahanApiTokenDiscoveryResponse>({
method: 'get',
url: `${config.tokenDiscovery}/${id}`,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
})
if (isRight(response)) {
const discovery: Portfolio.Token.Discovery | undefined = transformer(
response.value.data,
)

if (!discovery) {
return freeze(
{
tag: 'left',
error: {
status: -3,
message: 'Failed to transform token discovery response',
responseData: response.value.data,
},
},
true,
)
}

return freeze(
{
tag: 'right',
value: {
status: response.value.status,
data: discovery,
},
},
true,
)
}

return response
},
async tokenInfos(idsWithCache) {
const response = await request<
Expand Down Expand Up @@ -85,25 +116,38 @@ export const portfolioApiMaker = ({
)
}

function transformer(
tokenDiscovery: DullahanTokenDiscovery,
): Portfolio.Token.Discovery | undefined {
if (isRecord(tokenDiscovery)) {
const {supply, ...rest} = tokenDiscovery
return parseTokenDiscovery({
...rest,
supply: supply ? BigInt(supply) : 'error',
})
}
return
}

export const apiConfig: ApiConfig = freeze(
{
mainnet: {
tokenDiscoveries:
'https://add50d9d-76d7-47b7-b17f-e34021f63a02.mock.pstmn.io/v1/token-discoveries',
tokenDiscovery:
'https://dev-yoroi-backend-zero-mainnet.emurgornd.com/token/discovery',
tokenInfos:
'https://dev-yoroi-backend-zero-mainnet.emurgornd.com/tokens/info/multi',
},
preprod: {
tokenDiscoveries:
'https://add50d9d-76d7-47b7-b17f-e34021f63a02.mock.pstmn.io/v1/token-discoveries',
tokenDiscovery:
'https://dev-yoroi-backend-zero-preprod.emurgornd.com/token/discovery',
tokenInfos:
'https://dev-yoroi-backend-zero-preprod.emurgornd.com/tokens/info/multi',
},
sancho: {
tokenDiscoveries:
'https://yoroi-backend-zero-mainnet.emurgornd.com/stakekeys/{{STAKE_KEY_HASH}}/state',
tokenDiscovery:
'https://dev-yoroi-backend-zero-preprod.emurgornd.com/token/discovery',
tokenInfos:
'https://yoroi-backend-zero-mainnet.emurgornd.com/dreps/{{DREP_ID}}/state',
'https://dev-yoroi-backend-zero-preprod.emurgornd.com/tokens/info/multi',
},
},
true,
Expand Down

0 comments on commit e185dab

Please sign in to comment.