Repository: nodejs/undici

Problem statement:
Cache interceptor does not process capitalized `Cache-Control` header in request
## Bug Description

Cache interceptor does not process `Cache-Control: no-store` request header if header name is capitalized.

In code we can see that you look for lowercased header name only.

You should convert header names to lowercase before search for `cache-control`;

## Reproducible By

Please. see above.

## Expected Behavior

Please. see above.

## Logs & Screenshots

<!-- If applicable, add screenshots to help explain your problem, or 
alternatively add your console logs here. -->

## Environment

<!-- This is just your OS and environment information [e.g. Ubuntu 18.04 LTS,
Node v14.14.0] -->
undici v7

### Additional context

<!-- Add any other context about the problem here. -->



Hints:
Can you add a reproduction? It's working correctly here.
```javascript
import { Agent, fetch, interceptors } from "undici";

const dispatcher = new Agent().compose( interceptors.cache() );

for ( let n = 0; n < 2; n++ ) {
    const res = await fetch( "https://httpbingo.org/cache/60", {
        dispatcher,
        "headers": {
            "X-Data": new Date().toISOString(),
            "Cache-Control": "no-store",
        },
    } );

    console.log( ( await res.json() ).headers[ "X-Data" ] );
}
```

1. You can see, that data not changed - this means, that cached response used.
2. If you change `Cache-Control` header to the lower case - cache will be bypassed.
Ah right, thanks!
@mcollina this is related to that "normalizeHeaders" function I was working on that would always normalize the user passed headers. I think we should probably have a "pre-interceptor" that always does this to make things easier down chain.
Cannot be done on the `DispatcherBase#dispatch` directly?

Patch:
diff --git a/lib/interceptor/cache.js b/lib/interceptor/cache.js
--- a/lib/interceptor/cache.js
+++ b/lib/interceptor/cache.js
@@ -233,7 +233,7 @@ function handleResult (
     }
 
     let headers = {
-      ...normaliseHeaders(opts),
+      ...opts.headers,
       'if-modified-since': new Date(result.cachedAt).toUTCString()
     }
 
@@ -319,6 +319,11 @@ module.exports = (opts = {}) => {
         return dispatch(opts, handler)
       }
 
+      opts = {
+        ...opts,
+        headers: normaliseHeaders(opts)
+      }
+
       const reqCacheControl = opts.headers?.['cache-control']
         ? parseCacheControlHeader(opts.headers['cache-control'])
         : undefined
diff --git a/lib/util/cache.js b/lib/util/cache.js
--- a/lib/util/cache.js
+++ b/lib/util/cache.js
@@ -12,13 +12,11 @@ function makeCacheKey (opts) {
     throw new Error('opts.origin is undefined')
   }
 
-  const headers = normaliseHeaders(opts)
-
   return {
     origin: opts.origin.toString(),
     method: opts.method,
     path: opts.path,
-    headers
+    headers: opts.headers
   }
 }
 


Test patch:
diff --git a/test/issue-3904.js b/test/issue-3904.js
new file mode 100644
--- /dev/null
+++ b/test/issue-3904.js
@@ -0,0 +1,58 @@
+const { describe, test, after } = require('node:test')
+const assert = require('node:assert')
+const { createServer } = require('node:http')
+const { once } = require('node:events')
+const MemoryCacheStore = require('../lib/cache/memory-cache-store.js')
+const { Agent, interceptors, request, setGlobalDispatcher } = require('..')
+
+describe('Cache with cache-control: no-store request header', () => {
+  [
+    'CACHE-CONTROL',
+    'cache-control',
+    'Cache-Control'
+  ].forEach(headerName => {
+    test(`should not cache response for request with header: "${headerName}: no-store`, async () => {
+      const store = new MemoryCacheStore()
+      let requestCount = 0
+      const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
+        ++requestCount
+        res.setHeader('Vary', 'Accept-Encoding')
+        res.setHeader('Cache-Control', 'max-age=60')
+        res.end(`Request count: ${requestCount}`)
+      })
+
+      after(async () => {
+        server.close()
+
+        await once(server, 'close')
+      })
+
+      await new Promise(resolve => server.listen(0, resolve))
+      const { port } = server.address()
+      const url = `http://localhost:${port}`
+
+      const agent = new Agent()
+      setGlobalDispatcher(
+        agent.compose(
+          interceptors.cache({
+            store,
+            cacheByDefault: 1000,
+            methods: ['GET']
+          })
+        )
+      )
+
+      const res1 = await request(url, { headers: { [headerName]: 'no-store' } })
+      const body1 = await res1.body.text()
+      assert.strictEqual(body1, 'Request count: 1')
+      assert.strictEqual(requestCount, 1)
+
+      const res2 = await request(url)
+      const body2 = await res2.body.text()
+      assert.strictEqual(body2, 'Request count: 2')
+      assert.strictEqual(requestCount, 2)
+
+      await new Promise(resolve => server.close(resolve))
+    })
+  })
+})