Files
next.js/test/e2e/app-dir/use-cache-infinity-profile/use-cache-infinity-profile.test.ts
5af030c959 Normalize and validate expire and revalidate values to handle Infinity and surface mistakes early (#95493)
Supersedes https://github.com/vercel/next.js/pull/93475

## Summary

There's a few fixes bundled in here.

We're fixing `Infinity` getting serialized to `null`, which caused cache
handlers and build workers to misinterpret it as "don't cache" instead
of "cache forever". We use the existing infinity sentinel for that.

We're also making the checks a bit more specific from `typeof !==
'number'` to forbidding `NaN` and `-Infinity`. The checks are applied
consistently for profiles, inline cache life, and arguments to `fetch`
and `unstable_cache`.

Now that configs are also validated, we need to make sure that we only
do `revalidate > expire` validation when both values are specified.
There's a regression test for that, which
https://github.com/vercel/next.js/pull/93475 didn't do correctly.

Also, per review below:

- Now actually using returned value for `unstable_cache`
- Added validation + infinity fix for `revalidateTag` profile
- Added validation + infinity fix in `config.experimental.staleTimes`
and `config.expireTime`
- Added deep clone for `defaultConfig.cacheLife`

## Test Plan

New e2e tests that fail before the fixes.

---------

Co-authored-by: Zack Tanner <[email protected]>
2026-07-09 02:00:38 +01:00

42 lines
1.6 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
const uuidRegExp =
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
describe('use-cache-infinity-profile', () => {
const { next, isNextStart } = nextTestSetup({
files: __dirname,
// Deployment platforms provide their own cache handlers.
skipDeployment: true,
})
it('caches forever with a configured profile using Infinity revalidate and expire', async () => {
const $ = await next.render$('/')
const initialValue = $('#value').text()
expect(initialValue).toMatch(uuidRegExp)
// An infinite cache life must not degrade into a dynamic cache life, so
// the value stays the same across requests instead of regenerating.
const $second = await next.render$('/')
expect($second('#value').text()).toBe(initialValue)
if (isNextStart) {
// The page must be fully prerendered at build time.
const prerendered = await next.readFile('.next/server/app/index.html')
expect(prerendered).toContain(initialValue)
}
})
it('serves an inline Infinity cache life from a JSON-backed cache handler across requests', async () => {
const $ = await next.render$('/inline?key=a')
const initialValue = $('#value').text()
expect(initialValue).toMatch(uuidRegExp)
// The second request reads the entry back from the cache handler. If the
// infinite cache life doesn't survive the handler's JSON round trip, the
// entry is treated as immediately expired and the value regenerates.
const $second = await next.render$('/inline?key=a')
expect($second('#value').text()).toBe(initialValue)
})
})