Skip to content

Commit 03e0416

Browse files
committed
implement stake formatting utility and improve some unit tests
1 parent d5463e8 commit 03e0416

6 files changed

Lines changed: 60 additions & 82 deletions

File tree

packages/apps/reputation-oracle/server/src/modules/exchange-api-keys/types.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { ExchangeModule } from './exchange.module';
22
export { ExchangeClientFactory } from './exchange-client.factory';
3+
export * from './types';

packages/apps/reputation-oracle/server/src/modules/staking/staking.dto.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApiProperty } from '@nestjs/swagger';
1+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
22

33
export class StakeSummaryResponseDto {
44
@ApiProperty({ name: 'exchange_stake' })
@@ -10,9 +10,9 @@ export class StakeSummaryResponseDto {
1010
@ApiProperty({ name: 'min_threshold' })
1111
minThreshold: string;
1212

13-
@ApiProperty({ name: 'exchange_error', required: false, nullable: true })
13+
@ApiPropertyOptional({ name: 'exchange_error' })
1414
exchangeError?: string;
1515

16-
@ApiProperty({ name: 'on_chain_error', required: false, nullable: true })
16+
@ApiPropertyOptional({ name: 'on_chain_error' })
1717
onChainError?: string;
1818
}

packages/apps/reputation-oracle/server/src/modules/staking/staking.service.spec.ts

Lines changed: 43 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
jest.mock('@human-protocol/sdk');
2+
13
import { faker } from '@faker-js/faker';
24
import { createMock } from '@golevelup/ts-jest';
5+
import { StakingClient } from '@human-protocol/sdk';
36
import { Test, TestingModule } from '@nestjs/testing';
47
import { ethers } from 'ethers';
58

69
import { SupportedExchange } from '@/common/constants';
710
import { StakingConfigService, Web3ConfigService } from '@/config';
11+
import { type ExchangeClient } from '@/modules/exchange';
812
import { ExchangeClientFactory } from '@/modules/exchange/exchange-client.factory';
913
import {
1014
ExchangeApiKeyNotFoundError,
@@ -15,19 +19,7 @@ import { WalletWithProvider, Web3Service } from '@/modules/web3';
1519
import { mockWeb3ConfigService } from '@/modules/web3/fixtures';
1620

1721
import { StakingService } from './staking.service';
18-
import { ExchangeClient } from '../exchange/types';
19-
20-
jest.mock('@human-protocol/sdk', () => {
21-
const actual = jest.requireActual('@human-protocol/sdk');
22-
return {
23-
...actual,
24-
StakingClient: {
25-
build: jest.fn(),
26-
},
27-
};
28-
});
2922

30-
const { StakingClient } = jest.requireMock('@human-protocol/sdk');
3123
const mockExchangeApiKeysService = createMock<ExchangeApiKeysService>();
3224
const mockExchangeClientFactory = {
3325
create: jest.fn(),
@@ -41,14 +33,14 @@ const mockStakingConfigService: Omit<StakingConfigService, 'configService'> = {
4133
asset: 'HMT',
4234
timeoutMs: faker.number.int({ min: 1000, max: 10000 }),
4335
};
36+
const mockedStakingClient = jest.mocked(StakingClient);
4437

4538
describe('StakingService', () => {
4639
let stakingService: StakingService;
4740

48-
beforeEach(async () => {
41+
beforeAll(async () => {
4942
mockExchangeClientFactory.create.mockResolvedValue(mockExchangeClient);
5043
mockExchangeClient.getAccountBalance.mockReset();
51-
(StakingClient.build as jest.Mock).mockReset();
5244
mockWeb3Service.getSigner.mockReturnValue({
5345
provider: {},
5446
} as never);
@@ -74,7 +66,7 @@ describe('StakingService', () => {
7466
stakingService = module.get(StakingService);
7567
});
7668

77-
afterEach(() => {
69+
afterAll(() => {
7870
jest.clearAllMocks();
7971
});
8072

@@ -121,6 +113,22 @@ describe('StakingService', () => {
121113
};
122114
const onChainStake = faker.number.int();
123115
const exchangeStake = faker.number.int();
116+
let spyOnGetExchangeStakedBalance: jest.SpyInstance;
117+
let spyOnGetOnChainStakedBalance: jest.SpyInstance;
118+
119+
beforeAll(() => {
120+
spyOnGetExchangeStakedBalance = jest
121+
.spyOn(stakingService, 'getExchangeStakedBalance')
122+
.mockImplementation();
123+
spyOnGetOnChainStakedBalance = jest
124+
.spyOn(stakingService, 'getOnChainStakedBalance')
125+
.mockImplementation();
126+
});
127+
128+
afterAll(() => {
129+
spyOnGetExchangeStakedBalance.mockRestore();
130+
spyOnGetOnChainStakedBalance.mockRestore();
131+
});
124132

125133
it('throws when user is not found', async () => {
126134
mockUserRepository.findOneById.mockResolvedValueOnce(null);
@@ -132,32 +140,19 @@ describe('StakingService', () => {
132140

133141
it('returns aggregated exchange and on-chain stakes', async () => {
134142
mockUserRepository.findOneById.mockResolvedValueOnce(user as UserEntity);
135-
const exchangeStakeSpy = jest
136-
.spyOn(stakingService, 'getExchangeStakedBalance')
137-
.mockResolvedValueOnce(exchangeStake);
138-
jest
139-
.spyOn(stakingService, 'getOnChainStakedBalance')
140-
.mockResolvedValueOnce(onChainStake);
143+
spyOnGetExchangeStakedBalance.mockResolvedValueOnce(exchangeStake);
144+
spyOnGetOnChainStakedBalance.mockResolvedValueOnce(onChainStake);
141145

142146
const result = await stakingService.getStakeSummary(user.id);
143147

144-
expect(exchangeStakeSpy).toHaveBeenCalledWith(user.id);
145-
expect(stakingService.getOnChainStakedBalance).toHaveBeenCalledWith(
148+
expect(spyOnGetExchangeStakedBalance).toHaveBeenCalledWith(user.id);
149+
expect(spyOnGetOnChainStakedBalance).toHaveBeenCalledWith(
146150
user.evmAddress,
147151
);
148152
expect(result).toEqual({
149-
exchangeStake: exchangeStake.toLocaleString(undefined, {
150-
maximumFractionDigits: 18,
151-
}),
152-
onChainStake: onChainStake.toLocaleString(undefined, {
153-
maximumFractionDigits: 18,
154-
}),
155-
minThreshold: mockStakingConfigService.minThreshold.toLocaleString(
156-
undefined,
157-
{
158-
maximumFractionDigits: 18,
159-
},
160-
),
153+
exchangeStake: exchangeStake.toString(),
154+
onChainStake: onChainStake.toString(),
155+
minThreshold: mockStakingConfigService.minThreshold.toString(),
161156
});
162157
});
163158

@@ -166,28 +161,16 @@ describe('StakingService', () => {
166161
...user,
167162
evmAddress: null,
168163
} as UserEntity);
169-
const exchangeStakeSpy = jest
170-
.spyOn(stakingService, 'getExchangeStakedBalance')
171-
.mockResolvedValueOnce(exchangeStake);
172-
jest.spyOn(stakingService, 'getOnChainStakedBalance');
164+
spyOnGetExchangeStakedBalance.mockResolvedValueOnce(exchangeStake);
173165

174166
const result = await stakingService.getStakeSummary(user.id);
175167

176-
expect(stakingService.getOnChainStakedBalance).not.toHaveBeenCalled();
168+
expect(spyOnGetOnChainStakedBalance).not.toHaveBeenCalled();
177169
expect(result).toEqual({
178-
exchangeStake: exchangeStake.toLocaleString(undefined, {
179-
maximumFractionDigits: 18,
180-
}),
170+
exchangeStake: exchangeStake.toString(),
181171
onChainStake: '0',
182-
minThreshold: mockStakingConfigService.minThreshold.toLocaleString(
183-
undefined,
184-
{
185-
maximumFractionDigits: 18,
186-
},
187-
),
172+
minThreshold: mockStakingConfigService.minThreshold.toString(),
188173
});
189-
190-
exchangeStakeSpy.mockRestore();
191174
});
192175
});
193176

@@ -204,20 +187,18 @@ describe('StakingService', () => {
204187
mockWeb3Service.getSigner.mockReturnValueOnce({
205188
provider: mockProvider,
206189
} as WalletWithProvider);
207-
const mockStakingClient = {
208-
getStakerInfo: jest.fn().mockResolvedValue({
209-
stakedAmount,
210-
lockedAmount,
211-
}),
212-
};
213-
(StakingClient.build as jest.Mock).mockResolvedValueOnce(
214-
mockStakingClient,
215-
);
190+
191+
const getStakerInfoMock = jest
192+
.fn()
193+
.mockResolvedValue({ stakedAmount, lockedAmount });
194+
mockedStakingClient.build.mockResolvedValueOnce({
195+
getStakerInfo: getStakerInfoMock,
196+
} as unknown as StakingClient);
216197

217198
const result = await stakingService.getOnChainStakedBalance(address);
218199

219-
expect(StakingClient.build).toHaveBeenCalledWith(mockProvider);
220-
expect(mockStakingClient.getStakerInfo).toHaveBeenCalledWith(address);
200+
expect(mockedStakingClient.build).toHaveBeenCalledWith(mockProvider);
201+
expect(getStakerInfoMock).toHaveBeenCalledWith(address);
221202
expect(result).toBe(
222203
Number(ethers.formatEther(stakedAmount + lockedAmount)),
223204
);

packages/apps/reputation-oracle/server/src/modules/staking/staking.service.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from '@/modules/exchange-api-keys';
1313
import { UserNotFoundError, UserRepository } from '@/modules/user';
1414
import { Web3Service } from '@/modules/web3';
15+
import { formatStake } from '@/utils/stake';
1516

1617
import { StakeSummaryData } from './types';
1718

@@ -73,11 +74,9 @@ export class StakingService {
7374
};
7475

7576
try {
76-
summary.exchangeStake = (
77-
await this.getExchangeStakedBalance(userId)
78-
).toLocaleString(undefined, {
79-
maximumFractionDigits: 18,
80-
});
77+
summary.exchangeStake = formatStake(
78+
await this.getExchangeStakedBalance(userId),
79+
);
8180
} catch (error) {
8281
summary.onChainError = error.message
8382
? error.message
@@ -90,11 +89,9 @@ export class StakingService {
9089

9190
if (user.evmAddress) {
9291
try {
93-
summary.onChainStake = (
94-
await this.getOnChainStakedBalance(user.evmAddress)
95-
).toLocaleString(undefined, {
96-
maximumFractionDigits: 18,
97-
});
92+
summary.onChainStake = formatStake(
93+
await this.getOnChainStakedBalance(user.evmAddress),
94+
);
9895
} catch (error) {
9996
summary.onChainError = error.message
10097
? error.message
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const formatStake = (value: number) =>
2+
new Intl.NumberFormat(undefined, {
3+
maximumFractionDigits: 18,
4+
notation: 'standard',
5+
useGrouping: false,
6+
}).format(value);

0 commit comments

Comments
 (0)