Skip to content

Commit 784adf4

Browse files
Copilotbhavidhingra
authored andcommitted
fix(express): convert limit to string and forward nextBatchPrevId in resource delegations handler
Agent-Logs-Url: https://github.com/BitGo/BitGoJS/sessions/c1f514f3-1017-413f-90a9-4de5a30f1f0e Co-authored-by: bhavidhingra <17147510+bhavidhingra@users.noreply.github.com> TICKET: CHALO-346
1 parent ebfd681 commit 784adf4

3 files changed

Lines changed: 188 additions & 5 deletions

File tree

modules/express/src/clientRoutes.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,9 @@ async function handleV2SendMany(req: ExpressApiRouteRequest<'express.v2.wallet.s
10161016
* handle get account resources
10171017
* @param req
10181018
*/
1019-
async function handleV2AccountResources(req: ExpressApiRouteRequest<'express.v2.wallet.getaccountresources', 'post'>) {
1019+
export async function handleV2AccountResources(
1020+
req: ExpressApiRouteRequest<'express.v2.wallet.getaccountresources', 'post'>
1021+
) {
10201022
const bitgo = req.bitgo;
10211023
const coin = bitgo.coin(req.decoded.coin);
10221024
const wallet = await coin.wallets().get({ id: req.decoded.id });
@@ -1030,7 +1032,7 @@ async function handleV2AccountResources(req: ExpressApiRouteRequest<'express.v2.
10301032
* handle get resource delegations
10311033
* @param req
10321034
*/
1033-
async function handleV2ResourceDelegations(
1035+
export async function handleV2ResourceDelegations(
10341036
req: ExpressApiRouteRequest<'express.v2.wallet.resourcedelegations', 'get'>
10351037
) {
10361038
const bitgo = req.bitgo;
@@ -1039,7 +1041,8 @@ async function handleV2ResourceDelegations(
10391041
const query: Record<string, string> = {};
10401042
if (req.decoded.type) query.type = req.decoded.type;
10411043
if (req.decoded.resource) query.resource = req.decoded.resource;
1042-
if (req.decoded.limit) query.limit = req.decoded.limit;
1044+
if (req.decoded.limit !== undefined) query.limit = String(req.decoded.limit);
1045+
if (req.decoded.nextBatchPrevId) query.nextBatchPrevId = req.decoded.nextBatchPrevId;
10431046
return bitgo
10441047
.get(bitgo.url(`/${coin}/wallet/${walletId}/resourcedelegations`, 2))
10451048
.query(query)

modules/express/src/typedRoutes/api/v2/accountResources.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export const AccountResourceInfo = t.intersection([
3434
staked_bandwidth_used: t.number,
3535
energy_available: t.number,
3636
energy_used: t.number,
37+
}),
38+
t.partial({
3739
resourceDeficitForAssetTransfer: t.intersection([
3840
t.type({
3941
bandwidthDeficit: t.number,
@@ -44,8 +46,6 @@ export const AccountResourceInfo = t.intersection([
4446
energySunRequired: t.string,
4547
}),
4648
]),
47-
}),
48-
t.partial({
4949
maxResourcesDelegatable: t.type({
5050
bandwidthSun: t.string,
5151
energySun: t.string,
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import * as sinon from 'sinon';
2+
import { decodeOrElse } from '@bitgo/sdk-core';
3+
4+
import 'should';
5+
import 'should-sinon';
6+
import '../../lib/asserts';
7+
8+
import * as express from 'express';
9+
10+
import { handleV2AccountResources, handleV2ResourceDelegations } from '../../../src/clientRoutes';
11+
import { AccountResourcesResponse } from '../../../src/typedRoutes/api/v2/accountResources';
12+
import { ResourceDelegationsResponse } from '../../../src/typedRoutes/api/v2/resourceDelegations';
13+
14+
import { BitGo } from 'bitgo';
15+
16+
describe('TRX Resource Delegation handlers', () => {
17+
const sandbox = sinon.createSandbox();
18+
19+
afterEach(() => {
20+
sandbox.verifyAndRestore();
21+
});
22+
23+
describe('handleV2AccountResources', () => {
24+
const mockResources = {
25+
resources: [
26+
{
27+
address: 'TAddr123',
28+
free_bandwidth_available: 1500,
29+
free_bandwidth_used: 0,
30+
staked_bandwidth_available: 0,
31+
staked_bandwidth_used: 0,
32+
energy_available: 0,
33+
energy_used: 0,
34+
},
35+
],
36+
failedAddresses: [],
37+
};
38+
39+
function createMocks(result: unknown) {
40+
const getAccountResourcesStub = sandbox.stub().resolves(result);
41+
const walletStub = { getAccountResources: getAccountResourcesStub };
42+
const coinStub = {
43+
wallets: () => ({ get: () => Promise.resolve(walletStub) }),
44+
};
45+
const bitgoStub = sinon.createStubInstance(BitGo as any, { coin: coinStub });
46+
return { bitgoStub, getAccountResourcesStub };
47+
}
48+
49+
it('should call getAccountResources with addresses and return codec-valid result', async () => {
50+
const { bitgoStub, getAccountResourcesStub } = createMocks(mockResources);
51+
52+
const mockRequest = {
53+
bitgo: bitgoStub,
54+
decoded: {
55+
coin: 'ttrx',
56+
id: 'walletId123',
57+
addresses: ['TAddr123'],
58+
},
59+
};
60+
61+
const result = await handleV2AccountResources(mockRequest as express.Request & typeof mockRequest);
62+
decodeOrElse('AccountResourcesResponse', AccountResourcesResponse[200], result, (_) => {
63+
throw new Error('Response did not match expected codec');
64+
});
65+
getAccountResourcesStub.should.be.calledOnceWith({
66+
addresses: ['TAddr123'],
67+
destinationAddress: undefined,
68+
});
69+
});
70+
71+
it('should forward destinationAddress when provided', async () => {
72+
const { bitgoStub, getAccountResourcesStub } = createMocks(mockResources);
73+
74+
const mockRequest = {
75+
bitgo: bitgoStub,
76+
decoded: {
77+
coin: 'ttrx',
78+
id: 'walletId123',
79+
addresses: ['TAddr123'],
80+
destinationAddress: 'TDest456',
81+
},
82+
};
83+
84+
await handleV2AccountResources(mockRequest as express.Request & typeof mockRequest);
85+
getAccountResourcesStub.should.be.calledOnceWith({
86+
addresses: ['TAddr123'],
87+
destinationAddress: 'TDest456',
88+
});
89+
});
90+
});
91+
92+
describe('handleV2ResourceDelegations', () => {
93+
const mockDelegations = {
94+
address: 'TAddr123',
95+
coin: 'ttrx',
96+
delegations: {
97+
outgoing: [],
98+
incoming: [],
99+
},
100+
};
101+
102+
function createBitgoStub(result: unknown) {
103+
const resultStub = sandbox.stub().resolves(result);
104+
const queryStub = sandbox.stub().returns({ result: resultStub });
105+
const getStub = sandbox.stub().returns({ query: queryStub });
106+
const urlStub = sandbox
107+
.stub()
108+
.returns('https://test.bitgo.com/api/v2/ttrx/wallet/walletId123/resourcedelegations');
109+
const bitgoStub = sinon.createStubInstance(BitGo as any, { get: getStub, url: urlStub });
110+
return { bitgoStub, getStub, queryStub, resultStub };
111+
}
112+
113+
it('should forward type and resource query params', async () => {
114+
const { bitgoStub, queryStub } = createBitgoStub(mockDelegations);
115+
116+
const mockRequest = {
117+
bitgo: bitgoStub,
118+
decoded: {
119+
coin: 'ttrx',
120+
id: 'walletId123',
121+
type: 'outgoing' as const,
122+
resource: 'ENERGY',
123+
},
124+
};
125+
126+
await handleV2ResourceDelegations(mockRequest as express.Request & typeof mockRequest);
127+
queryStub.should.be.calledOnceWith({ type: 'outgoing', resource: 'ENERGY' });
128+
});
129+
130+
it('should convert limit to string when forwarding', async () => {
131+
const { bitgoStub, queryStub } = createBitgoStub(mockDelegations);
132+
133+
const mockRequest = {
134+
bitgo: bitgoStub,
135+
decoded: {
136+
coin: 'ttrx',
137+
id: 'walletId123',
138+
limit: 10,
139+
},
140+
};
141+
142+
await handleV2ResourceDelegations(mockRequest as express.Request & typeof mockRequest);
143+
queryStub.should.be.calledOnceWith({ limit: '10' });
144+
});
145+
146+
it('should forward nextBatchPrevId for pagination', async () => {
147+
const { bitgoStub, queryStub } = createBitgoStub(mockDelegations);
148+
149+
const mockRequest = {
150+
bitgo: bitgoStub,
151+
decoded: {
152+
coin: 'ttrx',
153+
id: 'walletId123',
154+
type: 'incoming' as const,
155+
nextBatchPrevId: 'cursor-abc123',
156+
},
157+
};
158+
159+
await handleV2ResourceDelegations(mockRequest as express.Request & typeof mockRequest);
160+
queryStub.should.be.calledOnceWith({ type: 'incoming', nextBatchPrevId: 'cursor-abc123' });
161+
});
162+
163+
it('should return codec-valid delegations result', async () => {
164+
const { bitgoStub } = createBitgoStub(mockDelegations);
165+
166+
const mockRequest = {
167+
bitgo: bitgoStub,
168+
decoded: {
169+
coin: 'ttrx',
170+
id: 'walletId123',
171+
},
172+
};
173+
174+
const result = await handleV2ResourceDelegations(mockRequest as express.Request & typeof mockRequest);
175+
decodeOrElse('ResourceDelegationsResponse', ResourceDelegationsResponse[200], result, (_) => {
176+
throw new Error('Response did not match expected codec');
177+
});
178+
});
179+
});
180+
});

0 commit comments

Comments
 (0)