/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import {cacheHeaders} from '@fluxer/hono/src/middleware/CacheHeaders'; import {Hono} from 'hono'; import {describe, expect, test} from 'vitest'; describe('CacheHeaders Middleware', () => { describe('static content caching', () => { test('sets immutable cache for CSS files', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/styles.css', () => { return new Response('body { color: red; }', { headers: {'Content-Type': 'text/css'}, }); }); const response = await app.request('/styles.css'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for JavaScript files', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/app.js', () => { return new Response('console.log("hello");', { headers: {'Content-Type': 'application/javascript'}, }); }); const response = await app.request('/app.js'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for images', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/logo.png', (c) => { c.header('Content-Type', 'image/png'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/logo.png'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for fonts', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/font.woff2', (c) => { c.header('Content-Type', 'font/woff2'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/font.woff2'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for application/font-woff2', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/font.woff2', (c) => { c.header('Content-Type', 'application/font-woff2'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/font.woff2'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for video files', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/video.mp4', (c) => { c.header('Content-Type', 'video/mp4'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/video.mp4'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('sets immutable cache for audio files', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/audio.mp3', (c) => { c.header('Content-Type', 'audio/mpeg'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/audio.mp3'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); }); describe('default caching', () => { test('sets no-cache for JSON responses', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/api/data', (c) => c.json({ok: true})); const response = await app.request('/api/data'); expect(response.headers.get('Cache-Control')).toBe('no-cache'); }); test('sets no-cache for HTML responses', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/', (c) => { c.header('Content-Type', 'text/html'); return c.html('Hello'); }); const response = await app.request('/'); expect(response.headers.get('Cache-Control')).toBe('no-cache'); }); test('sets no-cache for text/plain responses', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/readme.txt', (c) => c.text('Hello World')); const response = await app.request('/readme.txt'); expect(response.headers.get('Cache-Control')).toBe('no-cache'); }); test('sets no-cache when Content-Type is not set', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/test', (c) => c.body('data')); const response = await app.request('/test'); expect(response.headers.get('Cache-Control')).toBe('no-cache'); }); }); describe('existing Cache-Control header', () => { test('does not override existing Cache-Control header', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/test', (c) => { c.header('Cache-Control', 'private, max-age=3600'); return c.json({ok: true}); }); const response = await app.request('/test'); expect(response.headers.get('Cache-Control')).toBe('private, max-age=3600'); }); test('respects existing no-store header', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/sensitive', (c) => { c.header('Cache-Control', 'no-store'); return c.json({secret: 'data'}); }); const response = await app.request('/sensitive'); expect(response.headers.get('Cache-Control')).toBe('no-store'); }); }); describe('custom options', () => { test('uses custom staticCacheControl', async () => { const app = new Hono(); app.use('*', cacheHeaders({staticCacheControl: 'public, max-age=86400'})); app.get('/image.png', () => { return new Response(new Uint8Array([1, 2, 3]), { headers: {'Content-Type': 'image/png'}, }); }); const response = await app.request('/image.png'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=86400'); }); test('uses custom defaultCacheControl', async () => { const app = new Hono(); app.use('*', cacheHeaders({defaultCacheControl: 'no-store'})); app.get('/api/data', (c) => c.json({ok: true})); const response = await app.request('/api/data'); expect(response.headers.get('Cache-Control')).toBe('no-store'); }); test('uses both custom options together', async () => { const app = new Hono(); app.use( '*', cacheHeaders({ staticCacheControl: 'public, max-age=7200', defaultCacheControl: 'private, no-cache', }), ); app.get('/style.css', () => { return new Response('body {}', { headers: {'Content-Type': 'text/css'}, }); }); app.get('/api/data', (c) => c.json({ok: true})); const cssResponse = await app.request('/style.css'); expect(cssResponse.headers.get('Cache-Control')).toBe('public, max-age=7200'); const jsonResponse = await app.request('/api/data'); expect(jsonResponse.headers.get('Cache-Control')).toBe('private, no-cache'); }); }); describe('content type variations', () => { test('handles image/jpeg', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/photo.jpg', (c) => { c.header('Content-Type', 'image/jpeg'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/photo.jpg'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('handles image/gif', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/animation.gif', (c) => { c.header('Content-Type', 'image/gif'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/animation.gif'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('handles image/svg+xml', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/icon.svg', () => { return new Response('', { headers: {'Content-Type': 'image/svg+xml'}, }); }); const response = await app.request('/icon.svg'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); test('handles video/webm', async () => { const app = new Hono(); app.use('*', cacheHeaders()); app.get('/video.webm', (c) => { c.header('Content-Type', 'video/webm'); return c.body(new Uint8Array([1, 2, 3])); }); const response = await app.request('/video.webm'); expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); }); }); });