Skip to content

Commit 8f62482

Browse files
authored
test(express): added supertest for decrypt
2 parents ca63a45 + 6636fe3 commit 8f62482

File tree

1 file changed

+338
-0
lines changed

1 file changed

+338
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
import * as assert from 'assert';
2+
import * as t from 'io-ts';
3+
import { DecryptRequestBody, PostDecrypt } from '../../../src/typedRoutes/api/common/decrypt';
4+
import { assertDecode } from './common';
5+
import 'should';
6+
import 'should-http';
7+
import 'should-sinon';
8+
import * as sinon from 'sinon';
9+
import { BitGo } from 'bitgo';
10+
import { setupAgent } from '../../lib/testutil';
11+
12+
describe('Decrypt codec tests', function () {
13+
describe('DecryptRequestBody', function () {
14+
it('should validate body with only required field (input)', function () {
15+
const validBody = {
16+
input: 'encryptedString123',
17+
};
18+
19+
const decoded = assertDecode(t.type(DecryptRequestBody), validBody);
20+
assert.strictEqual(decoded.input, validBody.input);
21+
assert.strictEqual(decoded.password, undefined);
22+
});
23+
24+
it('should validate body with input and password', function () {
25+
const validBody = {
26+
input: 'encryptedString123',
27+
password: 'mySecurePassword123',
28+
};
29+
30+
const decoded = assertDecode(t.type(DecryptRequestBody), validBody);
31+
assert.strictEqual(decoded.input, validBody.input);
32+
assert.strictEqual(decoded.password, validBody.password);
33+
});
34+
35+
it('should reject body with missing input', function () {
36+
const invalidBody = {
37+
password: 'mySecurePassword123',
38+
};
39+
40+
assert.throws(() => {
41+
assertDecode(t.type(DecryptRequestBody), invalidBody);
42+
});
43+
});
44+
45+
it('should reject body with non-string input', function () {
46+
const invalidBody = {
47+
input: 12345, // number instead of string
48+
};
49+
50+
assert.throws(() => {
51+
assertDecode(t.type(DecryptRequestBody), invalidBody);
52+
});
53+
});
54+
55+
it('should reject body with non-string password', function () {
56+
const invalidBody = {
57+
input: 'encryptedString123',
58+
password: 12345, // number instead of string
59+
};
60+
61+
assert.throws(() => {
62+
assertDecode(t.type(DecryptRequestBody), invalidBody);
63+
});
64+
});
65+
});
66+
67+
describe('DecryptResponse', function () {
68+
const DecryptResponse = PostDecrypt.response[200];
69+
70+
it('should validate response with required field', function () {
71+
const validResponse = {
72+
decrypted: 'myDecryptedString',
73+
};
74+
75+
const decoded = assertDecode(DecryptResponse, validResponse);
76+
assert.strictEqual(decoded.decrypted, validResponse.decrypted);
77+
});
78+
79+
it('should reject response with missing decrypted field', function () {
80+
const invalidResponse = {};
81+
82+
assert.throws(() => {
83+
assertDecode(DecryptResponse, invalidResponse);
84+
});
85+
});
86+
87+
it('should reject response with non-string decrypted field', function () {
88+
const invalidResponse = {
89+
decrypted: 12345, // number instead of string
90+
};
91+
92+
assert.throws(() => {
93+
assertDecode(DecryptResponse, invalidResponse);
94+
});
95+
});
96+
});
97+
98+
describe('Edge cases', function () {
99+
it('should handle additional unknown properties', function () {
100+
const body = {
101+
input: 'encryptedString123',
102+
password: 'mySecurePassword123',
103+
unknownProperty: 'some value',
104+
};
105+
106+
// io-ts with t.exact() strips out additional properties
107+
const decoded = assertDecode(t.exact(t.type(DecryptRequestBody)), body);
108+
assert.strictEqual(decoded.input, body.input);
109+
assert.strictEqual(decoded.password, body.password);
110+
// @ts-expect-error - unknownProperty doesn't exist on the type
111+
assert.strictEqual(decoded.unknownProperty, undefined);
112+
});
113+
});
114+
115+
describe('PostDecrypt route definition', function () {
116+
it('should have the correct path', function () {
117+
assert.strictEqual(PostDecrypt.path, '/api/v[12]/decrypt');
118+
});
119+
120+
it('should have the correct HTTP method', function () {
121+
assert.strictEqual(PostDecrypt.method, 'POST');
122+
});
123+
124+
it('should have the correct request configuration', function () {
125+
// Verify the route is configured with a request property
126+
assert.ok(PostDecrypt.request);
127+
});
128+
129+
it('should have the correct response types', function () {
130+
// Check that the response object has the expected status codes
131+
assert.ok(PostDecrypt.response[200]);
132+
assert.ok(PostDecrypt.response[404]);
133+
});
134+
});
135+
136+
// ==========================================
137+
// SUPERTEST INTEGRATION TESTS
138+
// ==========================================
139+
140+
describe('Supertest Integration Tests', function () {
141+
const agent = setupAgent();
142+
143+
const mockDecryptResponse = 'myDecryptedString';
144+
145+
afterEach(function () {
146+
sinon.restore();
147+
});
148+
149+
it('should successfully decrypt with input and password (v1)', async function () {
150+
const requestBody = {
151+
input: 'encryptedString123',
152+
password: 'mySecurePassword123',
153+
};
154+
155+
sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse);
156+
157+
const result = await agent
158+
.post('/api/v1/decrypt')
159+
.set('Authorization', 'Bearer test_access_token_12345')
160+
.set('Content-Type', 'application/json')
161+
.send(requestBody);
162+
163+
assert.strictEqual(result.status, 200);
164+
result.body.should.have.property('decrypted');
165+
assert.strictEqual(result.body.decrypted, mockDecryptResponse);
166+
167+
const decodedResponse = assertDecode(PostDecrypt.response[200], result.body);
168+
assert.strictEqual(decodedResponse.decrypted, mockDecryptResponse);
169+
});
170+
171+
it('should successfully decrypt with input and password (v2)', async function () {
172+
const requestBody = {
173+
input: 'encryptedString123',
174+
password: 'mySecurePassword123',
175+
};
176+
177+
sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse);
178+
179+
const result = await agent
180+
.post('/api/v2/decrypt')
181+
.set('Authorization', 'Bearer test_access_token_12345')
182+
.set('Content-Type', 'application/json')
183+
.send(requestBody);
184+
185+
assert.strictEqual(result.status, 200);
186+
result.body.should.have.property('decrypted');
187+
assert.strictEqual(result.body.decrypted, mockDecryptResponse);
188+
189+
const decodedResponse = assertDecode(PostDecrypt.response[200], result.body);
190+
assert.strictEqual(decodedResponse.decrypted, mockDecryptResponse);
191+
});
192+
193+
it('should successfully decrypt long encrypted string (v1)', async function () {
194+
const requestBody = {
195+
input: 'a'.repeat(1000), // Long encrypted string
196+
password: 'mySecurePassword123',
197+
};
198+
199+
const mockLongDecrypted = 'b'.repeat(500);
200+
sinon.stub(BitGo.prototype, 'decrypt').returns(mockLongDecrypted);
201+
202+
const result = await agent
203+
.post('/api/v1/decrypt')
204+
.set('Authorization', 'Bearer test_access_token_12345')
205+
.set('Content-Type', 'application/json')
206+
.send(requestBody);
207+
208+
assert.strictEqual(result.status, 200);
209+
assert.strictEqual(result.body.decrypted, mockLongDecrypted);
210+
211+
const decodedResponse = assertDecode(PostDecrypt.response[200], result.body);
212+
assert.strictEqual(decodedResponse.decrypted, mockLongDecrypted);
213+
});
214+
215+
it('should successfully decrypt with special characters in password (v2)', async function () {
216+
const requestBody = {
217+
input: 'encryptedString123',
218+
password: 'p@ssw0rd!#$%^&*()',
219+
};
220+
221+
sinon.stub(BitGo.prototype, 'decrypt').returns(mockDecryptResponse);
222+
223+
const result = await agent
224+
.post('/api/v2/decrypt')
225+
.set('Authorization', 'Bearer test_access_token_12345')
226+
.set('Content-Type', 'application/json')
227+
.send(requestBody);
228+
229+
assert.strictEqual(result.status, 200);
230+
assert.strictEqual(result.body.decrypted, mockDecryptResponse);
231+
232+
const decodedResponse = assertDecode(PostDecrypt.response[200], result.body);
233+
assert.ok(decodedResponse);
234+
});
235+
});
236+
237+
// ==========================================
238+
// ERROR HANDLING TESTS
239+
// ==========================================
240+
241+
describe('Error Handling Tests', function () {
242+
const agent = setupAgent();
243+
244+
afterEach(function () {
245+
sinon.restore();
246+
});
247+
248+
it('should handle decryption failure with wrong password (v1)', async function () {
249+
const requestBody = {
250+
input: 'encryptedString123',
251+
password: 'wrongPassword',
252+
};
253+
254+
sinon.stub(BitGo.prototype, 'decrypt').throws(new Error("password error - ccm: tag doesn't match"));
255+
256+
const result = await agent
257+
.post('/api/v1/decrypt')
258+
.set('Authorization', 'Bearer test_access_token_12345')
259+
.set('Content-Type', 'application/json')
260+
.send(requestBody);
261+
262+
assert.strictEqual(result.status, 500);
263+
result.body.should.have.property('error');
264+
});
265+
266+
it('should handle decryption failure with wrong password (v2)', async function () {
267+
const requestBody = {
268+
input: 'encryptedString123',
269+
password: 'wrongPassword',
270+
};
271+
272+
sinon.stub(BitGo.prototype, 'decrypt').throws(new Error("password error - ccm: tag doesn't match"));
273+
274+
const result = await agent
275+
.post('/api/v2/decrypt')
276+
.set('Authorization', 'Bearer test_access_token_12345')
277+
.set('Content-Type', 'application/json')
278+
.send(requestBody);
279+
280+
assert.strictEqual(result.status, 500);
281+
result.body.should.have.property('error');
282+
});
283+
284+
it('should handle missing password error (v1)', async function () {
285+
const requestBody = {
286+
input: 'encryptedString123',
287+
password: '',
288+
};
289+
290+
sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('cannot decrypt without password'));
291+
292+
const result = await agent
293+
.post('/api/v1/decrypt')
294+
.set('Authorization', 'Bearer test_access_token_12345')
295+
.set('Content-Type', 'application/json')
296+
.send(requestBody);
297+
298+
assert.strictEqual(result.status, 500);
299+
result.body.should.have.property('error');
300+
});
301+
302+
it('should handle invalid encrypted input format (v2)', async function () {
303+
const requestBody = {
304+
input: 'invalidEncryptedFormat',
305+
password: 'mySecurePassword123',
306+
};
307+
308+
sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Invalid encrypted input format'));
309+
310+
const result = await agent
311+
.post('/api/v2/decrypt')
312+
.set('Authorization', 'Bearer test_access_token_12345')
313+
.set('Content-Type', 'application/json')
314+
.send(requestBody);
315+
316+
assert.strictEqual(result.status, 500);
317+
result.body.should.have.property('error');
318+
});
319+
320+
it('should handle decrypt method not available (v1)', async function () {
321+
const requestBody = {
322+
input: 'encryptedString123',
323+
password: 'mySecurePassword123',
324+
};
325+
326+
sinon.stub(BitGo.prototype, 'decrypt').throws(new Error('Decrypt method not available'));
327+
328+
const result = await agent
329+
.post('/api/v1/decrypt')
330+
.set('Authorization', 'Bearer test_access_token_12345')
331+
.set('Content-Type', 'application/json')
332+
.send(requestBody);
333+
334+
assert.strictEqual(result.status, 500);
335+
result.body.should.have.property('error');
336+
});
337+
});
338+
});

0 commit comments

Comments
 (0)