mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
test(backend): remove duplicate and useless tests (#2820)
This commit is contained in:
@@ -13,7 +13,7 @@ import {requestId} from '@fluxer/hono/src/middleware/RequestId';
|
||||
import type {LogFunction, RequestLoggerOptions} from '@fluxer/hono/src/middleware/RequestLogger';
|
||||
import {requestLogger} from '@fluxer/hono/src/middleware/RequestLogger';
|
||||
import {fluxerVersionHeader} from '@fluxer/hono/src/middleware/VersionHeader';
|
||||
import type {Context, Env, Hono, MiddlewareHandler} from 'hono';
|
||||
import type {Env, Hono, MiddlewareHandler} from 'hono';
|
||||
|
||||
interface MiddlewareStackOptions {
|
||||
requestId?: RequestIdOptions;
|
||||
@@ -38,7 +38,7 @@ interface ApplyMiddlewareStackOptions extends MiddlewareStackOptions {
|
||||
skipErrorHandler?: boolean;
|
||||
}
|
||||
|
||||
export function createStandardMiddlewareStack(options: MiddlewareStackOptions = {}): Array<MiddlewareHandler> {
|
||||
function createStandardMiddlewareStack(options: MiddlewareStackOptions = {}): Array<MiddlewareHandler> {
|
||||
const stack: Array<MiddlewareHandler> = [fluxerVersionHeader()];
|
||||
if (options.requestId) {
|
||||
stack.push(requestId(options.requestId));
|
||||
@@ -90,36 +90,3 @@ export function applyMiddlewareStack<E extends Env = Env>(
|
||||
app.onError(errorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefaultLogger(options: {serviceName: string; skip?: Array<string>}): LogFunction {
|
||||
return (data) => {
|
||||
if (options.skip?.includes(data.path)) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
service: options.serviceName,
|
||||
method: data.method,
|
||||
path: data.path,
|
||||
status: data.status,
|
||||
durationMs: data.durationMs,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function createDefaultErrorLogger(options: {serviceName: string}): (error: Error, context: Context) => void {
|
||||
return (error: Error, context: Context) => {
|
||||
console.error(
|
||||
JSON.stringify({
|
||||
service: options.serviceName,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
path: context.req.path,
|
||||
method: context.req.method,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface RequestIdOptions {
|
||||
setResponseHeader?: boolean;
|
||||
}
|
||||
|
||||
export const REQUEST_ID_KEY = 'requestId';
|
||||
const REQUEST_ID_KEY = 'requestId';
|
||||
|
||||
export function requestId(options: RequestIdOptions = {}): MiddlewareHandler {
|
||||
const {headerName = Headers.X_REQUEST_ID, generator = randomUUID, setResponseHeader = true} = options;
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
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 long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
test('sets long-lived 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');
|
||||
});
|
||||
});
|
||||
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('<html><body>Hello</body></html>');
|
||||
});
|
||||
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');
|
||||
});
|
||||
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');
|
||||
});
|
||||
test('handles image/svg+xml', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cacheHeaders());
|
||||
app.get('/icon.svg', () => {
|
||||
return new Response('<svg></svg>', {
|
||||
headers: {'Content-Type': 'image/svg+xml'},
|
||||
});
|
||||
});
|
||||
const response = await app.request('/icon.svg');
|
||||
expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,195 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createErrorHandler} from '@fluxer/hono/src/middleware/ErrorHandler';
|
||||
import {Hono} from 'hono';
|
||||
import {HTTPException} from 'hono/http-exception';
|
||||
import {describe, expect, test, vi} from 'vitest';
|
||||
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
describe('ErrorHandler Middleware', () => {
|
||||
describe('generic errors', () => {
|
||||
test('handles generic Error with 500 status', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new Error('Something went wrong');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.code).toBe('INTERNAL_SERVER_ERROR');
|
||||
expect(body.message).toBe('Something went wrong. Please try again later.');
|
||||
});
|
||||
test('includes stack trace when includeStack is true', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler({includeStack: true}));
|
||||
app.get('/test', () => {
|
||||
throw new Error('Something went wrong');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.message).toBe('Something went wrong');
|
||||
expect(body.stack).toBeTruthy();
|
||||
});
|
||||
test('excludes stack trace by default', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new Error('Something went wrong');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.stack).toBeUndefined();
|
||||
});
|
||||
});
|
||||
describe('HTTPException handling', () => {
|
||||
test('handles HTTPException with correct status', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new HTTPException(404, {message: 'Resource not found'});
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(404);
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.code).toBe('NOT_FOUND');
|
||||
expect(body.message).toBe('Resource not found');
|
||||
});
|
||||
test('handles HTTPException with 403 Forbidden', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new HTTPException(403, {message: 'Access denied'});
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(403);
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.code).toBe('FORBIDDEN');
|
||||
expect(body.message).toBe('Access denied');
|
||||
});
|
||||
test('handles HTTPException without message', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new HTTPException(400);
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(400);
|
||||
const body = (await response.json()) as ErrorResponse;
|
||||
expect(body.message).toBe('An error occurred');
|
||||
});
|
||||
});
|
||||
describe('logger option', () => {
|
||||
test('calls logger with error and context', async () => {
|
||||
const logger = vi.fn();
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler({logger}));
|
||||
app.get('/test', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
await app.request('/test');
|
||||
expect(logger).toHaveBeenCalledTimes(1);
|
||||
expect(logger).toHaveBeenCalledWith(expect.any(Error), expect.objectContaining({req: expect.anything()}));
|
||||
});
|
||||
test('does not call logger when not provided', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
describe('captureException option', () => {
|
||||
test('calls captureException with error and context info', async () => {
|
||||
const captureException = vi.fn();
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler({captureException}));
|
||||
app.get('/test', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
await app.request('/test');
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
expect(captureException).toHaveBeenCalledWith(
|
||||
expect.any(Error),
|
||||
expect.objectContaining({
|
||||
path: '/test',
|
||||
method: 'GET',
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('calls both logger and captureException when both provided', async () => {
|
||||
const logger = vi.fn();
|
||||
const captureException = vi.fn();
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler({logger, captureException}));
|
||||
app.get('/test', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
await app.request('/test');
|
||||
expect(logger).toHaveBeenCalledTimes(1);
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
describe('async errors', () => {
|
||||
test('handles async errors', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', async () => {
|
||||
await Promise.resolve();
|
||||
throw new Error('Async error');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
test('handles rejected promises', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/test', async () => {
|
||||
return Promise.reject(new Error('Rejected promise'));
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
describe('multiple routes', () => {
|
||||
test('handles errors from different routes', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/route1', () => {
|
||||
throw new HTTPException(404, {message: 'Route 1 not found'});
|
||||
});
|
||||
app.get('/route2', () => {
|
||||
throw new Error('Route 2 error');
|
||||
});
|
||||
const response1 = await app.request('/route1');
|
||||
expect(response1.status).toBe(404);
|
||||
const response2 = await app.request('/route2');
|
||||
expect(response2.status).toBe(500);
|
||||
});
|
||||
});
|
||||
describe('error recovery', () => {
|
||||
test('does not affect subsequent successful requests', async () => {
|
||||
const app = new Hono();
|
||||
app.onError(createErrorHandler());
|
||||
app.get('/error', () => {
|
||||
throw new Error('Error route');
|
||||
});
|
||||
app.get('/success', (c) => c.json({ok: true}));
|
||||
const errorResponse = await app.request('/error');
|
||||
expect(errorResponse.status).toBe(500);
|
||||
const successResponse = await app.request('/success');
|
||||
expect(successResponse.status).toBe(200);
|
||||
const body = (await successResponse.json()) as {
|
||||
ok: boolean;
|
||||
};
|
||||
expect(body.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,346 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Headers} from '@fluxer/constants/src/Headers';
|
||||
import {
|
||||
applyMiddlewareStack,
|
||||
createDefaultErrorLogger,
|
||||
createDefaultLogger,
|
||||
createStandardMiddlewareStack,
|
||||
} from '@fluxer/hono/src/middleware/MiddlewareStack';
|
||||
import type {RateLimitResult, RateLimitService} from '@fluxer/hono/src/middleware/RateLimit';
|
||||
import {REQUEST_ID_KEY} from '@fluxer/hono/src/middleware/RequestId';
|
||||
import type {Context} from 'hono';
|
||||
import {Hono} from 'hono';
|
||||
import {describe, expect, test, vi} from 'vitest';
|
||||
|
||||
function createMockRateLimitService(result: Partial<RateLimitResult> = {}): RateLimitService {
|
||||
const defaultResult: RateLimitResult = {
|
||||
allowed: true,
|
||||
limit: 100,
|
||||
remaining: 99,
|
||||
resetTime: new Date(Date.now() + 60000),
|
||||
...result,
|
||||
};
|
||||
return {
|
||||
checkLimit: vi.fn().mockResolvedValue(defaultResult),
|
||||
};
|
||||
}
|
||||
|
||||
describe('createStandardMiddlewareStack', () => {
|
||||
test('includes cacheHeaders by default with no options', () => {
|
||||
const stack = createStandardMiddlewareStack();
|
||||
expect(stack).toHaveLength(2);
|
||||
});
|
||||
test('includes requestId middleware when configured', () => {
|
||||
const stack = createStandardMiddlewareStack({requestId: {}});
|
||||
expect(stack).toHaveLength(3);
|
||||
});
|
||||
test('includes cors middleware when enabled', () => {
|
||||
const stack = createStandardMiddlewareStack({cors: {enabled: true}});
|
||||
expect(stack).toHaveLength(3);
|
||||
});
|
||||
test('excludes cors middleware when disabled', () => {
|
||||
const stack = createStandardMiddlewareStack({cors: {enabled: false}});
|
||||
expect(stack).toHaveLength(2);
|
||||
});
|
||||
test('excludes cacheHeaders when set to false', () => {
|
||||
const stack = createStandardMiddlewareStack({cacheHeaders: false});
|
||||
expect(stack).toHaveLength(1);
|
||||
});
|
||||
test('includes logger middleware when log function provided', () => {
|
||||
const stack = createStandardMiddlewareStack({logger: {log: vi.fn()}});
|
||||
expect(stack).toHaveLength(3);
|
||||
});
|
||||
test('excludes logger middleware when no log function', () => {
|
||||
const stack = createStandardMiddlewareStack({logger: {}});
|
||||
expect(stack).toHaveLength(2);
|
||||
});
|
||||
test('includes rateLimit middleware when enabled with service', () => {
|
||||
const stack = createStandardMiddlewareStack({
|
||||
rateLimit: {enabled: true, service: createMockRateLimitService()},
|
||||
});
|
||||
expect(stack).toHaveLength(3);
|
||||
});
|
||||
test('excludes rateLimit middleware when no service provided', () => {
|
||||
const stack = createStandardMiddlewareStack({rateLimit: {enabled: true}});
|
||||
expect(stack).toHaveLength(2);
|
||||
});
|
||||
test('includes custom middleware', () => {
|
||||
const customMiddleware = vi.fn();
|
||||
const stack = createStandardMiddlewareStack({
|
||||
customMiddleware: [customMiddleware, customMiddleware],
|
||||
});
|
||||
expect(stack).toHaveLength(4);
|
||||
});
|
||||
test('combines all middleware in correct order', () => {
|
||||
const stack = createStandardMiddlewareStack({
|
||||
requestId: {},
|
||||
cors: {enabled: true},
|
||||
logger: {log: vi.fn()},
|
||||
rateLimit: {enabled: true, service: createMockRateLimitService()},
|
||||
customMiddleware: [vi.fn()],
|
||||
});
|
||||
expect(stack).toHaveLength(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyMiddlewareStack', () => {
|
||||
test('applies version header middleware', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get(Headers.X_FLUXER_VERSION)).toBe('dev');
|
||||
});
|
||||
test('applies requestId middleware', async () => {
|
||||
const app = new Hono<{
|
||||
Variables: {
|
||||
[REQUEST_ID_KEY]: string;
|
||||
};
|
||||
}>();
|
||||
applyMiddlewareStack(app, {requestId: {}});
|
||||
app.get('/test', (c) => c.json({id: c.get(REQUEST_ID_KEY)}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeTruthy();
|
||||
});
|
||||
test('applies cors middleware', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {cors: {enabled: true, origins: '*'}});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
});
|
||||
test('applies error handler by default', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {});
|
||||
app.get('/test', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
const body = (await response.json()) as {
|
||||
code: string;
|
||||
};
|
||||
expect(body.code).toBe('INTERNAL_SERVER_ERROR');
|
||||
});
|
||||
test('skips requestId when skipRequestId is true', async () => {
|
||||
const app = new Hono<{
|
||||
Variables: {
|
||||
[REQUEST_ID_KEY]: string;
|
||||
};
|
||||
}>();
|
||||
applyMiddlewareStack(app, {
|
||||
requestId: {},
|
||||
skipRequestId: true,
|
||||
});
|
||||
app.get('/test', (c) => c.json({id: c.get(REQUEST_ID_KEY)}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeNull();
|
||||
});
|
||||
test('skips cors when skipCors is true', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
cors: {enabled: true, origins: '*'},
|
||||
skipCors: true,
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
|
||||
});
|
||||
test('skips logger when skipLogger is true', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
logger: {log},
|
||||
skipLogger: true,
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
});
|
||||
test('skips rateLimit when skipRateLimit is true', async () => {
|
||||
const service = createMockRateLimitService();
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
rateLimit: {enabled: true, service},
|
||||
skipRateLimit: true,
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(service.checkLimit).not.toHaveBeenCalled();
|
||||
});
|
||||
test('applies cache headers by default', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
||||
});
|
||||
test('skips cache headers when skipCacheHeaders is true', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {skipCacheHeaders: true});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Cache-Control')).toBeNull();
|
||||
});
|
||||
test('skips errorHandler when skipErrorHandler is true', async () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const error = new Error('Test error');
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
skipErrorHandler: true,
|
||||
});
|
||||
app.get('/test', () => {
|
||||
throw error;
|
||||
});
|
||||
try {
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(500);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(error);
|
||||
} finally {
|
||||
consoleErrorSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
test('applies custom middleware', async () => {
|
||||
const customMiddleware = vi.fn().mockImplementation(async (_c, next) => {
|
||||
await next();
|
||||
});
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
customMiddleware: [customMiddleware],
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(customMiddleware).toHaveBeenCalled();
|
||||
});
|
||||
test('applies logger with skip paths', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
logger: {log, skip: ['/_health']},
|
||||
});
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
app.get('/api', (c) => c.json({ok: true}));
|
||||
await app.request('/_health');
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
await app.request('/api');
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDefaultLogger', () => {
|
||||
test('logs request data as JSON', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const logger = createDefaultLogger({serviceName: 'test-service'});
|
||||
logger({method: 'GET', path: '/api/test', status: 200, durationMs: 50});
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
const loggedData = JSON.parse(consoleSpy.mock.calls[0][0] as string);
|
||||
expect(loggedData.service).toBe('test-service');
|
||||
expect(loggedData.method).toBe('GET');
|
||||
expect(loggedData.path).toBe('/api/test');
|
||||
expect(loggedData.status).toBe(200);
|
||||
expect(loggedData.durationMs).toBe(50);
|
||||
expect(loggedData.timestamp).toBeTruthy();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
test('skips paths in skip array', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const logger = createDefaultLogger({serviceName: 'test-service', skip: ['/_health']});
|
||||
logger({method: 'GET', path: '/_health', status: 200, durationMs: 1});
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
test('logs paths not in skip array', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const logger = createDefaultLogger({serviceName: 'test-service', skip: ['/_health']});
|
||||
logger({method: 'GET', path: '/api/users', status: 200, durationMs: 1});
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDefaultErrorLogger', () => {
|
||||
test('logs error data as JSON', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const errorLogger = createDefaultErrorLogger({serviceName: 'test-service'});
|
||||
const mockContext = {
|
||||
req: {
|
||||
path: '/api/test',
|
||||
method: 'POST',
|
||||
},
|
||||
} as Context;
|
||||
const error = new Error('Test error');
|
||||
errorLogger(error, mockContext);
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
const loggedData = JSON.parse(consoleSpy.mock.calls[0][0] as string);
|
||||
expect(loggedData.service).toBe('test-service');
|
||||
expect(loggedData.error).toBe('Test error');
|
||||
expect(loggedData.stack).toBeTruthy();
|
||||
expect(loggedData.path).toBe('/api/test');
|
||||
expect(loggedData.method).toBe('POST');
|
||||
expect(loggedData.timestamp).toBeTruthy();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration tests', () => {
|
||||
test('full middleware stack works together', async () => {
|
||||
const log = vi.fn();
|
||||
const rateLimitService = createMockRateLimitService();
|
||||
const keyGenerator = vi.fn().mockResolvedValue('test-client');
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
requestId: {},
|
||||
cors: {enabled: true, origins: '*'},
|
||||
logger: {log},
|
||||
rateLimit: {enabled: true, service: rateLimitService, keyGenerator},
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeTruthy();
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
expect(response.headers.get('Cache-Control')).toBe('no-cache');
|
||||
expect(response.headers.get('X-RateLimit-Limit')).toBe('100');
|
||||
expect(log).toHaveBeenCalled();
|
||||
expect(keyGenerator).toHaveBeenCalled();
|
||||
expect(rateLimitService.checkLimit).toHaveBeenCalled();
|
||||
});
|
||||
test('error handler catches errors from routes', async () => {
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {requestId: {}});
|
||||
app.get('/error', () => {
|
||||
throw new Error('Route error');
|
||||
});
|
||||
const response = await app.request('/error');
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeTruthy();
|
||||
});
|
||||
test('rate limiter blocks requests when limit exceeded', async () => {
|
||||
const rateLimitService = createMockRateLimitService({allowed: false, remaining: 0});
|
||||
const keyGenerator = vi.fn().mockResolvedValue('blocked-client');
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
rateLimit: {enabled: true, service: rateLimitService, keyGenerator},
|
||||
});
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(429);
|
||||
expect(keyGenerator).toHaveBeenCalled();
|
||||
});
|
||||
test('health endpoints are skipped by default for rate limiting', async () => {
|
||||
const rateLimitService = createMockRateLimitService();
|
||||
const app = new Hono();
|
||||
applyMiddlewareStack(app, {
|
||||
rateLimit: {enabled: true, service: rateLimitService},
|
||||
});
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
await app.request('/_health');
|
||||
expect(rateLimitService.checkLimit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,136 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Headers} from '@fluxer/constants/src/Headers';
|
||||
import {REQUEST_ID_KEY, requestId} from '@fluxer/hono/src/middleware/RequestId';
|
||||
import {Hono} from 'hono';
|
||||
import {describe, expect, test, vi} from 'vitest';
|
||||
|
||||
type AppEnv = {
|
||||
Variables: {
|
||||
requestId: string;
|
||||
};
|
||||
};
|
||||
|
||||
describe('RequestId Middleware', () => {
|
||||
test('generates a request ID when none provided', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId());
|
||||
app.get('/test', (c) => {
|
||||
const id = c.get(REQUEST_ID_KEY);
|
||||
return c.json({requestId: id});
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
const responseId = response.headers.get(Headers.X_REQUEST_ID);
|
||||
expect(responseId).toBeTruthy();
|
||||
expect(responseId).toMatch(/^[0-9a-f-]{36}$/);
|
||||
const body = (await response.json()) as {
|
||||
requestId: string;
|
||||
};
|
||||
expect(body.requestId).toBe(responseId);
|
||||
});
|
||||
test('uses existing request ID from header', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId());
|
||||
app.get('/test', (c) => {
|
||||
const id = c.get(REQUEST_ID_KEY);
|
||||
return c.json({requestId: id});
|
||||
});
|
||||
const existingId = 'existing-request-id-12345';
|
||||
const response = await app.request('/test', {
|
||||
headers: {
|
||||
[Headers.X_REQUEST_ID]: existingId,
|
||||
},
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBe(existingId);
|
||||
const body = (await response.json()) as {
|
||||
requestId: string;
|
||||
};
|
||||
expect(body.requestId).toBe(existingId);
|
||||
});
|
||||
test('uses custom header name', async () => {
|
||||
const customHeader = 'X-Custom-Request-ID';
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId({headerName: customHeader}));
|
||||
app.get('/test', (c) => {
|
||||
const id = c.get(REQUEST_ID_KEY);
|
||||
return c.json({requestId: id});
|
||||
});
|
||||
const existingId = 'custom-header-id';
|
||||
const response = await app.request('/test', {
|
||||
headers: {
|
||||
[customHeader]: existingId,
|
||||
},
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get(customHeader)).toBe(existingId);
|
||||
});
|
||||
test('uses custom generator function', async () => {
|
||||
const customGenerator = vi.fn().mockReturnValue('custom-generated-id');
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId({generator: customGenerator}));
|
||||
app.get('/test', (c) => {
|
||||
const id = c.get(REQUEST_ID_KEY);
|
||||
return c.json({requestId: id});
|
||||
});
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
expect(customGenerator).toHaveBeenCalled();
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBe('custom-generated-id');
|
||||
});
|
||||
test('does not call generator when request ID exists', async () => {
|
||||
const customGenerator = vi.fn().mockReturnValue('custom-generated-id');
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId({generator: customGenerator}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test', {
|
||||
headers: {
|
||||
[Headers.X_REQUEST_ID]: 'existing-id',
|
||||
},
|
||||
});
|
||||
expect(customGenerator).not.toHaveBeenCalled();
|
||||
});
|
||||
test('does not set response header when setResponseHeader is false', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId({setResponseHeader: false}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeNull();
|
||||
});
|
||||
test('sets response header by default', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
app.use('*', requestId());
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get(Headers.X_REQUEST_ID)).toBeTruthy();
|
||||
});
|
||||
test('request ID is available in context after middleware runs', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
let capturedId: string | undefined;
|
||||
app.use('*', requestId());
|
||||
app.get('/test', (c) => {
|
||||
capturedId = c.get(REQUEST_ID_KEY);
|
||||
return c.json({ok: true});
|
||||
});
|
||||
await app.request('/test');
|
||||
expect(capturedId).toBeTruthy();
|
||||
expect(capturedId).toMatch(/^[0-9a-f-]{36}$/);
|
||||
});
|
||||
test('different requests get different IDs', async () => {
|
||||
const app = new Hono<AppEnv>();
|
||||
const capturedIds: Array<string> = [];
|
||||
app.use('*', requestId());
|
||||
app.get('/test', (c) => {
|
||||
capturedIds.push(c.get(REQUEST_ID_KEY));
|
||||
return c.json({ok: true});
|
||||
});
|
||||
await app.request('/test');
|
||||
await app.request('/test');
|
||||
await app.request('/test');
|
||||
expect(capturedIds).toHaveLength(3);
|
||||
expect(new Set(capturedIds).size).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {requestLogger} from '@fluxer/hono/src/middleware/RequestLogger';
|
||||
import {Hono} from 'hono';
|
||||
import {describe, expect, test, vi} from 'vitest';
|
||||
|
||||
describe('RequestLogger Middleware', () => {
|
||||
describe('logging', () => {
|
||||
test('calls log function with request data', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledTimes(1);
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
path: '/test',
|
||||
status: 200,
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('includes method in log data', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.post('/users', (c) => c.json({created: true}));
|
||||
await app.request('/users', {method: 'POST'});
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('includes path in log data', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/api/users/123', (c) => c.json({ok: true}));
|
||||
await app.request('/api/users/123');
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path: '/api/users/123',
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('includes status code in log data', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/error', (c) => c.json({error: 'Not found'}, 404));
|
||||
await app.request('/error');
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
status: 404,
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('includes durationMs in log data', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
durationMs: expect.any(Number),
|
||||
}),
|
||||
);
|
||||
expect(log.mock.calls[0][0].durationMs).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
describe('skip option', () => {
|
||||
test('skips logging for paths in skip array', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log, skip: ['/_health']}));
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
await app.request('/_health');
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
});
|
||||
test('logs paths not in skip array', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log, skip: ['/_health']}));
|
||||
app.get('/api/users', (c) => c.json({ok: true}));
|
||||
await app.request('/api/users');
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
test('skips multiple paths', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log, skip: ['/_health', '/metrics', '/_health']}));
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
app.get('/metrics', (c) => c.json({ok: true}));
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
await app.request('/_health');
|
||||
await app.request('/metrics');
|
||||
await app.request('/_health');
|
||||
expect(log).not.toHaveBeenCalled();
|
||||
});
|
||||
test('uses empty skip array by default', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/_health', (c) => c.json({ok: true}));
|
||||
await app.request('/_health');
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('different HTTP methods', () => {
|
||||
test('logs GET requests', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({method: 'GET'}));
|
||||
});
|
||||
test('logs POST requests', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.post('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test', {method: 'POST'});
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({method: 'POST'}));
|
||||
});
|
||||
test('logs PUT requests', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.put('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test', {method: 'PUT'});
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({method: 'PUT'}));
|
||||
});
|
||||
test('logs DELETE requests', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.delete('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test', {method: 'DELETE'});
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({method: 'DELETE'}));
|
||||
});
|
||||
test('logs PATCH requests', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.patch('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test', {method: 'PATCH'});
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({method: 'PATCH'}));
|
||||
});
|
||||
});
|
||||
describe('different status codes', () => {
|
||||
test('logs 200 OK responses', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({status: 200}));
|
||||
});
|
||||
test('logs 201 Created responses', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.post('/test', (c) => c.json({created: true}, 201));
|
||||
await app.request('/test', {method: 'POST'});
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({status: 201}));
|
||||
});
|
||||
test('logs 400 Bad Request responses', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({error: 'Bad Request'}, 400));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({status: 400}));
|
||||
});
|
||||
test('logs 500 Internal Server Error responses', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/test', (c) => c.json({error: 'Server Error'}, 500));
|
||||
await app.request('/test');
|
||||
expect(log).toHaveBeenCalledWith(expect.objectContaining({status: 500}));
|
||||
});
|
||||
});
|
||||
describe('multiple requests', () => {
|
||||
test('logs each request separately', async () => {
|
||||
const log = vi.fn();
|
||||
const app = new Hono();
|
||||
app.use('*', requestLogger({log}));
|
||||
app.get('/first', (c) => c.json({ok: true}));
|
||||
app.get('/second', (c) => c.json({ok: true}));
|
||||
await app.request('/first');
|
||||
await app.request('/second');
|
||||
expect(log).toHaveBeenCalledTimes(2);
|
||||
expect(log).toHaveBeenNthCalledWith(1, expect.objectContaining({path: '/first'}));
|
||||
expect(log).toHaveBeenNthCalledWith(2, expect.objectContaining({path: '/second'}));
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user