import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { POST } from '#app/api/revalidate/route'; const { revalidateTag } = vi.hoisted(() => ({ revalidateTag: vi.fn() })); vi.mock('next/cache', () => ({ revalidateTag })); const SECRET = 'test-revalidate-secret'; function request({ secret = SECRET, body = JSON.stringify({ tags: ['web-manager'] }) }: { secret?: string | null; body?: string } = {}) { const headers = new Headers({ 'content-type': 'application/json' }); if (secret !== null) { headers.set('x-revalidate-secret', secret); } return new Request('http://web.test/api/revalidate', { method: 'POST', headers, body }); } beforeEach(() => { vi.stubEnv('REVALIDATE_SECRET', SECRET); revalidateTag.mockReset(); }); afterEach(() => { vi.unstubAllEnvs(); }); describe('authentication', () => { it('rejects a request with no secret header', async () => { const response = await POST(request({ secret: null })); expect(response.status).toBe(401); expect(revalidateTag).not.toHaveBeenCalled(); }); it('rejects a wrong secret', async () => { const response = await POST(request({ secret: 'wrong' })); expect(response.status).toBe(401); expect(revalidateTag).not.toHaveBeenCalled(); }); it('rejects everything when the server has no secret configured — even a matching empty header', async () => { vi.stubEnv('REVALIDATE_SECRET', ''); expect((await POST(request({ secret: '' }))).status).toBe(401); expect((await POST(request())).status).toBe(401); expect(revalidateTag).not.toHaveBeenCalled(); }); }); describe('payload validation', () => { it('rejects a body that is not JSON', async () => { const response = await POST(request({ body: 'not json {' })); expect(response.status).toBe(400); expect(await response.json()).toEqual({ message: 'Invalid JSON body' }); }); it.each([ ['a non-object payload', JSON.stringify('tags')], ['a null payload', JSON.stringify(null)], ['a missing tags field', JSON.stringify({})], ['tags that are not an array', JSON.stringify({ tags: 'web-manager' })], ['an empty tags array', JSON.stringify({ tags: [] })], ['a non-string tag', JSON.stringify({ tags: ['ok', 7] })], ['an empty-string tag', JSON.stringify({ tags: [''] })] ])('rejects %s with 400', async (_label, body) => { const response = await POST(request({ body })); expect(response.status).toBe(400); expect(revalidateTag).not.toHaveBeenCalled(); }); it('accepts exactly 50 tags but rejects 51', async () => { const fifty = Array.from({ length: 50 }, (_, i) => `tag-${i}`); expect((await POST(request({ body: JSON.stringify({ tags: fifty }) }))).status).toBe(200); expect( (await POST(request({ body: JSON.stringify({ tags: [...fifty, 'one-more'] }) }))).status ).toBe(400); }); it('accepts a 256-character tag but rejects 257', async () => { expect( (await POST(request({ body: JSON.stringify({ tags: ['a'.repeat(256)] }) }))).status ).toBe(200); expect( (await POST(request({ body: JSON.stringify({ tags: ['a'.repeat(257)] }) }))).status ).toBe(400); }); }); describe('revalidation', () => { it('revalidates every tag with the max profile and echoes them back', async () => { const tags = ['web-manager', 'web-manager:page:en:about']; const response = await POST(request({ body: JSON.stringify({ tags }) })); expect(response.status).toBe(200); expect(await response.json()).toEqual({ revalidated: tags }); expect(revalidateTag).toHaveBeenCalledTimes(2); expect(revalidateTag).toHaveBeenNthCalledWith(1, 'web-manager', 'max'); expect(revalidateTag).toHaveBeenNthCalledWith(2, 'web-manager:page:en:about', 'max'); }); });