diff --git a/API.md b/API.md index 1fe7e1b..77d67d0 100755 --- a/API.md +++ b/API.md @@ -53,10 +53,10 @@ Injects a fake request into an HTTP server. - `remoteAddress` - an optional string specifying the client remote address. Defaults to `'127.0.0.1'`. - `payload` - an optional request payload. Can be a string, Buffer, Stream or object. - `simulate` - an object containing flags to simulate various conditions: - - `end` - indicates whether the request will fire an `end` event. Defaults to `undefined`, meaning an `end` event will fire. + - `end` - indicates whether the request will fire an `end` event. Defaults to `true`, meaning an `end` event will fire. - `split` - indicates whether the request payload will be split into chunks. Defaults to `undefined`, meaning payload will not be chunked. - `error` - whether the request will emit an `error` event. Defaults to `undefined`, meaning no `error` event will be emitted. If set to `true`, the emitted error will have a message of `'Simulated'`. - - `close` - whether the request will emit a `close` event. Defaults to `undefined`, meaning no `close` event will be emitted. + - `close` - whether the request will emit a `close` event. Defaults to `true`, meaning a `close` event will be emitted. - `validate` - Optional flag to validate this options object. Defaults to `true`. Returns a response object where: diff --git a/lib/request.js b/lib/request.js index d5d71c8..75d800c 100755 --- a/lib/request.js +++ b/lib/request.js @@ -15,7 +15,7 @@ exports = module.exports = internals.Request = class extends Stream.Readable { constructor(options) { super({ - emitClose: !!(options.simulate?.close), + emitClose: options.simulate?.close !== false, autoDestroy: true // This is the default in node 14+ }); @@ -84,7 +84,6 @@ exports = module.exports = internals.Request = class extends Stream.Readable { this._shot = { payload, - isDone: false, simulate: options.simulate ?? {} }; @@ -114,18 +113,6 @@ exports = module.exports = internals.Request = class extends Stream.Readable { setImmediate(() => { - if (this._shot.isDone) { - /* $lab:coverage:off$ */ - if (this._shot.simulate.end !== false) { // 'end' defaults to true - this.push(null); - } - /* $lab:coverage:on$ */ - - return; - } - - this._shot.isDone = true; - if (this._shot.payload) { if (this._shot.simulate.split) { this.push(this._shot.payload.slice(0, 1)); @@ -142,8 +129,8 @@ exports = module.exports = internals.Request = class extends Stream.Readable { else if (this._shot.simulate.end !== false) { // 'end' defaults to true this.push(null); } - else if (this._shot.simulate.close) { // manually close (out of spec) - this.emit('close'); + else { + this.destroy(); } }); } diff --git a/lib/response.js b/lib/response.js index 8fb80b3..8e07182 100755 --- a/lib/response.js +++ b/lib/response.js @@ -43,6 +43,7 @@ exports = module.exports = internals.Response = class extends Http.ServerRespons this.removeListener('close', abort); + req.destroy(); process.nextTick(() => onEnd(res)); }; diff --git a/test/index.js b/test/index.js index b69cec7..1fe5885 100755 --- a/test/index.js +++ b/test/index.js @@ -725,7 +725,7 @@ describe('_read()', () => { req.on('end', () => { - res.writeHead(200, { 'Content-Length': 0 }); + res.writeHead(200, { 'Content-Length': buffer.length }); res.end(buffer); req.destroy(); }); @@ -734,9 +734,9 @@ describe('_read()', () => { }; const body = 'something special just for you'; - const res = await Shot.inject(dispatch, { method: 'get', url: '/', payload: body }); + const res = await Shot.inject(dispatch, { method: 'post', url: '/', payload: body }); expect(res.payload).to.equal(body); - expect(events).to.equal(['end']); + expect(events).to.equal(['end', 'close']); }); it('supports async iteration', async () => { @@ -758,6 +758,22 @@ describe('_read()', () => { expect(res.payload).to.equal(body); }); + it('emits req "close" on "finish"', async () => { + + const events = []; + const dispatch = function (req, res) { + + res.end('ok'); + + internals.trackStreamLifetime(req, events, 'req'); + internals.trackStreamLifetime(res, events, 'res'); + }; + + const res = await Shot.inject(dispatch, { method: 'get', url: '/' }); + expect(res.payload).to.equal('ok'); + expect(events).to.equal(['close (req)']); + }); + it('simulates split', async () => { const events = []; @@ -782,7 +798,7 @@ describe('_read()', () => { const body = 'something special just for you'; const res = await Shot.inject(dispatch, { method: 'get', url: '/', payload: body, simulate: { split: true } }); expect(res.payload).to.equal(body); - expect(events).to.equal(['end']); + expect(events).to.equal(['end', 'close']); }); it('simulates error (close = false)', async () => { @@ -841,7 +857,7 @@ describe('_read()', () => { internals.trackStreamLifetime(req, events); }; - Shot.inject(dispatch, { method: 'get', url: '/', simulate: { end: false } }); // Stuck + Shot.inject(dispatch, { method: 'get', url: '/', simulate: { close: false, end: false } }); // Stuck await internals.wait(10); expect(events).to.equal([]); }); @@ -855,7 +871,7 @@ describe('_read()', () => { internals.trackStreamLifetime(req, events); }; - Shot.inject(dispatch, { method: 'get', url: '/', payload: '1234567', simulate: { end: false } }); // Stuck + Shot.inject(dispatch, { method: 'get', url: '/', payload: '1234567', simulate: { close: false, end: false } }); // Stuck await internals.wait(10); expect(events).to.equal([]); });