@@ -4,6 +4,8 @@ import { cp } from 'node:fs/promises'
44import { createRequire } from 'node:module'
55import { join } from 'node:path'
66import { gunzipSync } from 'node:zlib'
7+ import { HttpResponse , http , passthrough } from 'msw'
8+ import { setupServer } from 'msw/node'
79import { gt , prerelease } from 'semver'
810import { v4 } from 'uuid'
911import { Mock , afterAll , beforeAll , beforeEach , describe , expect , test , vi } from 'vitest'
@@ -22,6 +24,8 @@ import {
2224 startMockBlobStore ,
2325} from '../utils/helpers.js'
2426import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs'
27+ import { purgeCache } from '@netlify/functions'
28+ import { afterEach } from 'node:test'
2529
2630const mockedCp = cp as Mock <
2731 Parameters < ( typeof import ( 'node:fs/promises' ) ) [ 'cp' ] > ,
@@ -36,9 +40,32 @@ vi.mock('node:fs/promises', async (importOriginal) => {
3640 }
3741} )
3842
43+ let server : ReturnType < typeof setupServer >
44+
3945// Disable the verbose logging of the lambda-local runtime
4046getLogger ( ) . level = 'alert'
4147
48+ const purgeAPI = vi . fn ( )
49+
50+ beforeAll ( ( ) => {
51+ server = setupServer (
52+ http . post ( 'https://api.netlify.com/api/v1/purge' , async ( { request } ) => {
53+ purgeAPI ( await request . json ( ) )
54+
55+ return HttpResponse . json ( {
56+ ok : true ,
57+ } )
58+ } ) ,
59+ http . all ( / .* / , ( ) => passthrough ( ) ) ,
60+ )
61+ server . listen ( )
62+ } )
63+
64+ afterAll ( ( ) => {
65+ // Disable API mocking after the tests are done.
66+ server . close ( )
67+ } )
68+
4269beforeEach < FixtureTestContext > ( async ( ctx ) => {
4370 // set for each test a new deployID and siteID
4471 ctx . deployID = generateRandomObjectID ( )
@@ -48,9 +75,15 @@ beforeEach<FixtureTestContext>(async (ctx) => {
4875 // hide debug logs in tests
4976 vi . spyOn ( console , 'debug' ) . mockImplementation ( ( ) => { } )
5077
78+ purgeAPI . mockClear ( )
79+
5180 await startMockBlobStore ( ctx )
5281} )
5382
83+ afterEach ( ( ) => {
84+ vi . unstubAllEnvs ( )
85+ } )
86+
5487test < FixtureTestContext > ( 'Test that the simple next app is working' , async ( ctx ) => {
5588 await createFixture ( 'simple' , ctx )
5689 await runPlugin ( ctx )
@@ -210,6 +243,39 @@ test<FixtureTestContext>('cacheable route handler is cached on cdn (revalidate=f
210243 )
211244} )
212245
246+ test < FixtureTestContext > ( 'purge API is not used when unstable_cache cache entry gets stale' , async ( ctx ) => {
247+ await createFixture ( 'simple' , ctx )
248+ await runPlugin ( ctx )
249+
250+ // set the NETLIFY_PURGE_API_TOKEN to get pass token check and allow fetch call to be made
251+ vi . stubEnv ( 'NETLIFY_PURGE_API_TOKEN' , 'mock' )
252+
253+ const page1 = await invokeFunction ( ctx , {
254+ url : '/unstable_cache' ,
255+ } )
256+ const data1 = load ( page1 . body ) ( 'pre' ) . text ( )
257+
258+ // allow for cache entry to get stale
259+ await new Promise ( ( res ) => setTimeout ( res , 2000 ) )
260+
261+ const page2 = await invokeFunction ( ctx , {
262+ url : '/unstable_cache' ,
263+ } )
264+ const data2 = load ( page2 . body ) ( 'pre' ) . text ( )
265+
266+ const page3 = await invokeFunction ( ctx , {
267+ url : '/unstable_cache' ,
268+ } )
269+ const data3 = load ( page3 . body ) ( 'pre' ) . text ( )
270+
271+ expect ( purgeAPI , 'Purge API should not be hit' ) . toHaveBeenCalledTimes ( 0 )
272+ expect (
273+ data2 ,
274+ 'Should use stale cache entry for current request and invalidate it in background' ,
275+ ) . toBe ( data1 )
276+ expect ( data3 , 'Should use updated cache entry' ) . not . toBe ( data2 )
277+ } )
278+
213279test < FixtureTestContext > ( 'cacheable route handler is cached on cdn (revalidate=15)' , async ( ctx ) => {
214280 await createFixture ( 'simple' , ctx )
215281 await runPlugin ( ctx )
0 commit comments