Repository: fastify/fastify (JavaScript)

Issue:
hasHeader returns false for existing response header
## 🐛 Bug Report

There is an issue with hasHeader method of Reply. (https://www.fastify.io/docs/latest/Reply/#hasheaderkey)
It returns false in the case of getHeader returning a value, however reply.raw.hasHeader returns true in the same case.

## To Reproduce

Steps to reproduce the behavior:

Try to add a header directly to reply via .raw anywhere in the code. (Found the place that does it in our code to check why it happens initially).

Then you will be able to do getHeader normally, as it uses .raw object to do hasHeader:
https://github.com/fastify/fastify/blob/73c0b6f598e0568dfd2944d715dfa7aa997ae457/lib/reply.js#L196-L200

But internal hasHeader function doesn't check that:
https://github.com/fastify/fastify/blob/73c0b6f598e0568dfd2944d715dfa7aa997ae457/lib/reply.js#L212

## Example code to illustrate

![image](https://user-images.githubusercontent.com/1105759/115752472-ccbe8700-a3a2-11eb-9695-0e933ece9330.png)

Result:

![image](https://user-images.githubusercontent.com/1105759/115752692-05f6f700-a3a3-11eb-869a-4642ad53bd3c.png)

## Expected behavior

hasHeader should return True for both cases

## Your Environment

- *node version*: 15
- *fastify version*: latest
- *os*: Mac




Discussion:
To me `hasHeader` should rely on `getHeaders`. see: https://github.com/fastify/fastify/blob/ba43b314b156b39732dc614826806d4ef61700b8/lib/reply.js#L212

I don't see any drawback on that but @mcollina could you confirm?
confirmed!
Well, it should be based on the same logic. We can avoid generating the wrapper object.
I'll work on it.



Patch:
diff --git a/lib/reply.js b/lib/reply.js
index 2b103c88fba..a6bff143886 100644
--- a/lib/reply.js
+++ b/lib/reply.js
@@ -209,7 +209,11 @@ Reply.prototype.getHeaders = function () {
 }
 
 Reply.prototype.hasHeader = function (key) {
-  return this[kReplyHeaders][key.toLowerCase()] !== undefined
+  key = key.toLowerCase()
+  if (this[kReplyHeaders][key] !== undefined) {
+    return true
+  }
+  return this.raw.hasHeader(key)
 }
 
 Reply.prototype.removeHeader = function (key) {


Test patch:
diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js
index d357b27bc0d..24d44373d5c 100644
--- a/test/internals/reply.test.js
+++ b/test/internals/reply.test.js
@@ -1094,6 +1094,35 @@ test('reply.header can reset the value', t => {
   })
 })
 
+// https://github.com/fastify/fastify/issues/3030
+test('reply.hasHeader computes raw and fastify headers', t => {
+  t.plan(4)
+
+  const fastify = require('../../')()
+
+  t.teardown(fastify.close.bind(fastify))
+
+  fastify.get('/headers', function (req, reply) {
+    reply.header('x-foo', 'foo')
+    reply.raw.setHeader('x-bar', 'bar')
+    t.ok(reply.hasHeader('x-foo'))
+    t.ok(reply.hasHeader('x-bar'))
+
+    reply.send()
+  })
+
+  fastify.listen(0, err => {
+    t.error(err)
+    fastify.server.unref()
+    sget({
+      method: 'GET',
+      url: 'http://localhost:' + fastify.server.address().port + '/headers'
+    }, () => {
+      t.pass()
+    })
+  })
+})
+
 test('Reply should handle JSON content type with a charset', t => {
   t.plan(16)