Repository: koajs/koa
Pull request #930 (2017-03-08 06:54:33)

res: use http.ServerResponse._header when accessors exist 
closes https://github.com/koajs/koa/pull/925

codecov[bot]: # [Codecov](https://codecov.io/gh/koajs/koa/pull/930?src=pr&el=h1) Report
> Merging [#930](https://codecov.io/gh/koajs/koa/pull/930?src=pr&el=desc) into [master](https://codecov.io/gh/koajs/koa/commit/188c0968e1f1f9bf03d7fba451979856ecc74bea?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.


```diff
@@          Coverage Diff          @@
##           master   #930   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           4      4           
  Lines         425    431    +6     
  Branches      101    103    +2     
=====================================
+ Hits          425    431    +6
```


| [Impacted Files](https://codecov.io/gh/koajs/koa/pull/930?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [lib/context.js](https://codecov.io/gh/koajs/koa/compare/188c0968e1f1f9bf03d7fba451979856ecc74bea...c24122b73ec0fb5ceddf37fd3c1d3449525747c4?src=pr&el=tree#diff-bGliL2NvbnRleHQuanM=) | `100% <100%> (ø)` | :white_check_mark: |
| [lib/response.js](https://codecov.io/gh/koajs/koa/compare/188c0968e1f1f9bf03d7fba451979856ecc74bea...c24122b73ec0fb5ceddf37fd3c1d3449525747c4?src=pr&el=tree#diff-bGliL3Jlc3BvbnNlLmpz) | `100% <100%> (ø)` | :white_check_mark: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/koajs/koa/pull/930?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/koajs/koa/pull/930?src=pr&el=footer). Last update [188c096...c24122b](https://codecov.io/gh/koajs/koa/compare/188c0968e1f1f9bf03d7fba451979856ecc74bea...c24122b73ec0fb5ceddf37fd3c1d3449525747c4?src=pr&el=footer&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Patch:
diff --git a/lib/context.js b/lib/context.js
index 94b5a42..8e46d20 100644
--- a/lib/context.js
+++ b/lib/context.js
@@ -121,8 +121,16 @@ const proto = module.exports = {
       return;
     }
 
-    // unset all headers, and set those specified
-    this.res._headers = {};
+    const { res } = this;
+
+    // first unset all headers
+    if (typeof res.getHeaderNames === 'function') {
+      res.getHeaderNames().forEach(name => res.removeHeader(name));
+    } else {
+      res._headers = {}; // Node < 7.7
+    }
+
+    // then set those specified
     this.set(err.headers);
 
     // force text/plain
diff --git a/lib/response.js b/lib/response.js
index 87ece39..0e77207 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -44,7 +44,10 @@ module.exports = {
    */
 
   get header() {
-    return this.res._headers || {};
+    const { res } = this;
+    return typeof res.getHeaders === 'function'
+      ? res.getHeaders()
+      : res._headers || {};  // Node < 7.7
   },
 
   /**


Test patch:
diff --git a/test/context/onerror.js b/test/context/onerror.js
index 61b989d..d567259 100644
--- a/test/context/onerror.js
+++ b/test/context/onerror.js
@@ -1,8 +1,10 @@
 
 'use strict';
 
+const assert = require('assert');
 const request = require('supertest');
 const Koa = require('../..');
+const context = require('../helpers/context');
 
 describe('ctx.onerror(err)', () => {
   it('should respond', done => {
@@ -169,5 +171,23 @@ describe('ctx.onerror(err)', () => {
         .expect('Content-Type', 'text/plain; charset=utf-8')
         .expect('Internal Server Error', done);
     });
+
+    it('should use res.getHeaderNames() accessor when available', () => {
+      let removed = 0;
+      const ctx = context();
+
+      ctx.app.emit = () => {};
+      ctx.res = {
+        getHeaderNames: () => ['content-type', 'content-length'],
+        removeHeader: () => removed++,
+        end: () => {},
+        emit: () => {}
+      };
+
+      ctx.onerror(new Error('error'));
+
+      assert.equal(removed, 2);
+    });
   });
 });
+
diff --git a/test/response/header.js b/test/response/header.js
index a378067..d12816d 100644
--- a/test/response/header.js
+++ b/test/response/header.js
@@ -1,7 +1,9 @@
 
 'use strict';
 
+const request = require('supertest');
 const response = require('../helpers/context').response;
+const Koa = require('../..');
 
 describe('res.header', () => {
   it('should return the response header object', () => {
@@ -10,6 +12,28 @@ describe('res.header', () => {
     res.header.should.eql({ 'x-foo': 'bar' });
   });
 
+  it('should use res.getHeaders() accessor when available', () => {
+    const res = response();
+    res.res._headers = null;
+    res.res.getHeaders = () => ({ 'x-foo': 'baz' });
+    res.header.should.eql({ 'x-foo': 'baz' });
+  });
+
+  it('should return the response header object when no mocks are in use', async () => {
+    const app = new Koa();
+    let header;
+
+    app.use(ctx => {
+      ctx.set('x-foo', '42');
+      header = Object.assign({}, ctx.response.header);
+    });
+
+    await request(app.listen())
+      .get('/');
+
+    header.should.eql({ 'x-foo': '42' });
+  });
+
   describe('when res._headers not present', () => {
     it('should return empty object', () => {
       const res = response();