Skip to content

Commit

Permalink
test: refactor app router on-demand revalidation test and add test ca…
Browse files Browse the repository at this point in the history
…ses for not prerendered content
  • Loading branch information
pieh committed Jun 18, 2024
1 parent f4eeaa2 commit b8f5208
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 231 deletions.
343 changes: 114 additions & 229 deletions tests/e2e/on-demand-app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,75 @@ import { expect } from '@playwright/test'
import { test } from '../utils/playwright-helpers.js'

test.describe('app router on-demand revalidation', () => {
test('revalidatePath', async ({ page, pollUntilHeadersMatch, serverComponents }) => {
// in case there is retry or some other test did hit that path before
// we want to make sure that cdn cache is not warmed up
const purgeCdnCache = await page.goto(
new URL('/api/purge-cdn?path=/static-fetch/1', serverComponents.url).href,
)
expect(purgeCdnCache?.status()).toBe(200)

// wait a bit until cdn cache purge propagates
await page.waitForTimeout(500)

const response1 = await pollUntilHeadersMatch(
new URL('static-fetch/1', serverComponents.url).href,
{
for (const { label, prerendered, pagePath, revalidateApiPath, expectedH1Content } of [
{
label: 'revalidatePath (prerendered page)',
prerendered: true,
pagePath: '/static-fetch/1',
revalidateApiPath: '/api/on-demand-revalidate/path?path=/static-fetch/1',
expectedH1Content: 'Hello, Statically fetched show 1',
},
{
label: 'revalidateTag (prerendered page)',
prerendered: true,
pagePath: '/static-fetch/2',
revalidateApiPath: '/api/on-demand-revalidate/tag?tag=show-2',
expectedH1Content: 'Hello, Statically fetched show 2',
},
{
label: 'revalidatePath (not prerendered page)',
prerendered: false,
pagePath: '/static-fetch/3',
revalidateApiPath: '/api/on-demand-revalidate/path?path=/static-fetch/3',
expectedH1Content: 'Hello, Statically fetched show 3',
},
{
label: 'revalidateTag (not prerendered page)',
prerendered: false,
pagePath: '/static-fetch/4',
revalidateApiPath: '/api/on-demand-revalidate/tag?tag=show-4',
expectedH1Content: 'Hello, Statically fetched show 4',
},
]) {
test(label, async ({ page, pollUntilHeadersMatch, serverComponents }) => {
// in case there is retry or some other test did hit that path before
// we want to make sure that cdn cache is not warmed up
const purgeCdnCache = await page.goto(
new URL(`/api/purge-cdn?path=${pagePath}`, serverComponents.url).href,
)
expect(purgeCdnCache?.status()).toBe(200)

// wait a bit until cdn cache purge propagates
await page.waitForTimeout(500)

const response1 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
headersToMatch: {
// either first time hitting this route or we invalidated
// just CDN node in earlier step
// we will invoke function and see Next cache hit status \
// in the response because it was prerendered at build time
// in the response if it was prerendered at build time
// or regenerated in previous attempt to run this test
'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m],
'cache-status': [
/"Netlify Edge"; fwd=(miss|stale)/m,
prerendered ? /"Next.js"; hit/m : /"Next.js"; (hit|fwd=miss)/m,
],
},
headersNotMatchedMessage:
'First request to tested page should be a miss or stale on the Edge and hit in Next.js',
},
)
const headers1 = response1?.headers() || {}
expect(response1?.status()).toBe(200)
expect(headers1['x-nextjs-cache']).toBeUndefined()
expect(headers1['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)
})
const headers1 = response1?.headers() || {}
expect(response1?.status()).toBe(200)
expect(headers1['x-nextjs-cache']).toBeUndefined()
expect(headers1['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

const date1 = await page.textContent('[data-testid="date-now"]')
const date1 = await page.textContent('[data-testid="date-now"]')

const h1 = await page.textContent('h1')
expect(h1).toBe('Hello, Statically fetched show 1')
const h1 = await page.textContent('h1')
expect(h1).toBe(expectedH1Content)

const response2 = await pollUntilHeadersMatch(
new URL('static-fetch/1', serverComponents.url).href,
{
const response2 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
headersToMatch: {
// we are hitting the same page again and we most likely will see
// CDN hit (in this case Next reported cache status is omitted
Expand All @@ -52,36 +80,31 @@ test.describe('app router on-demand revalidation', () => {
},
headersNotMatchedMessage:
'Second request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)',
},
)
const headers2 = response2?.headers() || {}
expect(response2?.status()).toBe(200)
expect(headers2['x-nextjs-cache']).toBeUndefined()
if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers2['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
expect(date2).toBe(date1)

const revalidate = await page.goto(
new URL('/api/on-demand-revalidate/path', serverComponents.url).href,
)
expect(revalidate?.status()).toBe(200)

// wait a bit until cdn tags and invalidated and cdn is purged
await page.waitForTimeout(500)

// now after the revalidation it should have a different date
const response3 = await pollUntilHeadersMatch(
new URL('static-fetch/1', serverComponents.url).href,
{
})
const headers2 = response2?.headers() || {}
expect(response2?.status()).toBe(200)
expect(headers2['x-nextjs-cache']).toBeUndefined()
if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers2['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
expect(date2).toBe(date1)

const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href)
expect(revalidate?.status()).toBe(200)

// wait a bit until cdn tags and invalidated and cdn is purged
await page.waitForTimeout(500)

// now after the revalidation it should have a different date
const response3 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
headersToMatch: {
// revalidatePath just marks the page(s) as invalid and does NOT
// automatically refreshes the cache. This request will cause
Expand All @@ -92,156 +115,19 @@ test.describe('app router on-demand revalidation', () => {
},
headersNotMatchedMessage:
'Third request to tested page should be a miss or stale on the Edge and miss in Next.js after on-demand revalidation',
},
)
const headers3 = response3?.headers() || {}
expect(response3?.status()).toBe(200)
expect(headers3?.['x-nextjs-cache']).toBeUndefined()
expect(headers3['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
expect(date3).not.toBe(date2)

const response4 = await pollUntilHeadersMatch(
new URL('static-fetch/1', serverComponents.url).href,
{
headersToMatch: {
// we are hitting the same page again and we most likely will see
// CDN hit (in this case Next reported cache status is omitted
// as it didn't actually take place in handling this request)
// or we will see CDN miss because different CDN node handled request
'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m,
},
headersNotMatchedMessage:
'Fourth request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)',
},
)
const headers4 = response4?.headers() || {}
expect(response4?.status()).toBe(200)
expect(headers4?.['x-nextjs-cache']).toBeUndefined()
if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers4['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date4 = await page.textContent('[data-testid="date-now"]')
expect(date4).toBe(date3)
})

test('revalidateTag', async ({ page, pollUntilHeadersMatch, serverComponents }) => {
// in case there is retry or some other test did hit that path before
// we want to make sure that cdn cache is not warmed up
const purgeCdnCache = await page.goto(
new URL('/api/purge-cdn?path=/static-fetch-1', serverComponents.url).href,
)
expect(purgeCdnCache?.status()).toBe(200)

// wait a bit until cdn cache purge propagates
await page.waitForTimeout(500)

const response1 = await pollUntilHeadersMatch(
new URL('static-fetch-1', serverComponents.url).href,
{
headersToMatch: {
// either first time hitting this route or we invalidated
// just CDN node in earlier step
// we will invoke function and see Next cache hit status \
// in the response because it was prerendered at build time
// or regenerated in previous attempt to run this test
'cache-status': [/"Netlify Edge"; fwd=(miss|stale)/m, /"Next.js"; hit/m],
},
headersNotMatchedMessage:
'First request to tested page should be a miss or stale on the Edge and hit in Next.js',
},
)
const headers1 = response1?.headers() || {}
expect(response1?.status()).toBe(200)
expect(headers1['x-nextjs-cache']).toBeUndefined()
expect(headers1['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

const date1 = await page.textContent('[data-testid="date-now"]')

const h1 = await page.textContent('h1')
expect(h1).toBe('Hello, Static Fetch 1')

const response2 = await pollUntilHeadersMatch(
new URL('static-fetch-1', serverComponents.url).href,
{
headersToMatch: {
// we are hitting the same page again and we most likely will see
// CDN hit (in this case Next reported cache status is omitted
// as it didn't actually take place in handling this request)
// or we will see CDN miss because different CDN node handled request
'cache-status': /"Netlify Edge"; (hit|fwd=miss|fwd=stale)/m,
},
headersNotMatchedMessage:
'Second request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)',
},
)
const headers2 = response2?.headers() || {}
expect(response2?.status()).toBe(200)
expect(headers2['x-nextjs-cache']).toBeUndefined()
if (!headers2['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers2['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
expect(date2).toBe(date1)

const revalidate = await page.goto(
new URL('/api/on-demand-revalidate/tag', serverComponents.url).href,
)
expect(revalidate?.status()).toBe(200)

// wait a bit until cdn tags and invalidated and cdn is purged
await page.waitForTimeout(500)

// now after the revalidation it should have a different date
const response3 = await pollUntilHeadersMatch(
new URL('static-fetch-1', serverComponents.url).href,
{
headersToMatch: {
// revalidateTag just marks the page(s) as invalid and does NOT
// automatically refreshes the cache. This request will cause
// Next.js cache miss and new response will be generated and cached
// Depending if we hit same CDN node as previous request, we might
// get either fwd=miss or fwd=stale
'cache-status': [/"Next.js"; fwd=miss/m, /"Netlify Edge"; fwd=(miss|stale)/m],
},
headersNotMatchedMessage:
'Third request to tested page should be a miss or stale on the Edge and miss in Next.js after on-demand revalidation',
},
)
const headers3 = response3?.headers() || {}
expect(response3?.status()).toBe(200)
expect(headers3?.['x-nextjs-cache']).toBeUndefined()
expect(headers3['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
expect(date3).not.toBe(date2)

const response4 = await pollUntilHeadersMatch(
new URL('static-fetch-1', serverComponents.url).href,
{
})
const headers3 = response3?.headers() || {}
expect(response3?.status()).toBe(200)
expect(headers3?.['x-nextjs-cache']).toBeUndefined()
expect(headers3['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
expect(date3).not.toBe(date2)

const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
headersToMatch: {
// we are hitting the same page again and we most likely will see
// CDN hit (in this case Next reported cache status is omitted
Expand All @@ -251,23 +137,22 @@ test.describe('app router on-demand revalidation', () => {
},
headersNotMatchedMessage:
'Fourth request to tested page should most likely be a hit on the Edge (optionally miss or stale if different CDN node)',
},
)
const headers4 = response4?.headers() || {}
expect(response4?.status()).toBe(200)
expect(headers4?.['x-nextjs-cache']).toBeUndefined()

if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers4['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date4 = await page.textContent('[data-testid="date-now"]')
expect(date4).toBe(date3)
})
})
const headers4 = response4?.headers() || {}
expect(response4?.status()).toBe(200)
expect(headers4?.['x-nextjs-cache']).toBeUndefined()
if (!headers4['cache-status'].includes('"Netlify Edge"; hit')) {
// if we missed CDN cache, we will see Next cache hit status
// as we reuse cached response
expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m)
}
expect(headers4['netlify-cdn-cache-control']).toBe(
's-maxage=31536000, stale-while-revalidate=31536000',
)

// the page is cached
const date4 = await page.textContent('[data-testid="date-now"]')
expect(date4).toBe(date3)
})
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { NextRequest, NextResponse } from 'next/server'
import { revalidatePath } from 'next/cache'

export async function GET(request: NextRequest) {
revalidatePath('/static-fetch/[id]', 'page')
const url = new URL(request.url)
const pathToRevalidate = url.searchParams.get('path') ?? '/static-fetch/[id]/page'

revalidatePath(pathToRevalidate)
return NextResponse.json({ revalidated: true, now: new Date().toISOString() })
}

Expand Down
Loading

0 comments on commit b8f5208

Please sign in to comment.