Skip to content

Commit fffed96

Browse files
committed
test(express): add supertests for shareWallet
TICKET: WP-6352
1 parent ba0d219 commit fffed96

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
import * as assert from 'assert';
2+
import * as t from 'io-ts';
3+
import * as sinon from 'sinon';
4+
import { agent as supertest } from 'supertest';
5+
import 'should';
6+
import 'should-http';
7+
import 'should-sinon';
8+
import '../../lib/asserts';
9+
import { BitGo } from 'bitgo';
10+
import {
11+
ShareWalletParams,
12+
ShareWalletBody,
13+
ShareWalletResponse,
14+
PostShareWallet,
15+
} from '../../../src/typedRoutes/api/v2/shareWallet';
16+
import { assertDecode } from './common';
17+
import { app } from '../../../src/expressApp';
18+
import { DefaultConfig } from '../../../src/config';
19+
20+
describe('ShareWallet API Tests', function () {
21+
let agent: ReturnType<typeof supertest>;
22+
23+
before(function () {
24+
const testApp = app(DefaultConfig);
25+
agent = supertest(testApp);
26+
});
27+
28+
afterEach(function () {
29+
sinon.restore();
30+
});
31+
32+
describe('Success Cases', function () {
33+
it('should successfully share wallet with view permissions (no keychain)', async function () {
34+
const coin = 'tbtc';
35+
const walletId = '59cd72485007a239fb00282ed480da1f';
36+
const email = '[email protected]';
37+
const permissions = 'view';
38+
const message = 'Sharing for review';
39+
40+
const shareResponse = {
41+
id: 'share123',
42+
coin,
43+
wallet: walletId,
44+
fromUser: 'fromUser456',
45+
toUser: 'toUserId123',
46+
permissions,
47+
message,
48+
state: 'active',
49+
};
50+
51+
// Stub wallet.shareWallet to return the expected response
52+
const shareWalletStub = sinon.stub().resolves(shareResponse);
53+
const walletStub = {
54+
shareWallet: shareWalletStub,
55+
} as any;
56+
57+
// Stub the wallet retrieval chain
58+
const getWalletStub = sinon.stub().resolves(walletStub);
59+
const walletsStub = { get: getWalletStub } as any;
60+
const coinStub = { wallets: sinon.stub().returns(walletsStub) } as any;
61+
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
62+
63+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
64+
email,
65+
permissions,
66+
message,
67+
});
68+
69+
res.status.should.equal(200);
70+
res.body.should.have.property('id', 'share123');
71+
res.body.should.have.property('wallet', walletId);
72+
res.body.should.have.property('permissions', permissions);
73+
res.body.should.have.property('message', message);
74+
});
75+
76+
it('should successfully share wallet with optional skipKeychain flag', async function () {
77+
const coin = 'tbtc';
78+
const walletId = '59cd72485007a239fb00282ed480da1f';
79+
const email = '[email protected]';
80+
const permissions = 'view,spend';
81+
82+
const shareResponse = {
83+
id: 'share456',
84+
coin,
85+
wallet: walletId,
86+
fromUser: 'fromUser456',
87+
toUser: 'toUserId789',
88+
permissions,
89+
state: 'active',
90+
};
91+
92+
const shareWalletStub = sinon.stub().resolves(shareResponse);
93+
const walletStub = { shareWallet: shareWalletStub } as any;
94+
const getWalletStub = sinon.stub().resolves(walletStub);
95+
const walletsStub = { get: getWalletStub } as any;
96+
const coinStub = { wallets: sinon.stub().returns(walletsStub) } as any;
97+
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
98+
99+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
100+
email,
101+
permissions,
102+
skipKeychain: true,
103+
});
104+
105+
res.status.should.equal(200);
106+
res.body.should.have.property('id', 'share456');
107+
});
108+
109+
it('should successfully share wallet with optional disableEmail flag', async function () {
110+
const coin = 'tbtc';
111+
const walletId = '59cd72485007a239fb00282ed480da1f';
112+
const email = '[email protected]';
113+
const permissions = 'view';
114+
115+
const shareResponse = {
116+
id: 'share555',
117+
coin,
118+
wallet: walletId,
119+
fromUser: 'fromUser456',
120+
toUser: 'toUserId555',
121+
permissions,
122+
state: 'active',
123+
};
124+
125+
const shareWalletStub = sinon.stub().resolves(shareResponse);
126+
const walletStub = { shareWallet: shareWalletStub } as any;
127+
const getWalletStub = sinon.stub().resolves(walletStub);
128+
const walletsStub = { get: getWalletStub } as any;
129+
const coinStub = { wallets: sinon.stub().returns(walletsStub) } as any;
130+
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
131+
132+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
133+
email,
134+
permissions,
135+
disableEmail: true,
136+
});
137+
138+
res.status.should.equal(200);
139+
res.body.should.have.property('id', 'share555');
140+
});
141+
});
142+
143+
describe('Error Cases', function () {
144+
it('should return 500 when wallet not found', async function () {
145+
const coin = 'tbtc';
146+
const walletId = 'nonexistent_wallet_id';
147+
const email = '[email protected]';
148+
const permissions = 'view';
149+
150+
// Stub coin.wallets().get() to reject with error
151+
const getWalletStub = sinon.stub().rejects(new Error('wallet not found'));
152+
const walletsStub = { get: getWalletStub } as any;
153+
const coinStub = { wallets: sinon.stub().returns(walletsStub) } as any;
154+
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
155+
156+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
157+
email,
158+
permissions,
159+
});
160+
161+
res.status.should.equal(500);
162+
res.body.should.have.property('error');
163+
});
164+
165+
it('should return 500 when shareWallet fails', async function () {
166+
const coin = 'tbtc';
167+
const walletId = '59cd72485007a239fb00282ed480da1f';
168+
const email = '[email protected]';
169+
const permissions = 'view';
170+
171+
// Stub wallet.shareWallet to reject with error
172+
const shareWalletStub = sinon.stub().rejects(new Error('invalid email address'));
173+
const walletStub = { shareWallet: shareWalletStub } as any;
174+
const getWalletStub = sinon.stub().resolves(walletStub);
175+
const walletsStub = { get: getWalletStub } as any;
176+
const coinStub = { wallets: sinon.stub().returns(walletsStub) } as any;
177+
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
178+
179+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
180+
email,
181+
permissions,
182+
});
183+
184+
res.status.should.equal(500);
185+
res.body.should.have.property('error');
186+
});
187+
188+
it('should return 400 when email is missing', async function () {
189+
const coin = 'tbtc';
190+
const walletId = '59cd72485007a239fb00282ed480da1f';
191+
const permissions = 'view';
192+
193+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
194+
permissions,
195+
// email is missing
196+
});
197+
198+
res.status.should.equal(400);
199+
res.body.should.be.an.Array();
200+
res.body[0].should.match(/email/);
201+
});
202+
203+
it('should return 400 when permissions is missing', async function () {
204+
const coin = 'tbtc';
205+
const walletId = '59cd72485007a239fb00282ed480da1f';
206+
const email = '[email protected]';
207+
208+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
209+
email,
210+
// permissions is missing
211+
});
212+
213+
res.status.should.equal(400);
214+
res.body.should.be.an.Array();
215+
res.body[0].should.match(/permissions/);
216+
});
217+
218+
it('should return 400 when request body has invalid types', async function () {
219+
const coin = 'tbtc';
220+
const walletId = '59cd72485007a239fb00282ed480da1f';
221+
222+
const res = await agent.post(`/api/v2/${coin}/wallet/${walletId}/share`).send({
223+
email: 123, // should be string
224+
permissions: 'view',
225+
});
226+
227+
res.status.should.equal(400);
228+
res.body.should.be.an.Array();
229+
res.body[0].should.match(/email.*string/);
230+
});
231+
});
232+
233+
describe('Codec Validation', function () {
234+
it('should validate ShareWalletParams with required coin and id', function () {
235+
const validParams = {
236+
coin: 'tbtc',
237+
id: '59cd72485007a239fb00282ed480da1f',
238+
};
239+
240+
const decodedParams = assertDecode(t.exact(t.type(ShareWalletParams)), validParams);
241+
assert.ok(decodedParams);
242+
assert.strictEqual(decodedParams.coin, 'tbtc');
243+
assert.strictEqual(decodedParams.id, '59cd72485007a239fb00282ed480da1f');
244+
});
245+
246+
it('should validate ShareWalletBody with required fields', function () {
247+
const validBody = {
248+
249+
permissions: 'view,spend',
250+
};
251+
252+
const decodedBody = assertDecode(t.exact(t.type(ShareWalletBody)), validBody);
253+
assert.ok(decodedBody);
254+
assert.strictEqual(decodedBody.email, '[email protected]');
255+
assert.strictEqual(decodedBody.permissions, 'view,spend');
256+
});
257+
258+
it('should validate ShareWalletBody with optional fields', function () {
259+
const validBody = {
260+
261+
permissions: 'view',
262+
walletPassphrase: 'myPassphrase',
263+
message: 'Test message',
264+
reshare: false,
265+
skipKeychain: true,
266+
disableEmail: false,
267+
};
268+
269+
const decodedBody = assertDecode(t.exact(t.type(ShareWalletBody)), validBody);
270+
assert.ok(decodedBody);
271+
assert.strictEqual(decodedBody.walletPassphrase, 'myPassphrase');
272+
assert.strictEqual(decodedBody.message, 'Test message');
273+
assert.strictEqual(decodedBody.reshare, false);
274+
assert.strictEqual(decodedBody.skipKeychain, true);
275+
assert.strictEqual(decodedBody.disableEmail, false);
276+
});
277+
278+
it('should validate successful response (200)', function () {
279+
const validResponse = {
280+
id: 'share123',
281+
coin: 'tbtc',
282+
wallet: '59cd72485007a239fb00282ed480da1f',
283+
fromUser: 'user1',
284+
toUser: 'user2',
285+
permissions: 'view,spend',
286+
};
287+
288+
const decodedResponse = assertDecode(ShareWalletResponse[200], validResponse);
289+
assert.ok(decodedResponse);
290+
assert.strictEqual(decodedResponse.id, 'share123');
291+
assert.strictEqual(decodedResponse.wallet, '59cd72485007a239fb00282ed480da1f');
292+
});
293+
294+
it('should validate response with optional keychain field', function () {
295+
const validResponse = {
296+
id: 'share123',
297+
coin: 'tbtc',
298+
wallet: '59cd72485007a239fb00282ed480da1f',
299+
fromUser: 'user1',
300+
toUser: 'user2',
301+
permissions: 'view,spend',
302+
keychain: {
303+
pub: 'xpub123',
304+
encryptedPrv: 'encrypted',
305+
fromPubKey: 'from',
306+
toPubKey: 'to',
307+
path: 'm',
308+
},
309+
};
310+
311+
const decodedResponse = assertDecode(ShareWalletResponse[200], validResponse);
312+
assert.ok(decodedResponse);
313+
assert.ok(decodedResponse.keychain);
314+
});
315+
});
316+
317+
describe('Route Definition', function () {
318+
it('should have correct route configuration', function () {
319+
assert.strictEqual(PostShareWallet.method, 'POST');
320+
assert.strictEqual(PostShareWallet.path, '/api/v2/{coin}/wallet/{id}/share');
321+
assert.ok(PostShareWallet.request);
322+
assert.ok(PostShareWallet.response);
323+
});
324+
325+
it('should have correct response types', function () {
326+
assert.ok(PostShareWallet.response[200]);
327+
assert.ok(PostShareWallet.response[400]);
328+
});
329+
});
330+
});

0 commit comments

Comments
 (0)