From c3ed48a4520ad7ec6be2950c6da8e7842cc30470 Mon Sep 17 00:00:00 2001 From: Mikhail Losev Date: Tue, 24 Apr 2018 17:01:30 +0300 Subject: [PATCH] use IncomingMessageMock for ResponseMock and SpecialPage (#1582) * use IncomingMessageMock for ResponseMock and SpecialPage * fix tests * renames --- src/request-pipeline/incoming-message-mock.js | 26 ++++++ src/request-pipeline/index.js | 15 +-- .../request-hooks/response-mock.js | 28 +++--- src/request-pipeline/special-page.js | 12 +-- test/server/proxy-test.js | 91 +++++++++++++++---- test/server/request-hook-test.js | 14 ++- 6 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 src/request-pipeline/incoming-message-mock.js diff --git a/src/request-pipeline/incoming-message-mock.js b/src/request-pipeline/incoming-message-mock.js new file mode 100644 index 000000000..e9e34d16c --- /dev/null +++ b/src/request-pipeline/incoming-message-mock.js @@ -0,0 +1,26 @@ +import { Readable } from 'stream'; + +export default class IncomingMessageMock extends Readable { + constructor (init) { + super(); + + this.headers = init.headers; + this.trailers = init.trailers; + this.statusCode = init.statusCode; + this._body = this._getBody(init._body); + } + + _read () { + this.push(this._body); + this._body = null; + } + + _getBody (body) { + if (!body) + return new Buffer(0); + + const bodyStr = typeof body === 'object' ? JSON.stringify(body) : String(body); + + return Buffer.from(bodyStr); + } +} diff --git a/src/request-pipeline/index.js b/src/request-pipeline/index.js index ee59f837b..c5ad5d370 100644 --- a/src/request-pipeline/index.js +++ b/src/request-pipeline/index.js @@ -11,7 +11,7 @@ import { fetchBody, respond404 } from '../utils/http'; import { inject as injectUpload } from '../upload'; import { respondOnWebSocket } from './websocket'; import { PassThrough } from 'stream'; -import * as specialPage from './special-page'; +import createSpecialPageResponse from './special-page'; import matchUrl from 'match-url-wildcard'; import * as requestEventInfo from '../session/events/info'; import REQUEST_EVENT_NAMES from '../session/events/names'; @@ -35,7 +35,7 @@ const stages = { 1: function sendDestinationRequest (ctx, next) { if (ctx.isSpecialPage) { - ctx.destRes = specialPage.getResponse(); + ctx.destRes = createSpecialPageResponse(); next(); } else { @@ -126,7 +126,7 @@ const stages = { }, 4: async function fetchContent (ctx, next) { - await getResponseBody(ctx); + ctx.destResBody = await fetchBody(ctx.destRes); if (ctx.requestFilterRules.length) ctx.saveNonProcessedDestResBody(ctx.destResBody); @@ -293,15 +293,6 @@ function sendRequest (ctx, next) { req.on('socketHangUp', () => ctx.req.socket.end()); } -async function getResponseBody (ctx) { - if (ctx.isSpecialPage) - ctx.destResBody = specialPage.getBody(); - else if (ctx.mock) - ctx.destResBody = ctx.mock.getBody(); - else - ctx.destResBody = await fetchBody(ctx.destRes); -} - // API export function run (req, res, serverInfo, openSessions) { const ctx = new RequestPipelineContext(req, res, serverInfo); diff --git a/src/request-pipeline/request-hooks/response-mock.js b/src/request-pipeline/request-hooks/response-mock.js index 5e2b48ec5..9aeb240cf 100644 --- a/src/request-pipeline/request-hooks/response-mock.js +++ b/src/request-pipeline/request-hooks/response-mock.js @@ -1,3 +1,4 @@ +import IncomingMessageMock from '../incoming-message-mock'; import { JSON_MIME } from '../../utils/content-type'; const PAGE_CONTENT_TYPE = 'text/html; charset=utf-8'; @@ -76,28 +77,23 @@ export default class ResponseMock { statusCode: this.statusCode || 200 }; + if (this.headers) + response.headers = Object.assign(response.headers, this.headers); + if (this.body === void 0) - this.body = EMPTY_PAGE_HTML; + response._body = EMPTY_PAGE_HTML; else if (typeof this.body === 'function') { response.setBody = value => { - this.body = value; + response._body = value; }; - response = Object.assign(response, this.body(this.requestOptions, response)); - delete response.setBody; - } - - if (this.headers) - response.headers = Object.assign(response.headers, this.headers); - - return response; - } - getBody () { - if (!this.body) - return new Uint8Array(0); + response = Object.assign(response, this.body(this.requestOptions, response)); - const bodyStr = typeof this.body === 'object' ? JSON.stringify(this.body) : String(this.body); + delete response.setBody; + } + else + response._body = this.body; - return Buffer.from(bodyStr); + return new IncomingMessageMock(response); } } diff --git a/src/request-pipeline/special-page.js b/src/request-pipeline/special-page.js index 3452ea525..1df74b7da 100644 --- a/src/request-pipeline/special-page.js +++ b/src/request-pipeline/special-page.js @@ -1,14 +1,14 @@ -export function getResponse () { - return { +import IncomingMessageMock from './incoming-message-mock'; + +export default function createSpecialPageResponse () { + return new IncomingMessageMock({ + _body: new Buffer(0), statusCode: 200, trailers: {}, headers: { 'content-type': 'text/html', 'content-length': '0' } - }; + }); } -export function getBody () { - return new Buffer(0); -} diff --git a/test/server/proxy-test.js b/test/server/proxy-test.js index 89f893114..b71150761 100644 --- a/test/server/proxy-test.js +++ b/test/server/proxy-test.js @@ -2125,29 +2125,86 @@ describe('Proxy', () => { }); }); - it('Should allow to use a response mock', () => { - const url = 'http://dummy_page.com'; - const mock = new ResponseMock(); - const rule = new RequestFilterRule(url); - const processedHtml = fs.readFileSync('test/server/data/empty-page/expected.html').toString(); + describe('Response mock', () => { + it('Basic', () => { + const url = 'http://dummy_page.com'; + const mock = new ResponseMock(); + const rule = new RequestFilterRule(url); + const processedHtml = fs.readFileSync('test/server/data/empty-page/expected.html').toString(); - session.addRequestEventListeners(rule, { - onRequest: e => e.setMock(mock) + session.addRequestEventListeners(rule, { + onRequest: e => e.setMock(mock) + }); + + const options = { + url: proxy.openSession(url, session), + headers: { + accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*!/!*;q=0.8' + } + }; + + return request(options) + .then(body => { + compareCode(body, processedHtml); + + session.removeRequestEventListeners(rule); + }); }); - const options = { - url: proxy.openSession(url, session), - headers: { - accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*!/!*;q=0.8' - } - }; + it('Should allow to mock response without body (page)', () => { + const url = 'http://dummy_page.com'; + const mock = new ResponseMock(null, 204); + const rule = new RequestFilterRule(url); - return request(options) - .then(body => { - compareCode(body, processedHtml); + session.addRequestEventListeners(rule, { + onRequest: e => e.setMock(mock) + }); - session.removeRequestEventListeners(rule); + const options = { + url: proxy.openSession(url, session), + resolveWithFullResponse: true, + headers: { + accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*!/!*;q=0.8' + } + }; + + return request(options) + .then(res => { + const expected = fs.readFileSync('test/server/data/empty-page/expected.html').toString(); + + compareCode(res.body, expected); + expect(res.statusCode).eql(200); + + session.removeRequestEventListeners(rule); + }); + }); + + it('Should allow to mock a large response', () => { + const url = 'http://example.com/get'; + const largeResponse = '1234567890'.repeat(1000000); + const mock = new ResponseMock(largeResponse); + const rule = new RequestFilterRule(url); + + session.addRequestEventListeners(rule, { + onRequest: e => e.setMock(mock) }); + + const options = { + url: proxy.openSession(url, session), + headers: { + accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*!/!*;q=0.8', + referer: proxy.openSession('http://example.com', session), + [XHR_HEADERS.requestMarker]: 'true' + } + }; + + return request(options) + .then(body => { + expect(body).eql(largeResponse); + + session.removeRequestEventListeners(rule); + }); + }); }); it('Should allow to set request options', () => { diff --git a/test/server/request-hook-test.js b/test/server/request-hook-test.js index 271a9ef59..39c777a84 100644 --- a/test/server/request-hook-test.js +++ b/test/server/request-hook-test.js @@ -45,7 +45,7 @@ describe('ResponseMock', () => { expect(response.headers['content-type']).eql('application/json'); expect(response.statusCode).eql(200); - expect(mock.getBody().toString()).eql(JSON.stringify(data)); + expect(response.read().toString()).eql(JSON.stringify(data)); }); it('HTML page', () => { @@ -55,7 +55,7 @@ describe('ResponseMock', () => { expect(response.headers['content-type']).to.include('text/html'); expect(response.statusCode).eql(200); - expect(mock.getBody().toString()).eql(html); + expect(response.read().toString()).eql(html); }); it('Empty HTML page', () => { @@ -64,18 +64,16 @@ describe('ResponseMock', () => { expect(response.headers['content-type']).to.include('text/html'); expect(response.statusCode).eql(200); - expect(mock.getBody().toString()).eql(''); + expect(response.read().toString()).eql(''); }); it('Custom status code', () => { const mock = new ResponseMock(null, 204); const response = mock.getResponse(); - const body = mock.getBody(); expect(response.headers['content-type']).to.include('text/html'); expect(response.statusCode).eql(204); - expect(body).to.be.instanceof(Uint8Array); - expect(body.length).eql(0); + expect(response.read()).to.be.null; }); it('Custom headers', () => { @@ -85,7 +83,7 @@ describe('ResponseMock', () => { expect(response.headers['content-type']).eql('application/javascript'); expect(response.statusCode).eql(200); - expect(mock.getBody().toString()).eql(script); + expect(response.read().toString()).eql(script); }); it('Respond function', () => { @@ -112,7 +110,7 @@ describe('ResponseMock', () => { expect(response.headers['content-type']).to.include('text/html'); expect(response.statusCode).eql(555); expect(response.headers['x-calculated-header-name']).eql('calculated-value'); - expect(mock.getBody().toString()).eql('calculated body3'); + expect(response.read().toString()).eql('calculated body3'); }); }); });