-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
IPortfolio
datasource with Octav implementation
- Loading branch information
Showing
12 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
src/datasources/portfolio-api/octav-api.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { faker } from '@faker-js/faker'; | ||
import { getAddress } from 'viem'; | ||
import { FakeConfigurationService } from '@/config/__tests__/fake.configuration.service'; | ||
import { DataSourceError } from '@/domain/errors/data-source.error'; | ||
import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; | ||
import { NetworkResponseError } from '@/datasources/network/entities/network.error.entity'; | ||
import { OctavApi } from '@/datasources/portfolio-api/octav-api.service'; | ||
import { rawify } from '@/validation/entities/raw.entity'; | ||
import type { INetworkService } from '@/datasources/network/network.service.interface'; | ||
import type { Portfolio } from '@/domain/portfolio/entities/portfolio.entity'; | ||
|
||
const mockNetworkService = jest.mocked({ | ||
get: jest.fn(), | ||
} as jest.MockedObjectDeep<INetworkService>); | ||
|
||
describe('OctavApiService', () => { | ||
let target: OctavApi; | ||
let fakeConfigurationService: FakeConfigurationService; | ||
let httpErrorFactory: HttpErrorFactory; | ||
let baseUri: string; | ||
let apiKey: string; | ||
|
||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
|
||
fakeConfigurationService = new FakeConfigurationService(); | ||
httpErrorFactory = new HttpErrorFactory(); | ||
baseUri = faker.internet.url({ appendSlash: false }); | ||
apiKey = faker.string.sample(); | ||
fakeConfigurationService.set('portfolio.baseUri', baseUri); | ||
fakeConfigurationService.set('portfolio.apiKey', apiKey); | ||
|
||
target = new OctavApi( | ||
fakeConfigurationService, | ||
mockNetworkService, | ||
httpErrorFactory, | ||
); | ||
}); | ||
|
||
it('should error if baseUri is not defined', () => { | ||
const httpErrorFactory = new HttpErrorFactory(); | ||
const _fakeConfigurationService = new FakeConfigurationService(); | ||
fakeConfigurationService.set('portfolio.apiKey', apiKey); | ||
|
||
expect( | ||
() => | ||
new OctavApi( | ||
_fakeConfigurationService, | ||
mockNetworkService, | ||
httpErrorFactory, | ||
), | ||
).toThrow(); | ||
}); | ||
|
||
it('should error if apiKey is not defined', () => { | ||
const httpErrorFactory = new HttpErrorFactory(); | ||
const _fakeConfigurationService = new FakeConfigurationService(); | ||
fakeConfigurationService.set('portfolio.baseUri', baseUri); | ||
|
||
expect( | ||
() => | ||
new OctavApi( | ||
_fakeConfigurationService, | ||
mockNetworkService, | ||
httpErrorFactory, | ||
), | ||
).toThrow(); | ||
}); | ||
|
||
describe('getPortfolio', () => { | ||
it('should get portfolio', async () => { | ||
const safeAddress = getAddress(faker.finance.ethereumAddress()); | ||
const portfolio: Portfolio = {}; | ||
mockNetworkService.get.mockResolvedValueOnce({ | ||
status: 200, | ||
data: rawify(portfolio), | ||
}); | ||
|
||
await target.getPortfolio(safeAddress); | ||
|
||
expect(mockNetworkService.get).toHaveBeenCalledWith({ | ||
url: `${baseUri}/api/rest/portfolio`, | ||
networkRequest: { | ||
headers: { | ||
Authorization: `Bearer ${apiKey}`, | ||
}, | ||
params: { | ||
addresses: safeAddress, | ||
includeImages: true, | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
it('should forward error', async () => { | ||
const safeAddress = getAddress(faker.finance.ethereumAddress()); | ||
const status = faker.internet.httpStatusCode({ types: ['serverError'] }); | ||
const error = new NetworkResponseError( | ||
new URL(`${baseUri}/api/rest/portfolio`), | ||
{ | ||
status, | ||
} as Response, | ||
{ | ||
message: 'Unexpected error', | ||
}, | ||
); | ||
mockNetworkService.get.mockRejectedValueOnce(error); | ||
|
||
await expect(target.getPortfolio(safeAddress)).rejects.toThrow( | ||
new DataSourceError('Unexpected error', status), | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Inject, Injectable } from '@nestjs/common'; | ||
import { IConfigurationService } from '@/config/configuration.service.interface'; | ||
import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; | ||
import { | ||
NetworkService, | ||
INetworkService, | ||
} from '@/datasources/network/network.service.interface'; | ||
import { IPortfolioApi } from '@/domain/interfaces/portfolio-api.interface'; | ||
import { Portfolio } from '@/domain/portfolio/entities/portfolio.entity'; | ||
import { Raw } from '@/validation/entities/raw.entity'; | ||
|
||
@Injectable() | ||
export class OctavApi implements IPortfolioApi { | ||
private readonly baseUri: string; | ||
private readonly apiKey: string; | ||
|
||
constructor( | ||
@Inject(IConfigurationService) | ||
private readonly configurationService: IConfigurationService, | ||
@Inject(NetworkService) | ||
private readonly networkService: INetworkService, | ||
private readonly httpErrorFactory: HttpErrorFactory, | ||
) { | ||
this.baseUri = | ||
this.configurationService.getOrThrow<string>('portfolio.baseUri'); | ||
this.apiKey = | ||
this.configurationService.getOrThrow<string>('portfolio.apiKey'); | ||
} | ||
|
||
async getPortfolio(safeAddress: `0x${string}`): Promise<Raw<Portfolio>> { | ||
try { | ||
const url = `${this.baseUri}/api/rest/portfolio`; | ||
const { data: portfolio } = await this.networkService.get<Portfolio>({ | ||
url, | ||
networkRequest: { | ||
headers: { | ||
Authorization: `Bearer ${this.apiKey}`, | ||
}, | ||
params: { | ||
addresses: safeAddress, | ||
includeImages: true, | ||
}, | ||
}, | ||
}); | ||
return portfolio; | ||
} catch (error) { | ||
throw this.httpErrorFactory.from(error); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; | ||
import { OctavApi } from '@/datasources/portfolio-api/octav-api.service'; | ||
import { IPortfolioApi } from '@/domain/interfaces/portfolio-api.interface'; | ||
|
||
@Module({ | ||
providers: [HttpErrorFactory, { provide: IPortfolioApi, useClass: OctavApi }], | ||
exports: [IPortfolioApi], | ||
}) | ||
export class PortfolioApiModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { Portfolio } from '@/domain/portfolio/entities/portfolio.entity'; | ||
import type { Raw } from '@/validation/entities/raw.entity'; | ||
|
||
export const IPortfolioApi = Symbol('IPortfolioApi'); | ||
|
||
export interface IPortfolioApi { | ||
getPortfolio(safeAddress: `0x${string}`): Promise<Raw<Portfolio>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
it.todo('PortfolioSchema'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { z } from 'zod'; | ||
|
||
export const PortfolioSchema = z.unknown(); | ||
|
||
export type Portfolio = z.infer<typeof PortfolioSchema>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters