Skip to content

Commit 23e4d49

Browse files
committed
fix(express): fix express overriding 2xx status
changed the redirectRequest method so it returns the original status from WP not only the body of the response WP-2961 TICKET: WP-2961
1 parent 2cd6b5d commit 23e4d49

File tree

2 files changed

+157
-6
lines changed

2 files changed

+157
-6
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,13 @@ async function handleNetworkV1EnterpriseClientConnections(
11141114
* @param req
11151115
* @param next
11161116
*/
1117-
function redirectRequest(bitgo: BitGo, method: string, url: string, req: express.Request, next: express.NextFunction) {
1117+
export function redirectRequest(
1118+
bitgo: BitGo,
1119+
method: string,
1120+
url: string,
1121+
req: express.Request,
1122+
next: express.NextFunction
1123+
) {
11181124
let request;
11191125

11201126
switch (method) {
@@ -1142,7 +1148,11 @@ function redirectRequest(bitgo: BitGo, method: string, url: string, req: express
11421148
if (req.params.enterpriseId) {
11431149
request.set('enterprise-id', req.params.enterpriseId);
11441150
}
1145-
return request.result();
1151+
1152+
return request.result().then((result) => {
1153+
const status = request.res?.statusCode || 200;
1154+
return { status, body: result };
1155+
});
11461156
}
11471157

11481158
// something has presumably gone wrong
@@ -1217,8 +1227,7 @@ function prepareBitGo(config: Config) {
12171227
next();
12181228
};
12191229
}
1220-
1221-
type RequestHandlerResponse = string | unknown | undefined;
1230+
type RequestHandlerResponse = string | unknown | undefined | { status: number; body: unknown };
12221231
interface RequestHandler extends express.RequestHandler<ParamsDictionary, any, RequestHandlerResponse> {
12231232
(req: express.Request, res: express.Response, next: express.NextFunction):
12241233
| RequestHandlerResponse
@@ -1260,12 +1269,17 @@ function handleRequestHandlerError(res: express.Response, error: unknown) {
12601269
* Promise handler wrapper to handle sending responses and error cases
12611270
* @param promiseRequestHandler
12621271
*/
1263-
function promiseWrapper(promiseRequestHandler: RequestHandler) {
1272+
export function promiseWrapper(promiseRequestHandler: RequestHandler) {
12641273
return async function promWrapper(req: express.Request, res: express.Response, next: express.NextFunction) {
12651274
debug(`handle: ${req.method} ${req.originalUrl}`);
12661275
try {
12671276
const result = await promiseRequestHandler(req, res, next);
1268-
res.status(200).send(result);
1277+
if (typeof result === 'object' && result !== null && 'body' in result && 'status' in result) {
1278+
const { status, body } = result as { status: number; body: unknown };
1279+
res.status(status).send(body);
1280+
} else {
1281+
res.status(200).send(result);
1282+
}
12691283
} catch (e) {
12701284
handleRequestHandlerError(res, e);
12711285
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import 'should';
2+
import * as sinon from 'sinon';
3+
import * as express from 'express';
4+
import { ApiResponseError, BitGo } from 'bitgo';
5+
import { promiseWrapper, redirectRequest } from '../../../src/clientRoutes';
6+
7+
describe('common methods', () => {
8+
describe('redirectRequest', () => {
9+
let bitgo: BitGo;
10+
let req: express.Request;
11+
let next: express.NextFunction;
12+
13+
beforeEach(() => {
14+
bitgo = new BitGo({ env: 'test' });
15+
req = {
16+
body: {},
17+
params: {},
18+
bitgo,
19+
} as express.Request;
20+
next = () => undefined;
21+
});
22+
23+
afterEach(() => {
24+
sinon.restore();
25+
});
26+
27+
it('should handle GET request and return status and body', async () => {
28+
const url = 'https://example.com/api';
29+
const response = { res: { statusCode: 200 }, result: async () => ({ success: true }) };
30+
sinon
31+
.stub(bitgo, 'get')
32+
.withArgs(url)
33+
.returns(response as any);
34+
35+
const result = await redirectRequest(bitgo, 'GET', url, req, next);
36+
result.status.should.equal(200);
37+
result.body.should.deepEqual({ success: true });
38+
});
39+
40+
it('should handle POST request and return status and body', async () => {
41+
const url = 'https://example.com/api';
42+
req.body = { data: 'test' };
43+
const response = { res: { statusCode: 201 }, result: async () => ({ success: true }) };
44+
sinon
45+
.stub(bitgo, 'post')
46+
.withArgs(url)
47+
.returns({ send: () => response } as any);
48+
49+
const result = await redirectRequest(bitgo, 'POST', url, req, next);
50+
result.status.should.equal(201);
51+
result.body.should.deepEqual({ success: true });
52+
});
53+
54+
it('should handle error response and return status and body', async () => {
55+
const url = 'https://example.com/api';
56+
const response = {
57+
result: async () => {
58+
throw new ApiResponseError('Bad Request', 400);
59+
},
60+
};
61+
sinon
62+
.stub(bitgo, 'get')
63+
.withArgs(url)
64+
.returns(response as any);
65+
66+
await redirectRequest(bitgo, 'GET', url, req, next).should.be.rejectedWith(ApiResponseError, {
67+
message: 'Bad Request',
68+
status: 400,
69+
});
70+
});
71+
});
72+
73+
describe('promiseWrapper', () => {
74+
afterEach(() => {
75+
sinon.restore();
76+
});
77+
it('should handle successful request', async () => {
78+
const handler = sinon.stub().resolves({ status: 200, body: { success: true } });
79+
const req: any = {};
80+
const res: any = {
81+
status: sinon.stub().returnsThis(),
82+
send: sinon.stub().returnsThis(),
83+
};
84+
const next = sinon.stub();
85+
86+
await promiseWrapper(handler)(req, res, next);
87+
88+
res.status.calledWith(200).should.be.true();
89+
res.send.calledWith({ success: true }).should.be.true();
90+
});
91+
92+
it('should handle successful request with status 202', async () => {
93+
const handler = sinon.stub().resolves({ status: 202, body: { success: true } });
94+
const req: any = {};
95+
const res: any = {
96+
status: sinon.stub().returnsThis(),
97+
send: sinon.stub().returnsThis(),
98+
};
99+
const next = sinon.stub();
100+
101+
await promiseWrapper(handler)(req, res, next);
102+
103+
res.status.calledWith(202).should.be.true();
104+
res.send.calledWith({ success: true }).should.be.true();
105+
});
106+
107+
it('should handle successful request with default status', async () => {
108+
const handler = sinon.stub().resolves({ success: true });
109+
const req: any = {};
110+
const res: any = {
111+
status: sinon.stub().returnsThis(),
112+
send: sinon.stub().returnsThis(),
113+
};
114+
const next = sinon.stub();
115+
116+
await promiseWrapper(handler)(req, res, next);
117+
118+
res.status.calledWith(200).should.be.true();
119+
res.send.calledWith({ success: true }).should.be.true();
120+
});
121+
122+
it('should handle error request', async () => {
123+
const handler = sinon.stub().rejects(new Error('Test error'));
124+
const req: any = {};
125+
const res: any = {
126+
status: sinon.stub().returnsThis(),
127+
send: sinon.stub().returnsThis(),
128+
};
129+
const next = sinon.stub();
130+
131+
await promiseWrapper(handler)(req, res, next);
132+
133+
res.status.calledWith(500).should.be.true();
134+
res.send.calledWithMatch((result: any) => result.message === 'Test error').should.be.true();
135+
});
136+
});
137+
});

0 commit comments

Comments
 (0)