diff --git a/src/app/api/revalidate/route.spec.ts b/src/app/api/revalidate/route.spec.ts index 28e81d4..b2347b0 100644 --- a/src/app/api/revalidate/route.spec.ts +++ b/src/app/api/revalidate/route.spec.ts @@ -1,7 +1,7 @@ /** * @jest-environment node */ -import { POST } from './route'; +import { GET, POST } from './route'; import { revalidatePath, revalidateTag } from 'next/cache'; // Mock Next.js cache @@ -15,6 +15,96 @@ jest.mock('../../../i18n/routing', () => ({ AVAILABLE_LOCALES: ['en', 'fr'], })); +describe('GET /api/revalidate', () => { + const mockRevalidatePath = revalidatePath as jest.MockedFunction< + typeof revalidatePath + >; + const mockRevalidateTag = revalidateTag as jest.MockedFunction< + typeof revalidateTag + >; + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv }; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('returns 500 when CRON_SECRET is not configured', async () => { + delete process.env.CRON_SECRET; + + const request = new Request('http://localhost:3000/api/revalidate', { + method: 'GET', + headers: { + authorization: 'Bearer some-secret', + }, + }); + + const response = await GET(request); + const json = await response.json(); + + expect(response.status).toBe(500); + expect(json).toEqual({ + ok: false, + error: 'Server misconfigured: CRON_SECRET missing', + }); + expect(mockRevalidatePath).not.toHaveBeenCalled(); + expect(mockRevalidateTag).not.toHaveBeenCalled(); + }); + + it('returns 401 when authorization header does not match', async () => { + process.env.CRON_SECRET = 'correct-secret'; + + const request = new Request('http://localhost:3000/api/revalidate', { + method: 'GET', + headers: { + authorization: 'Bearer wrong-secret', + }, + }); + + const response = await GET(request); + const json = await response.json(); + + expect(response.status).toBe(401); + expect(json).toEqual({ + ok: false, + error: 'Unauthorized', + }); + expect(mockRevalidatePath).not.toHaveBeenCalled(); + expect(mockRevalidateTag).not.toHaveBeenCalled(); + }); + + it('revalidates only GBFS feed pages for authorized cron requests', async () => { + process.env.CRON_SECRET = 'test-secret'; + + const request = new Request('http://localhost:3000/api/revalidate', { + method: 'GET', + headers: { + authorization: 'Bearer test-secret', + }, + }); + + const response = await GET(request); + const json = await response.json(); + + expect(response.status).toBe(200); + expect(json).toEqual({ + ok: true, + message: 'All GBFS feeds revalidated successfully', + }); + expect(mockRevalidateTag).toHaveBeenCalledWith('feed-type-gbfs', 'max'); + expect(mockRevalidatePath).toHaveBeenCalledWith( + '/[locale]/feeds/gbfs/[feedId]', + 'layout', + ); + expect(mockRevalidateTag).toHaveBeenCalledTimes(1); + expect(mockRevalidatePath).toHaveBeenCalledTimes(1); + }); +}); + describe('POST /api/revalidate', () => { const mockRevalidatePath = revalidatePath as jest.MockedFunction< typeof revalidatePath diff --git a/src/app/api/revalidate/route.ts b/src/app/api/revalidate/route.ts index d04f9b7..5ff1ee0 100644 --- a/src/app/api/revalidate/route.ts +++ b/src/app/api/revalidate/route.ts @@ -23,9 +23,9 @@ const defaultRevalidateOptions: RevalidateBody = { }; /** - * GET handler for the Vercel cron job that revalidates all feed pages once a day. + * GET handler for the Vercel cron job that revalidates all GBFS feed pages. * Vercel automatically passes Authorization: Bearer with each invocation. - * Configured in vercel.json under "crons" (schedule: 0 9 * * * = 4am EST / 9am UTC). + * Configured in vercel.json under "crons" for 4am UTC Monday-Saturday and 7am UTC Sunday. */ export async function GET(req: Request): Promise { const authHeader = req.headers.get('authorization'); @@ -46,14 +46,14 @@ export async function GET(req: Request): Promise { } try { - revalidateTag('guest-feeds', 'max'); - revalidatePath('/[locale]/feeds/[feedDataType]/[feedId]', 'layout'); + revalidateTag('feed-type-gbfs', 'max'); + revalidatePath('/[locale]/feeds/gbfs/[feedId]', 'layout'); console.log( - '[cron] revalidate /api/revalidate: all-feeds revalidation triggered', + '[cron] revalidate /api/revalidate: all-gbfs-feeds revalidation triggered', ); return NextResponse.json({ ok: true, - message: 'All feeds revalidated successfully', + message: 'All GBFS feeds revalidated successfully', }); } catch (error) { console.error( diff --git a/vercel.json b/vercel.json index a28d1d0..502c65b 100644 --- a/vercel.json +++ b/vercel.json @@ -2,7 +2,11 @@ "crons": [ { "path": "/api/revalidate", - "schedule": "0 9 * * *" + "schedule": "0 4 * * 1-6" + }, + { + "path": "/api/revalidate", + "schedule": "0 7 * * 0" } ] }