Skip to content

Commit 57775b3

Browse files
feat(mbe): add integration test
1 parent 22d49dc commit 57775b3

1 file changed

Lines changed: 234 additions & 54 deletions

File tree

src/__tests__/api/master/recoveryConsolidationsWallet.test.ts

Lines changed: 234 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { app as expressApp } from '../../../masterExpressApp';
66
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
77
import { Trx } from '@bitgo/sdk-coin-trx';
88
import { Sol } from '@bitgo/sdk-coin-sol';
9+
import { Sui } from '@bitgo/sdk-coin-sui';
910
import { EnclavedExpressClient } from '../../../api/master/clients/enclavedExpressClient';
1011

11-
describe('POST /api/:coin/wallet/recoveryConsolidations', () => {
12+
describe('POST /api/:coin/wallet/recoveryconsolidations', () => {
1213
let agent: request.SuperAgentTest;
1314
const enclavedExpressUrl = 'http://enclaved.invalid';
1415
const accessToken = 'test-token';
@@ -40,70 +41,249 @@ describe('POST /api/:coin/wallet/recoveryConsolidations', () => {
4041
sinon.restore();
4142
});
4243

43-
it('should handle TRON consolidation recovery', async () => {
44-
const mockTransactions = [{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' }];
44+
describe('Non-MPC Wallets (multisigType: onchain)', () => {
45+
it('should handle TRON consolidation recovery for onchain wallet', async () => {
46+
const mockTransactions = [
47+
{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' },
48+
{ txHex: 'unsigned-tx-2', serializedTx: 'serialized-unsigned-tx-2' }
49+
];
4550

46-
const recoverConsolidationsStub = sinon.stub(Trx.prototype, 'recoverConsolidations').resolves({
47-
transactions: mockTransactions,
51+
const recoverConsolidationsStub = sinon.stub(Trx.prototype, 'recoverConsolidations').resolves({
52+
transactions: mockTransactions,
53+
});
54+
55+
const recoveryMultisigStub = sinon
56+
.stub(EnclavedExpressClient.prototype, 'recoveryMultisig')
57+
.resolves({ txHex: 'signed-tx' });
58+
59+
const response = await agent
60+
.post(`/api/trx/wallet/recoveryconsolidations`)
61+
.set('Authorization', `Bearer ${accessToken}`)
62+
.send({
63+
multisigType: 'onchain',
64+
userPub: 'user-xpub',
65+
backupPub: 'backup-xpub',
66+
bitgoPub: 'bitgo-xpub',
67+
tokenContractAddress: 'tron-token',
68+
startingScanIndex: 1,
69+
endingScanIndex: 3,
70+
});
71+
72+
response.status.should.equal(200);
73+
response.body.should.have.property('signedTxs');
74+
response.body.signedTxs.should.have.length(2);
75+
76+
sinon.assert.calledOnce(recoverConsolidationsStub);
77+
sinon.assert.calledTwice(recoveryMultisigStub);
78+
79+
const callArgs = recoverConsolidationsStub.firstCall.args[0];
80+
callArgs.tokenContractAddress!.should.equal('tron-token');
81+
callArgs.userKey!.should.equal('user-xpub');
82+
callArgs.backupKey!.should.equal('backup-xpub');
83+
callArgs.bitgoKey.should.equal('bitgo-xpub');
4884
});
4985

50-
const recoveryMultisigStub = sinon
51-
.stub(EnclavedExpressClient.prototype, 'recoveryMultisig')
52-
.resolves({ txHex: 'signed-tx' });
53-
54-
const response = await agent
55-
.post(`/api/trx/wallet/recoveryConsolidations`)
56-
.set('Authorization', `Bearer ${accessToken}`)
57-
.send({
58-
userPub: 'user-xpub',
59-
backupPub: 'backup-xpub',
60-
bitgoKey: 'bitgo-xpub',
61-
tokenContractAddress: 'tron-token',
62-
startingScanIndex: 1,
63-
endingScanIndex: 3,
86+
it('should handle Solana consolidation recovery for onchain wallet', async () => {
87+
const mockTransactions = [{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' }];
88+
89+
const recoverConsolidationsStub = sinon.stub(Sol.prototype, 'recoverConsolidations').resolves({
90+
transactions: mockTransactions,
6491
});
6592

66-
response.status.should.equal(200);
67-
response.body.should.have.property('signedTxs');
68-
sinon.assert.calledOnce(recoverConsolidationsStub);
69-
sinon.assert.calledOnce(recoveryMultisigStub);
70-
const callArgs = recoverConsolidationsStub.firstCall.args[0];
71-
callArgs.tokenContractAddress!.should.equal('tron-token');
72-
callArgs.userKey.should.equal('user-xpub');
73-
callArgs.backupKey.should.equal('backup-xpub');
74-
callArgs.bitgoKey.should.equal('bitgo-xpub');
93+
const recoveryMultisigStub = sinon
94+
.stub(EnclavedExpressClient.prototype, 'recoveryMultisig')
95+
.resolves({ txHex: 'signed-tx' });
96+
97+
const response = await agent
98+
.post(`/api/sol/wallet/recoveryconsolidations`)
99+
.set('Authorization', `Bearer ${accessToken}`)
100+
.send({
101+
multisigType: 'onchain',
102+
userPub: 'user-xpub',
103+
backupPub: 'backup-xpub',
104+
bitgoPub: 'bitgo-xpub',
105+
durableNonces: {
106+
publicKeys: ['sol-pubkey-1', 'sol-pubkey-2'],
107+
secretKey: 'sol-secret',
108+
},
109+
});
110+
111+
response.status.should.equal(200);
112+
response.body.should.have.property('signedTxs');
113+
sinon.assert.calledOnce(recoverConsolidationsStub);
114+
sinon.assert.calledOnce(recoveryMultisigStub);
115+
116+
const callArgs = recoverConsolidationsStub.firstCall.args[0];
117+
callArgs.durableNonces.should.have.property('publicKeys').which.is.an.Array();
118+
callArgs.durableNonces.should.have.property('secretKey', 'sol-secret');
119+
callArgs.userKey!.should.equal('user-xpub');
120+
callArgs.backupKey!.should.equal('backup-xpub');
121+
callArgs.bitgoKey.should.equal('bitgo-xpub');
122+
});
75123
});
76124

77-
it('should handle Solana consolidation recovery', async () => {
78-
const mockTransactions = [{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' }];
125+
describe('MPC Wallets (multisigType: tss)', () => {
126+
it('should handle MPC consolidation recovery with commonKeychain', async () => {
127+
const mockTxRequests = [
128+
{
129+
walletCoin: 'tsui',
130+
transactions: [{
131+
unsignedTx: {
132+
txHex: 'unsigned-mpc-tx-1',
133+
serializedTx: 'serialized-unsigned-mpc-tx-1'
134+
},
135+
signatureShares: []
136+
}]
137+
}
138+
] as any;
79139

80-
const recoverConsolidationsStub = sinon.stub(Sol.prototype, 'recoverConsolidations').resolves({
81-
transactions: mockTransactions,
140+
const recoverConsolidationsStub = sinon.stub(Sui.prototype, 'recoverConsolidations').resolves({
141+
txRequests: mockTxRequests,
142+
});
143+
144+
const recoveryMPCStub = sinon
145+
.stub(EnclavedExpressClient.prototype, 'recoveryMPC')
146+
.resolves({ txHex: 'signed-mpc-tx' });
147+
148+
const response = await agent
149+
.post(`/api/tsui/wallet/recoveryconsolidations`)
150+
.set('Authorization', `Bearer ${accessToken}`)
151+
.send({
152+
multisigType: 'tss',
153+
commonKeychain: 'common-keychain-key',
154+
apiKey: 'test-api-key',
155+
startingScanIndex: 0,
156+
endingScanIndex: 5,
157+
});
158+
159+
response.status.should.equal(200);
160+
response.body.should.have.property('signedTxs');
161+
response.body.signedTxs.should.have.length(1);
162+
163+
sinon.assert.calledOnce(recoverConsolidationsStub);
164+
sinon.assert.calledOnce(recoveryMPCStub);
165+
166+
const callArgs = recoverConsolidationsStub.firstCall.args[0];
167+
callArgs.userKey!.should.equal('');
168+
callArgs.backupKey!.should.equal('');
169+
callArgs.bitgoKey.should.equal('common-keychain-key');
170+
171+
const mpcCallArgs = recoveryMPCStub.firstCall.args[0];
172+
mpcCallArgs.userPub.should.equal('common-keychain-key');
173+
mpcCallArgs.backupPub.should.equal('common-keychain-key');
174+
mpcCallArgs.apiKey.should.equal('test-api-key');
82175
});
83176

84-
const recoveryMultisigStub = sinon
85-
.stub(EnclavedExpressClient.prototype, 'recoveryMultisig')
86-
.resolves({ txHex: 'signed-tx' });
87-
88-
const response = await agent
89-
.post(`/api/sol/wallet/recoveryConsolidations`)
90-
.set('Authorization', `Bearer ${accessToken}`)
91-
.send({
92-
userPub: 'user-xpub',
93-
backupPub: 'backup-xpub',
94-
bitgoKey: 'bitgo-xpub',
95-
durableNonces: {
96-
publicKeys: ['sol-pubkey-1', 'sol-pubkey-2'],
97-
secretKey: 'sol-secret',
98-
},
177+
it('should handle SOL MPC consolidation recovery', async () => {
178+
const mockTransactions = [{ txHex: 'unsigned-mpc-tx-1', serializedTx: 'serialized-mpc-tx-1' }];
179+
180+
const recoverConsolidationsStub = sinon.stub(Sol.prototype, 'recoverConsolidations').resolves({
181+
transactions: mockTransactions,
99182
});
100183

101-
response.status.should.equal(200);
102-
response.body.should.have.property('signedTxs');
103-
sinon.assert.calledOnce(recoverConsolidationsStub);
104-
sinon.assert.calledOnce(recoveryMultisigStub);
105-
const callArgs = recoverConsolidationsStub.firstCall.args[0];
106-
callArgs.durableNonces.should.have.property('publicKeys').which.is.an.Array();
107-
callArgs.durableNonces.should.have.property('secretKey', 'sol-secret');
184+
const recoveryMPCStub = sinon
185+
.stub(EnclavedExpressClient.prototype, 'recoveryMPC')
186+
.resolves({ txHex: 'signed-mpc-tx' });
187+
188+
const response = await agent
189+
.post(`/api/sol/wallet/recoveryconsolidations`)
190+
.set('Authorization', `Bearer ${accessToken}`)
191+
.send({
192+
multisigType: 'tss',
193+
commonKeychain: 'sol-common-key',
194+
apiKey: 'sol-api-key',
195+
durableNonces: {
196+
publicKeys: ['sol-pubkey-1'],
197+
secretKey: 'sol-secret',
198+
},
199+
});
200+
201+
response.status.should.equal(200);
202+
response.body.should.have.property('signedTxs');
203+
sinon.assert.calledOnce(recoverConsolidationsStub);
204+
sinon.assert.calledOnce(recoveryMPCStub);
205+
206+
const mpcCallArgs = recoveryMPCStub.firstCall.args[0];
207+
mpcCallArgs.userPub.should.equal('sol-common-key');
208+
mpcCallArgs.backupPub.should.equal('sol-common-key');
209+
mpcCallArgs.apiKey.should.equal('sol-api-key');
210+
});
211+
});
212+
213+
describe('Error Cases', () => {
214+
it('should throw error when commonKeychain is missing for MPC wallet', async () => {
215+
const response = await agent
216+
.post(`/api/tsui/wallet/recoveryconsolidations`)
217+
.set('Authorization', `Bearer ${accessToken}`)
218+
.send({
219+
multisigType: 'tss',
220+
// Missing commonKeychain
221+
apiKey: 'test-api-key',
222+
});
223+
224+
response.status.should.equal(500);
225+
response.body.should.have.property('error');
226+
response.body.should.have.property('details').which.match(/Missing required key: commonKeychain/);
227+
});
228+
229+
it('should throw error when required keys are missing for onchain wallet', async () => {
230+
const response = await agent
231+
.post(`/api/trx/wallet/recoveryconsolidations`)
232+
.set('Authorization', `Bearer ${accessToken}`)
233+
.send({
234+
multisigType: 'onchain',
235+
userPub: 'user-xpub',
236+
// Missing backupPub and bitgoPub
237+
});
238+
239+
response.status.should.equal(500);
240+
response.body.should.have.property('error');
241+
response.body.should.have.property('details').which.match(/Missing required keys/);
242+
});
243+
244+
it('should handle empty recovery consolidations result', async () => {
245+
const recoverConsolidationsStub = sinon.stub(Trx.prototype, 'recoverConsolidations').resolves({
246+
transactions: [], // Empty result
247+
} as any);
248+
249+
const response = await agent
250+
.post(`/api/trx/wallet/recoveryconsolidations`)
251+
.set('Authorization', `Bearer ${accessToken}`)
252+
.send({
253+
multisigType: 'onchain',
254+
userPub: 'user-xpub',
255+
backupPub: 'backup-xpub',
256+
bitgoPub: 'bitgo-xpub',
257+
});
258+
259+
response.status.should.equal(200);
260+
response.body.should.have.property('signedTxs');
261+
response.body.signedTxs.should.have.length(0); // Empty array
262+
263+
sinon.assert.calledOnce(recoverConsolidationsStub);
264+
});
265+
266+
it('should throw error when recoverConsolidations returns unexpected result structure', async () => {
267+
const recoverConsolidationsStub = sinon.stub(Trx.prototype, 'recoverConsolidations').resolves({
268+
// Missing both transactions and txRequests properties
269+
someOtherProperty: 'value'
270+
} as any);
271+
272+
const response = await agent
273+
.post(`/api/trx/wallet/recoveryconsolidations`)
274+
.set('Authorization', `Bearer ${accessToken}`)
275+
.send({
276+
multisigType: 'onchain',
277+
userPub: 'user-xpub',
278+
backupPub: 'backup-xpub',
279+
bitgoPub: 'bitgo-xpub',
280+
});
281+
282+
response.status.should.equal(500);
283+
response.body.should.have.property('error');
284+
response.body.should.have.property('details').which.match(/recoverConsolidations did not return expected transactions/);
285+
286+
sinon.assert.calledOnce(recoverConsolidationsStub);
287+
});
108288
});
109289
});

0 commit comments

Comments
 (0)