Repository: nodejs/undici (JavaScript)

Issue:
Cache prioritises cacheByDefault/max-age over `no-cache` response directive
## Bug Description

When `cacheByDefault` is set, no revalidation occurs for a response which had the `no-cache` directive.

## Reproducible By

```js
const http = require('node:http');

const server = http.createServer((req, res) => {
  console.log('request received');

  req.on('data', () => {});

  req.on('end', () => {
    res.setHeader('cache-control', 'no-cache');
    res.end('OK');

    console.log('Closing server...');
    server.close(() => console.log('Server closed.'));
  });
});

const port = 1234;
server.listen(port, async () => {
  console.log('server listening on port', port);

  const undici = require('undici');

  const dispatcher = new undici.Agent().compose(undici.interceptors.cache({
    cacheByDefault: 1000,
  }));
  
  const res1 = await undici.fetch(`http://localhost:${port}/`, { dispatcher });
  console.log('res1.status:', res1.status);

  await new Promise(resolve => setTimeout(resolve, 200));

  const res2 = await undici.fetch(`http://localhost:${port}/`, { dispatcher });
  console.log('res2.status:', res2.status);
});
```

## Expected Behavior

https://datatracker.ietf.org/doc/html/rfc9111#name-no-cache-2:

> The no-cache response directive, in its unqualified form (without an argument), indicates that the response **MUST NOT** be used to satisfy any other request without forwarding it for validation and receiving a successful response

## Logs & Screenshots

```
server listening on port 1234
request received
Closing server...
Server closed.
res1.status: 200
res2.status: 200
```

## Environment

node: v22.12.0
undici: 7.8.0

### Additional context

<!-- Add any other context about the problem here. -->



Discussion:
If this is indeed a bug, something like this should fix it:

```js
--- a/lib/interceptor/cache.js
+++ b/lib/interceptor/cache.js
@@ -20,7 +20,12 @@ const { AbortError } = require('../core/errors.js')
  */
 function needsRevalidation (result, cacheControlDirectives) {
   if (cacheControlDirectives?.['no-cache']) {
-    // Always revalidate requests with the no-cache directive
+    // Always revalidate requests with the no-cache request directive
+    return true
+  }
+
+  if (result.cacheControlDirectives?.['no-cache']) {
+    // Always revalidate requests with the no-cache response directive
     return true
   }

```



Patch:
diff --git a/lib/interceptor/cache.js b/lib/interceptor/cache.js
index 295c566f255..f7c42a08917 100644
--- a/lib/interceptor/cache.js
+++ b/lib/interceptor/cache.js
@@ -20,7 +20,12 @@ const { AbortError } = require('../core/errors.js')
  */
 function needsRevalidation (result, cacheControlDirectives) {
   if (cacheControlDirectives?.['no-cache']) {
-    // Always revalidate requests with the no-cache directive
+    // Always revalidate requests with the no-cache request directive
+    return true
+  }
+
+  if (result.cacheControlDirectives?.['no-cache'] && !Array.isArray(result.cacheControlDirectives['no-cache'])) {
+    // Always revalidate requests with unqualified no-cache response directive
     return true
   }
 


Test patch:
diff --git a/test/interceptors/cache.js b/test/interceptors/cache.js
index 6ea6ec35ea2..b458bff29bd 100644
--- a/test/interceptors/cache.js
+++ b/test/interceptors/cache.js
@@ -128,6 +128,45 @@ describe('Cache Interceptor', () => {
     }
   })
 
+  test('revalidates reponses with no-cache directive, regardless of cacheByDefault', async () => {
+    let requestCount = 0
+    const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
+      ++requestCount
+      res.setHeader('Vary', 'Accept-Encoding')
+      res.setHeader('cache-control', 'no-cache')
+      res.end(`Request count: ${requestCount}`)
+    }).listen(0)
+
+    after(async () => {
+      server.close()
+
+      await once(server, 'close')
+    })
+
+    await once(server, 'listening')
+
+    const client = new Client(`http://localhost:${server.address().port}`)
+      .compose(interceptors.cache({
+        cacheByDefault: 1000
+      }))
+
+    const request = {
+      origin: 'localhost',
+      method: 'GET',
+      path: '/'
+    }
+
+    const res1 = await client.request(request)
+    const body1 = await res1.body.text()
+    strictEqual(body1, 'Request count: 1')
+    strictEqual(requestCount, 1)
+
+    const res2 = await client.request(request)
+    const body2 = await res2.body.text()
+    strictEqual(body2, 'Request count: 2')
+    strictEqual(requestCount, 2)
+  })
+
   test('stale responses are revalidated before deleteAt (if-modified-since)', async () => {
     const clock = FakeTimers.install({
       shouldClearNativeTimers: true