Files
zero-fucks-given/apps/web/tests/revalidate-route.test.ts
T

109 lines
3.7 KiB
TypeScript

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');
});
});