From 92d0c8a9eaeb83f21aff4e17e5909e18ed5092a9 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:59:34 +0700 Subject: [PATCH 1/6] refactor(src): restructure for better organization --- src/{logger => core}/buildLogMessage.ts | 25 ++++------- src/{logger => core}/createLogger.ts | 28 ++++-------- src/{logger => core}/filter.ts | 11 +---- src/core/handleHttpError.ts | 14 ++++++ src/core/index.ts | 2 + src/index.ts | 33 ++++---------- src/logger/handleHttpError.ts | 22 ---------- src/logger/index.ts | 2 - src/plugins/index.ts | 1 + .../start.ts => plugins/startServer.ts} | 19 +------- src/transports/console.ts | 22 ++++++++++ .../logToFile.ts => transports/file.ts} | 19 +------- src/transports/index.ts | 24 +--------- src/{types.ts => types/index.ts} | 44 +++++++------------ src/utils/colorMapping.ts | 18 ++------ src/utils/duration.ts | 14 ++---- src/utils/index.ts | 6 +++ src/utils/log.ts | 15 ++----- src/utils/method.ts | 16 +++---- src/utils/path.ts | 14 ++---- src/utils/status.ts | 14 ++---- 21 files changed, 117 insertions(+), 246 deletions(-) rename src/{logger => core}/buildLogMessage.ts (68%) rename src/{logger => core}/createLogger.ts (61%) rename src/{logger => core}/filter.ts (61%) create mode 100644 src/core/handleHttpError.ts create mode 100644 src/core/index.ts delete mode 100644 src/logger/handleHttpError.ts delete mode 100644 src/logger/index.ts create mode 100644 src/plugins/index.ts rename src/{utils/start.ts => plugins/startServer.ts} (72%) create mode 100644 src/transports/console.ts rename src/{logger/logToFile.ts => transports/file.ts} (58%) rename src/{types.ts => types/index.ts} (69%) create mode 100644 src/utils/index.ts diff --git a/src/logger/buildLogMessage.ts b/src/core/buildLogMessage.ts similarity index 68% rename from src/logger/buildLogMessage.ts rename to src/core/buildLogMessage.ts index 40f86c7..6f3df41 100644 --- a/src/logger/buildLogMessage.ts +++ b/src/core/buildLogMessage.ts @@ -7,27 +7,18 @@ import { Options, RequestInfo, StoreData -} from '~/types' -import durationString from '~/utils/duration' -import logString from '~/utils/log' -import methodString from '~/utils/method' -import pathString from '~/utils/path' -import statusString from '~/utils/status' +} from '../types' +import { + durationString, + logString, + methodString, + pathString, + statusString +} from '../utils' const defaultLogFormat = '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}' -/** - * Builds a log message. - * - * @param {LogLevel} level The log level. - * @param {RequestInfo} request The request information. - * @param {LogData} data The log data. - * @param {StoreData} store The store data. - * @param {Options} options The logger options. - * @param {boolean} useColors Whether to apply colors to the log message. - * @returns {string} The formatted log message. - */ export function buildLogMessage( level: LogLevel, request: RequestInfo, diff --git a/src/logger/createLogger.ts b/src/core/createLogger.ts similarity index 61% rename from src/logger/createLogger.ts rename to src/core/createLogger.ts index 89415ef..4f6cc8b 100644 --- a/src/logger/createLogger.ts +++ b/src/core/createLogger.ts @@ -1,7 +1,5 @@ -import { buildLogMessage } from '~/logger/buildLogMessage' -import { filterLog } from '~/logger/filter' -import { logToFile } from '~/logger/logToFile' -import { logToTransports } from '~/transports' +import { logToTransports } from '../transports' +import { logToFile } from '../transports' import { LogData, Logger, @@ -9,17 +7,11 @@ import { Options, RequestInfo, StoreData -} from '~/types' +} from '../types' +import { buildLogMessage } from './buildLogMessage' +import { filterLog } from './filter' +import { handleHttpError } from './handleHttpError' -/** - * Logs a message to the console and optionally to a file. - * - * @param {LogLevel} level The log level. - * @param {RequestInfo} request The request information. - * @param {LogData} data The log data. - * @param {StoreData} store The store data. - * @param {Options} options The logger options. - */ async function log( level: LogLevel, request: RequestInfo, @@ -54,16 +46,12 @@ async function log( await Promise.all(promises) } -/** - * Creates a logger instance. - * - * @param {Options} options The logger options. - * @returns {Logger} The logger instance. - */ export function createLogger(options?: Options): Logger { return { log: (level, request, data, store) => log(level, request, data, store, options), + handleHttpError: (request, error, store) => + handleHttpError(request, error, store, options), customLogFormat: options?.config?.customLogFormat } } diff --git a/src/logger/filter.ts b/src/core/filter.ts similarity index 61% rename from src/logger/filter.ts rename to src/core/filter.ts index f69b9a9..572acf2 100644 --- a/src/logger/filter.ts +++ b/src/core/filter.ts @@ -1,19 +1,10 @@ -import { LogLevel, Options } from '~/types' +import { LogLevel, Options } from '../types' const checkFilter = (filterValue: any, value: any) => Array.isArray(filterValue) ? filterValue.includes(value) : filterValue === value -/** - * Filters log messages. - * - * @param {LogLevel} logLevel The log level. - * @param {number} status The status code. - * @param {string} method The method. - * @param {Options} options The options. - * @returns {boolean} `true` if the log message should be logged, otherwise `false`. - */ export function filterLog( logLevel: LogLevel, status: number, diff --git a/src/core/handleHttpError.ts b/src/core/handleHttpError.ts new file mode 100644 index 0000000..c0f3049 --- /dev/null +++ b/src/core/handleHttpError.ts @@ -0,0 +1,14 @@ +import { HttpError, Options, RequestInfo, StoreData } from '../types' +import { buildLogMessage } from './buildLogMessage' + +export function handleHttpError( + request: RequestInfo, + error: HttpError, + store: StoreData, + options?: Options +): void { + const statusCode = error.status || 500 + console.error( + buildLogMessage('ERROR', request, { status: statusCode }, store, options) + ) +} diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..424d51f --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,2 @@ +export { createLogger } from './createLogger' +export { handleHttpError } from './handleHttpError' diff --git a/src/index.ts b/src/index.ts index 5a9ac3d..4d6a164 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,24 +1,9 @@ -import { Server } from 'bun' -import Elysia from 'elysia' +import { Elysia } from 'elysia' -import { createLogger, handleHttpError } from '~/logger' -import { HttpError, Options } from '~/types' -import startServer from '~/utils/start' +import { createLogger } from './core' +import { startServer } from './plugins' +import { HttpError, Options, Server } from './types' -/** - * Creates a logger plugin for ElysiaJS. - * - * @export - * @module logger - * @category Logger - * @subcategory Functions - * - * @name Logixlysia - * @description Logixlysia is a logger plugin for ElysiaJS. - * @param {Options} [options] Configuration options for the logger. - * - * @returns {Elysia} The logger plugin for ElysiaJS. - */ export default function logixlysia(options?: Options): Elysia { const log = createLogger(options) @@ -33,14 +18,14 @@ export default function logixlysia(options?: Options): Elysia { log.log('INFO', request, { status: 200 }, store as { beforeTime: bigint }) }) .onError({ as: 'global' }, ({ request, error, store }) => { - handleHttpError( + log.handleHttpError( request, error as HttpError, - store as { beforeTime: bigint }, - options + store as { beforeTime: bigint } ) }) } -export { createLogger } from '~/logger' -export { handleHttpError } from '~/logger' +export { createLogger } from './core' +export { handleHttpError } from './core' +export { logToTransports } from './transports' diff --git a/src/logger/handleHttpError.ts b/src/logger/handleHttpError.ts deleted file mode 100644 index 29351ca..0000000 --- a/src/logger/handleHttpError.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { buildLogMessage } from '~/logger/buildLogMessage' -import { HttpError, Options, RequestInfo, StoreData } from '~/types' - -/** - * Handles an HTTP error and logs it. - * - * @param {RequestInfo} request The request information. - * @param {HttpError} error The HTTP error. - * @param {StoreData} store The store data. - * @param {Options} options The logger options. - */ -export function handleHttpError( - request: RequestInfo, - error: HttpError, - store: StoreData, - options?: Options -): void { - const statusCode = error.status || 500 - console.error( - buildLogMessage('ERROR', request, { status: statusCode }, store, options) - ) -} diff --git a/src/logger/index.ts b/src/logger/index.ts deleted file mode 100644 index ee3137d..0000000 --- a/src/logger/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { createLogger } from '~/logger/createLogger' -export { handleHttpError } from '~/logger/handleHttpError' diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..46006ab --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1 @@ +export { default as startServer } from './startServer' diff --git a/src/utils/start.ts b/src/plugins/startServer.ts similarity index 72% rename from src/utils/start.ts rename to src/plugins/startServer.ts index b1d14d8..582dc42 100644 --- a/src/utils/start.ts +++ b/src/plugins/startServer.ts @@ -1,25 +1,12 @@ -import { Options, Server } from '~/types' +import { Options, Server } from '../types' -/** - * Creates a box text. - * - * @param {string} text The text. - * @param {number} width The box width. - * @returns {string} The box text. - */ const createBoxText = (text: string, width: number): string => { const paddingLength = Math.max(0, (width - text.length) / 2) const padding = ' '.repeat(paddingLength) return `${padding}${text}${padding}`.padEnd(width) } -/** - * Starts the server string. - * - * @param {Server} config The server configuration. - * @returns {void} The server string. - */ -function startServer(config: Server, options?: Options): void { +export default function startServer(config: Server, options?: Options): void { const { hostname, port, protocol } = config const showBanner = options?.config?.showBanner ?? true @@ -44,5 +31,3 @@ function startServer(config: Server, options?: Options): void { console.log(`🦊 Elysia is running at ${protocol}://${hostname}:${port}`) } } - -export default startServer diff --git a/src/transports/console.ts b/src/transports/console.ts new file mode 100644 index 0000000..52ab772 --- /dev/null +++ b/src/transports/console.ts @@ -0,0 +1,22 @@ +import { buildLogMessage } from '../core/buildLogMessage' +import { LogData, LogLevel, Options, RequestInfo, StoreData } from '../types' + +export async function logToTransports( + level: LogLevel, + request: RequestInfo, + data: LogData, + store: StoreData, + options?: Options +): Promise { + if (!options?.config?.transports || options.config.transports.length === 0) { + return + } + + const message = buildLogMessage(level, request, data, store, options, false) + + const promises = options.config.transports.map(transport => + transport.log(level, message, { request, data, store }) + ) + + await Promise.all(promises) +} diff --git a/src/logger/logToFile.ts b/src/transports/file.ts similarity index 58% rename from src/logger/logToFile.ts rename to src/transports/file.ts index c71f47e..8215758 100644 --- a/src/logger/logToFile.ts +++ b/src/transports/file.ts @@ -1,16 +1,11 @@ import { promises as fs } from 'fs' import { dirname } from 'path' -import { buildLogMessage } from '~/logger/buildLogMessage' -import { LogData, LogLevel, Options, RequestInfo, StoreData } from '~/types' +import { buildLogMessage } from '../core/buildLogMessage' +import { LogData, LogLevel, Options, RequestInfo, StoreData } from '../types' const dirCache = new Set() -/** - * Ensures that the directory exists. If not, it creates the directory. - * - * @param {string} filePath The path to the log file. - */ async function ensureDirectoryExists(filePath: string): Promise { const dir = dirname(filePath) if (!dirCache.has(dir)) { @@ -19,16 +14,6 @@ async function ensureDirectoryExists(filePath: string): Promise { } } -/** - * Logs a message to a file. - * - * @param {string} filePath The path to the log file. - * @param {LogLevel} level The log level. - * @param {RequestInfo} request The request information. - * @param {LogData} data The log data. - * @param {StoreData} store The store data. - * @param {Options} options The logger options. - */ export async function logToFile( filePath: string, level: LogLevel, diff --git a/src/transports/index.ts b/src/transports/index.ts index 7dcc531..f5a3ba2 100644 --- a/src/transports/index.ts +++ b/src/transports/index.ts @@ -1,22 +1,2 @@ -import { buildLogMessage } from '~/logger/buildLogMessage' -import { LogData, LogLevel, Options, RequestInfo, StoreData } from '~/types' - -export async function logToTransports( - level: LogLevel, - request: RequestInfo, - data: LogData, - store: StoreData, - options?: Options -): Promise { - if (!options?.config?.transports || options.config.transports.length === 0) { - return - } - - const message = buildLogMessage(level, request, data, store, options, false) - - const promises = options.config.transports.map(transport => - transport.log(level, message, { request, data, store }) - ) - - await Promise.all(promises) -} +export { logToTransports } from './console' +export { logToFile } from './file' diff --git a/src/types.ts b/src/types/index.ts similarity index 69% rename from src/types.ts rename to src/types/index.ts index 8182517..89e8277 100644 --- a/src/types.ts +++ b/src/types/index.ts @@ -1,37 +1,42 @@ -interface RequestInfo { +export interface RequestInfo { headers: { get: (key: string) => string | null } method: string url: string } -interface Server { +export interface Server { hostname?: string port?: number protocol?: string } -interface ColorMap { +export interface ColorMap { [key: string]: (str: string) => string } -type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string +export type LogLevel = 'INFO' | 'WARNING' | 'ERROR' | string -interface LogData { +export interface LogData { status?: number message?: string } -interface Logger { +export interface Logger { log( level: LogLevel, request: RequestInfo, data: LogData, store: StoreData ): void + handleHttpError( + request: RequestInfo, + error: HttpError, + store: StoreData + ): void customLogFormat?: string } -interface LogComponents { +export interface LogComponents { now: string epoch: string level: string @@ -43,11 +48,11 @@ interface LogComponents { ip: string } -interface StoreData { +export interface StoreData { beforeTime: bigint } -class HttpError extends Error { +export class HttpError extends Error { status: number constructor(status: number, message: string) { @@ -56,7 +61,7 @@ class HttpError extends Error { } } -interface TransportFunction { +export interface TransportFunction { ( level: LogLevel, message: string, @@ -68,11 +73,11 @@ interface TransportFunction { ): Promise | void } -interface Transport { +export interface Transport { log: TransportFunction } -interface Options { +export interface Options { config?: { customLogFormat?: string logFilePath?: string @@ -86,18 +91,3 @@ interface Options { transports?: Transport[] } } - -export { - ColorMap, - HttpError, - LogComponents, - LogData, - Logger, - LogLevel, - Options, - RequestInfo, - Server, - StoreData, - Transport, - TransportFunction -} diff --git a/src/utils/colorMapping.ts b/src/utils/colorMapping.ts index 7e537cf..ddbe6be 100644 --- a/src/utils/colorMapping.ts +++ b/src/utils/colorMapping.ts @@ -1,24 +1,14 @@ import chalk from 'chalk' -import { ColorMap } from '~/types' +import { ColorMap } from '../types' -/** - * The color map for the log levels. - * - * @type {ColorMap} - */ -const LogLevelColorMap: ColorMap = { +export const LogLevelColorMap: ColorMap = { INFO: chalk.bgGreen.black, WARNING: chalk.bgYellow.black, ERROR: chalk.bgRed.black } -/** - * The color map for the HTTP methods. - * - * @type {ColorMap} - */ -const HttpMethodColorMap: ColorMap = { +export const HttpMethodColorMap: ColorMap = { GET: chalk.green, POST: chalk.yellow, PUT: chalk.blue, @@ -27,5 +17,3 @@ const HttpMethodColorMap: ColorMap = { HEAD: chalk.cyan, OPTIONS: chalk.magenta } - -export { HttpMethodColorMap, LogLevelColorMap } diff --git a/src/utils/duration.ts b/src/utils/duration.ts index e619d4e..cc15636 100644 --- a/src/utils/duration.ts +++ b/src/utils/duration.ts @@ -7,14 +7,10 @@ const timeUnits = [ { unit: 'ns', threshold: 1, decimalPlaces: 0 } ] -/** - * Converts a time difference into a formatted string with the most appropriate time unit. - * - * @param {bigint} beforeTime - The timestamp taken before the request. - * @param {boolean} useColors - Whether to apply colors to the output. - * @returns {string} A formatted duration string including the time unit. - */ -function durationString(beforeTime: bigint, useColors: boolean): string { +export default function durationString( + beforeTime: bigint, + useColors: boolean +): string { const nanoseconds = Number(process.hrtime.bigint() - beforeTime) for (const { unit, threshold, decimalPlaces } of timeUnits) { @@ -29,5 +25,3 @@ function durationString(beforeTime: bigint, useColors: boolean): string { ? chalk.gray('0ns'.padStart(8).padEnd(16)) : '0ns'.padStart(8).padEnd(16) } - -export default durationString diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..792a8de --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,6 @@ +export { HttpMethodColorMap, LogLevelColorMap } from './colorMapping' +export { default as durationString } from './duration' +export { default as logString } from './log' +export { default as methodString } from './method' +export { default as pathString } from './path' +export { default as statusString } from './status' diff --git a/src/utils/log.ts b/src/utils/log.ts index 950c45e..94d1982 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -1,18 +1,9 @@ -import { LogLevel } from '~/types' -import { LogLevelColorMap } from '~/utils/colorMapping' +import { LogLevel } from '../types' +import { LogLevelColorMap } from './colorMapping' -/** - * Converts the log level to a string. - * - * @param {LogLevel} level The log level. - * @param {boolean} useColors - Whether to apply colors to the output. - * @returns {string} The log level as a string. - */ -const logString = (level: LogLevel, useColors: boolean): string => { +export default function logString(level: LogLevel, useColors: boolean): string { const levelStr = level.toUpperCase() return useColors ? LogLevelColorMap[levelStr]?.(levelStr.padEnd(7)) || levelStr : levelStr.padEnd(7) } - -export default logString diff --git a/src/utils/method.ts b/src/utils/method.ts index 9cb59a6..81f0d62 100644 --- a/src/utils/method.ts +++ b/src/utils/method.ts @@ -1,17 +1,11 @@ -import { HttpMethodColorMap } from '~/utils/colorMapping' +import { HttpMethodColorMap } from './colorMapping' -/** - * Converts an HTTP request method to a colored string representation. - * - * @param {string} method The HTTP request method (e.g., 'GET', 'POST'). - * @param {boolean} useColors - Whether to apply colors to the output. - * @returns {string} A string representing the method. - */ -function methodString(method: string, useColors: boolean): string { +export default function methodString( + method: string, + useColors: boolean +): string { const colorFunction = HttpMethodColorMap[method] return useColors && colorFunction ? colorFunction(method.padEnd(7)) : method.padEnd(7) } - -export default methodString diff --git a/src/utils/path.ts b/src/utils/path.ts index 1fdfef5..2dfbff4 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,17 +1,11 @@ -import { RequestInfo } from '~/types' +import { RequestInfo } from '../types' -/** - * Returns the path string. - * - * @param {RequestInfo} requestInfo The request info. - * @returns {string | undefined} The path string. - */ -const pathString = (requestInfo: RequestInfo): string | undefined => { +export default function pathString( + requestInfo: RequestInfo +): string | undefined { try { return new URL(requestInfo.url).pathname } catch { return undefined } } - -export default pathString diff --git a/src/utils/status.ts b/src/utils/status.ts index cef5eb6..18c605e 100644 --- a/src/utils/status.ts +++ b/src/utils/status.ts @@ -1,13 +1,9 @@ import chalk from 'chalk' -/** - * Converts the status code to a string. - * - * @param {number} status The status code. - * @param {boolean} useColors - Whether to apply colors to the output. - * @returns {string} The status code as a string. - */ -const statusString = (status: number, useColors: boolean): string => { +export default function statusString( + status: number, + useColors: boolean +): string { const color = status >= 500 ? 'red' @@ -20,5 +16,3 @@ const statusString = (status: number, useColors: boolean): string => { : 'white' return useColors ? chalk[color](status.toString()) : status.toString() } - -export default statusString From 6e701dff90898379398ecd85dcac366f33e7321d Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:15:12 +0700 Subject: [PATCH 2/6] refactor(test): new testing --- test/logger/filter.test.ts | 62 ------- test/logger/logToFile.test.ts | 78 --------- test/logger/transports.test.ts | 84 --------- test/logixlysia.test.ts | 209 ----------------------- test/types/ColorMap.test.ts | 25 --- test/types/HttpError.test.ts | 16 -- test/types/Logger.test.ts | 91 ---------- test/types/RequestInfo.test.ts | 23 --- test/types/Server.test.ts | 27 --- test/types/StoreData.test.ts | 17 -- test/utils/duration.test.ts | 55 ------ test/utils/log.test.ts | 31 ---- test/utils/method.test.ts | 21 --- test/utils/path.test.ts | 36 ---- test/utils/start.test.ts | 38 ----- test/utils/status.test.ts | 31 ---- tests/__mocks__/fs.ts | 9 + tests/core/buildLogMessage.test.ts | 33 ++++ tests/core/filter.test.ts | 35 ++++ tests/helpers.ts | 19 +++ tests/logixlysia.test.ts | 8 + tests/transports/logToTransports.test.ts | 29 ++++ 22 files changed, 133 insertions(+), 844 deletions(-) delete mode 100644 test/logger/filter.test.ts delete mode 100644 test/logger/logToFile.test.ts delete mode 100644 test/logger/transports.test.ts delete mode 100644 test/logixlysia.test.ts delete mode 100644 test/types/ColorMap.test.ts delete mode 100644 test/types/HttpError.test.ts delete mode 100644 test/types/Logger.test.ts delete mode 100644 test/types/RequestInfo.test.ts delete mode 100644 test/types/Server.test.ts delete mode 100644 test/types/StoreData.test.ts delete mode 100644 test/utils/duration.test.ts delete mode 100644 test/utils/log.test.ts delete mode 100644 test/utils/method.test.ts delete mode 100644 test/utils/path.test.ts delete mode 100644 test/utils/start.test.ts delete mode 100644 test/utils/status.test.ts create mode 100644 tests/__mocks__/fs.ts create mode 100644 tests/core/buildLogMessage.test.ts create mode 100644 tests/core/filter.test.ts create mode 100644 tests/helpers.ts create mode 100644 tests/logixlysia.test.ts create mode 100644 tests/transports/logToTransports.test.ts diff --git a/test/logger/filter.test.ts b/test/logger/filter.test.ts deleted file mode 100644 index 4da2f47..0000000 --- a/test/logger/filter.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { filterLog } from '~/logger/filter' -import { LogLevel, Options } from '~/types' - -describe('Filter log', () => { - const logLevel: LogLevel = 'info' - const status = 200 - const method = 'GET' - - it('Should return true if no filter is provided', () => { - expect(filterLog(logLevel, status, method)).toBe(true) - }) - - it('Should filter by log level (single value)', () => { - const options: Options = { config: { logFilter: { level: 'error' } } } - expect(filterLog(logLevel, status, method, options)).toBe(false) - }) - - it('Should filter by log level (array)', () => { - const options: Options = { - config: { logFilter: { level: ['error', 'info'] } } - } - expect(filterLog(logLevel, status, method, options)).toBe(true) - }) - - it('Should filter by status (single value)', () => { - const options: Options = { config: { logFilter: { status: 404 } } } - expect(filterLog(logLevel, status, method, options)).toBe(false) - }) - - it('Should filter by status (array)', () => { - const options: Options = { config: { logFilter: { status: [200, 404] } } } - expect(filterLog(logLevel, status, method, options)).toBe(true) - }) - - it('Should filter by method (single value)', () => { - const options: Options = { config: { logFilter: { method: 'POST' } } } - expect(filterLog(logLevel, status, method, options)).toBe(false) - }) - - it('Should filter by method (array)', () => { - const options: Options = { - config: { logFilter: { method: ['GET', 'POST'] } } - } - expect(filterLog(logLevel, status, method, options)).toBe(true) - }) - - it('Should return false if any filter condition is not met', () => { - const options: Options = { - config: { logFilter: { level: 'info', status: 404, method: 'POST' } } - } - expect(filterLog(logLevel, status, method, options)).toBe(false) - }) - - it('Should return true if all filter conditions are met', () => { - const options: Options = { - config: { logFilter: { level: 'info', status: 200, method: 'GET' } } - } - expect(filterLog(logLevel, status, method, options)).toBe(true) - }) -}) diff --git a/test/logger/logToFile.test.ts b/test/logger/logToFile.test.ts deleted file mode 100644 index 0249ee9..0000000 --- a/test/logger/logToFile.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from 'bun:test' -import fs from 'fs/promises' -import path from 'path' - -import { logToFile } from '~/logger/logToFile' -import { LogData, LogLevel, Options, RequestInfo, StoreData } from '~/types' - -describe('logToFile', () => { - const testDir = path.join(process.cwd(), 'test-logs') - const testFile = path.join(testDir, 'test.log') - - beforeEach(async () => { - await fs.mkdir(testDir, { recursive: true }) - }) - - afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }) - }) - - it('should create a log file and write to it', async () => { - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - const options: Options = {} - - await logToFile(testFile, level, request, data, store, options) - - const fileContent = await fs.readFile(testFile, 'utf-8') - expect(fileContent).toContain('INFO') - expect(fileContent).toContain('GET') - expect(fileContent).toContain('200') - }) - - it('should append to an existing log file', async () => { - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - const options: Options = {} - - await logToFile(testFile, level, request, data, store, options) - await logToFile(testFile, level, request, data, store, options) - - const fileContent = await fs.readFile(testFile, 'utf-8') - const logEntries = fileContent.trim().split('\n') - expect(logEntries).toHaveLength(2) - }) - - it('should use custom log format if provided', async () => { - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - const options: Options = { - config: { - customLogFormat: '{level} - {method} {pathname}' - } - } - - await logToFile(testFile, level, request, data, store, options) - - const fileContent = await fs.readFile(testFile, 'utf-8') - expect(fileContent.trim()).toMatch('INFO - GET') - }) -}) diff --git a/test/logger/transports.test.ts b/test/logger/transports.test.ts deleted file mode 100644 index 6c101c6..0000000 --- a/test/logger/transports.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { beforeEach, describe, expect, it, jest } from 'bun:test' - -import { logToTransports } from '~/transports' -import { - LogData, - LogLevel, - Options, - RequestInfo, - StoreData, - Transport -} from '~/types' - -describe('Custom Transports', () => { - let mockTransport: Transport - let options: Options - - beforeEach(() => { - mockTransport = { - log: jest.fn().mockResolvedValue(undefined) - } - options = { - config: { - transports: [mockTransport] - } - } - }) - - it('should call the custom transport log function', async () => { - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - - await logToTransports(level, request, data, store, options) - - expect(mockTransport.log).toHaveBeenCalledTimes(1) - expect(mockTransport.log).toHaveBeenCalledWith(level, expect.any(String), { - request, - data, - store - }) - }) - - it('should not call any transports if none are configured', async () => { - const options: Options = { config: {} } - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - - await logToTransports(level, request, data, store, options) - - expect(mockTransport.log).not.toHaveBeenCalled() - }) - - it('should call multiple transports if configured', async () => { - const secondMockTransport: Transport = { - log: jest.fn().mockResolvedValue(undefined) - } - options.config!.transports!.push(secondMockTransport) - - const level: LogLevel = 'INFO' - const request: RequestInfo = { - url: '/test', - method: 'GET', - headers: { get: () => null } - } - const data: LogData = { status: 200 } - const store: StoreData = { beforeTime: BigInt(0) } - - await logToTransports(level, request, data, store, options) - - expect(mockTransport.log).toHaveBeenCalledTimes(1) - expect(secondMockTransport.log).toHaveBeenCalledTimes(1) - }) -}) diff --git a/test/logixlysia.test.ts b/test/logixlysia.test.ts deleted file mode 100644 index 16963a9..0000000 --- a/test/logixlysia.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { edenTreaty } from '@elysiajs/eden' -import { beforeAll, beforeEach, describe, expect, it } from 'bun:test' -import { Elysia } from 'elysia' - -import logixlysia from '~/index' - -describe('Logixlysia', () => { - let server: Elysia - let app: ReturnType | any - let logs: string[] - - beforeEach(() => { - logs = [] - }) - - describe('IP Logging', () => { - describe('when enabled', () => { - beforeAll(() => { - server = new Elysia() - .use( - logixlysia({ - config: { - ip: true, - customLogFormat: - '🦊 {now} {duration} {level} {method} {pathname} {status} {message} {ip}' - } - }) - ) - .get('/', () => '🦊 Logixlysia Getting') - .post('logixlysia', () => '🦊 Logixlysia Posting') - .listen(3000) - - app = edenTreaty('http://127.0.0.1:3000') - }) - - it("logs incoming IP address for GET '/' requests when X-Forwarded-For header is present", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - await app.get('/', { - headers: { 'X-Forwarded-For': '192.168.1.1' } - }) - } - - logs.forEach(log => { - expect(log).toMatch( - /^🦊 .+ INFO .+ .+ GET \/ .+ IP: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ - ) - }) - }) - - it("logs 'null' for GET '/' requests when X-Forwarded-For header is not present", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - await app.get('/') - } - - logs.forEach(log => { - expect(log).toMatch(/^🦊 .+ INFO .+ .+ GET \/ .+ IP: null$/) - }) - }) - }) - - describe('when disabled', () => { - beforeAll(() => { - server = new Elysia() - .use( - logixlysia({ - config: { - ip: false, - customLogFormat: - '🦊 {now} {duration} {level} {method} {pathname} {status} {message} {ip}' - } - }) - ) - .get('/', () => '🦊 Logixlysia Getting') - .post('logixlysia', () => '🦊 Logixlysia Posting') - .listen(3000) - - app = edenTreaty('http://127.0.0.1:3000') - }) - - it("responds correctly to GET '/' requests", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - logs.push((await app.get('/')).data) - } - - logs.forEach(log => { - expect(log).toBe('🦊 Logixlysia Getting') - }) - }) - - it("responds correctly to POST '/logixlysia' requests", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - const postResponse = await app.logixlysia.post({}) - logs.push( - postResponse.status === 200 ? postResponse.data : postResponse.error - ) - } - - logs.forEach(log => { - expect(log).toBe('🦊 Logixlysia Posting') - }) - }) - - it('throws an error when attempting to post to an undefined route', async () => { - const response = await app.post('/undefinedRoute', {}) - const error = response.error - - expect(response.status).toBe(404) - expect(error).toBeInstanceOf(Error) - }) - }) - }) - - describe('Log Filtering', () => { - describe('when enabled', () => { - beforeAll(() => { - server = new Elysia() - .use( - logixlysia({ - config: { - logFilter: { - level: 'INFO', - status: [200, 404], - method: 'GET' - } - } - }) - ) - .get('/', () => '🦊 Logixlysia Getting') - .post('logixlysia', () => '🦊 Logixlysia Posting') - .listen(3000) - - app = edenTreaty('http://127.0.0.1:3000') - }) - - it("logs 'GET' requests with status 200 or 404 when log filtering criteria are met", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - logs.push((await app.get('/')).data) - } - - expect(logs.length).toBe(requestCount) - logs.forEach(log => { - expect(log).toMatch('🦊 Logixlysia Getting') - }) - }) - - it("doesn't log 'POST' requests when log filtering criteria are not met", async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - await app.post('/logixlysia', {}) - } - - expect(logs.length).toBe(0) - }) - - const otherMethods = ['PUT', 'DELETE', 'PATCH', 'HEAD'] - otherMethods.forEach(method => { - it(`logs '${method}' requests with status 200 or 404 when log filtering criteria are met`, async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - logs.push((await app[method.toLowerCase()]('/')).data) - } - - expect(logs.length).toBe(requestCount) - }) - }) - }) - - describe('when disabled', () => { - beforeAll(() => { - server = new Elysia() - .use( - logixlysia({ - config: { - logFilter: null - } - }) - ) - .get('/', () => '🦊 Logixlysia Getting') - .post('logixlysia', () => '🦊 Logixlysia Posting') - .listen(3000) - - app = edenTreaty('http://127.0.0.1:3000') - }) - - it('logs all requests when log filtering is disabled', async () => { - const requestCount = 5 - - for (let i = 0; i < requestCount; i++) { - logs.push((await app.get('/')).data) - logs.push((await app.post('/logixlysia', {})).data) - } - - expect(logs.length).toBe(requestCount * 2) - }) - }) - }) -}) diff --git a/test/types/ColorMap.test.ts b/test/types/ColorMap.test.ts deleted file mode 100644 index 90aebfa..0000000 --- a/test/types/ColorMap.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { ColorMap } from '~/types' - -describe('Color Mapping Interface', () => { - it('Defines an object with string keys mapping to functions', () => { - const colorMap: ColorMap = { - red: (str: string) => `Red: ${str}`, - green: (str: string) => `Green: ${str}`, - blue: (str: string) => `Blue: ${str}` - } - - expect(colorMap).toEqual( - expect.objectContaining({ - red: expect.any(Function), - green: expect.any(Function), - blue: expect.any(Function) - }) - ) - - Object.keys(colorMap).forEach(key => { - expect(typeof colorMap[key]).toBe('function') - }) - }) -}) diff --git a/test/types/HttpError.test.ts b/test/types/HttpError.test.ts deleted file mode 100644 index 5b25801..0000000 --- a/test/types/HttpError.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { HttpError } from '~/types' - -describe('HttpError', () => { - it('Should create an instance with correct status and message', () => { - const status = 404 - const message = 'Not Found' - const error = new HttpError(status, message) - - expect(error).toBeInstanceOf(Error) - expect(error).toBeInstanceOf(HttpError) - expect(error.status).toBe(status) - expect(error.message).toBe(message) - }) -}) diff --git a/test/types/Logger.test.ts b/test/types/Logger.test.ts deleted file mode 100644 index 259cf91..0000000 --- a/test/types/Logger.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { beforeEach, describe, expect, it, jest } from 'bun:test' - -import { LogData, RequestInfo, StoreData } from '~/types' - -interface Logger { - info(request: RequestInfo, data: LogData, store: StoreData): void - warning(request: RequestInfo, data: LogData, store: StoreData): void - error(request: RequestInfo, data: LogData, store: StoreData): void -} - -describe('Logger interface', () => { - let logger: Logger - - beforeEach(() => { - logger = { - info: jest.fn(), - warning: jest.fn(), - error: jest.fn() - } - }) - - it('Defines the Logger interface correctly', () => { - expect(logger).toEqual( - expect.objectContaining({ - info: expect.any(Function), - warning: expect.any(Function), - error: expect.any(Function) - }) - ) - }) - - it('Calls the info log function with the correct arguments', () => { - const request: RequestInfo = { - url: '/info', - method: 'GET', - headers: { - get: function () { - throw new Error('Function not implemented.') - } - } - } - const data: LogData = { status: 200, message: 'Info log message' } - const store: StoreData = { - beforeTime: 0n - } - - logger.info(request, data, store) - - expect(logger.info).toHaveBeenCalledWith(request, data, store) - }) - - it('Calls the warning log function with the correct arguments', () => { - const request: RequestInfo = { - url: '/warning', - method: 'POST', - headers: { - get: function () { - throw new Error('Function not implemented.') - } - } - } - const data: LogData = { status: 404, message: 'Warning log message' } - const store: StoreData = { - beforeTime: 0n - } - - logger.warning(request, data, store) - - expect(logger.warning).toHaveBeenCalledWith(request, data, store) - }) - - it('Calls the error log function with the correct arguments', () => { - const request: RequestInfo = { - url: '/error', - method: 'DELETE', - headers: { - get: function () { - throw new Error('Function not implemented.') - } - } - } - const data: LogData = { status: 500, message: 'Error log message' } - const store: StoreData = { - beforeTime: 0n - } - - logger.error(request, data, store) - - expect(logger.error).toHaveBeenCalledWith(request, data, store) - }) -}) diff --git a/test/types/RequestInfo.test.ts b/test/types/RequestInfo.test.ts deleted file mode 100644 index 0acbb07..0000000 --- a/test/types/RequestInfo.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { RequestInfo } from '~/types' - -describe('Request Infomation interface', () => { - it('Defines the RequestInfo interface correctly', () => { - const headers = { get: () => 'value' } - const method = 'GET' - const url = 'https://example.com/api' - - const request: RequestInfo = { headers, method, url } - - expect(request).toEqual( - expect.objectContaining({ - headers: expect.objectContaining({ - get: expect.any(Function) - }), - method: expect.any(String), - url: expect.any(String) - }) - ) - }) -}) diff --git a/test/types/Server.test.ts b/test/types/Server.test.ts deleted file mode 100644 index fc831ac..0000000 --- a/test/types/Server.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { Server } from '~/types' - -describe('Server interface', () => { - it('Defines the Server interface correctly', () => { - const server: Server = { - hostname: 'example.com', - port: 8080, - protocol: 'https' - } - - expect(server).toEqual( - expect.objectContaining({ - hostname: expect.any(String), - port: expect.any(Number), - protocol: expect.any(String) - }) - ) - }) - - it('Allows optional properties in the Server interface', () => { - const serverWithoutOptionalProps: Server = {} - - expect(serverWithoutOptionalProps).toEqual(expect.objectContaining({})) - }) -}) diff --git a/test/types/StoreData.test.ts b/test/types/StoreData.test.ts deleted file mode 100644 index 787a29c..0000000 --- a/test/types/StoreData.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { StoreData } from '~/types' - -describe('Store Data interface', () => { - it('Defines the StoreData interface correctly', () => { - const beforeTime: bigint = BigInt(1633393533526) // Example bigint value - - const storeData: StoreData = { beforeTime } - - expect(storeData).toEqual( - expect.objectContaining({ - beforeTime: expect.any(BigInt) - }) - ) - }) -}) diff --git a/test/utils/duration.test.ts b/test/utils/duration.test.ts deleted file mode 100644 index 05af1e0..0000000 --- a/test/utils/duration.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// import { describe, expect, it } from 'bun:test' - -// import durationString from '~/utils/duration' - -// describe('Duration String', () => { -// const testCases: [string, number, RegExp][] = [ -// [ -// 'Generates a string representing the duration in Seconds (s) unit', -// 2e9, -// new RegExp(`^\\u001B\\[90m\\s+2\\.00s\\s+\\u001B\\[39m$`) -// ], -// [ -// 'Generates a string representing the duration in Milliseconds (ms) unit', -// 2e6, -// new RegExp(`^\\u001B\\[90m\\s+2ms\\s+\\u001B\\[39m$`) -// ], -// [ -// 'Generates a string representing the duration in Microseconds (µs) unit', -// 2e3, -// new RegExp(`^\\u001B\\[90m\\s+2µs\\s+\\u001B\\[39m$`) -// ] -// ] - -// testCases.forEach(([description, nanoseconds, expectedPattern]) => { -// it(description, () => { -// const beforeTime = process.hrtime.bigint() - BigInt(nanoseconds) -// const result = durationString(beforeTime, true) -// expect(result).toMatch(expectedPattern) -// }) -// }) - -// it('Generates a string representing the duration in Nanoseconds (ns) unit', () => { -// const beforeTime = process.hrtime.bigint() - BigInt(2) -// const result = durationString(beforeTime, true) -// expect(result).toMatch( -// new RegExp(`^\\u001B\\[90m\\s+\\d+ns\\s+\\u001B\\[39m$`) -// ) -// }) - -// it('Returns non-colored string when useColors is false', () => { -// const beforeTime = process.hrtime.bigint() - BigInt(2e9) -// const result = durationString(beforeTime, false) -// expect(result).toMatch(/^\s+2\.00s\s+$/) -// }) - -// it('Returns a small duration for very small time differences', () => { -// const beforeTime = process.hrtime.bigint() -// const result = durationString(beforeTime, true) -// expect(result).toMatch( -// new RegExp(`^\\u001B\\[90m\\s+\\d+ns\\s+\\u001B\\[39m$`) -// ) -// }) -// }) - -// Disable this test for now diff --git a/test/utils/log.test.ts b/test/utils/log.test.ts deleted file mode 100644 index aa9d840..0000000 --- a/test/utils/log.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from 'bun:test' -import chalk from 'chalk' - -import logString from '~/utils/log' - -describe('Log String', () => { - it('Produces a green background string for INFO log level', () => { - const result = logString('INFO', true) - expect(result).toBe(chalk.bgGreen.black('INFO ')) - }) - - it('Produces a yellow background string for WARNING log level', () => { - const result = logString('WARNING', true) - expect(result).toBe(chalk.bgYellow.black('WARNING')) - }) - - it('Produces a red background string for ERROR log level', () => { - const result = logString('ERROR', true) - expect(result).toBe(chalk.bgRed.black('ERROR ')) - }) - - it('Returns the padded input string for unrecognized log levels', () => { - const result = logString('DEBUG', true) - expect(result).toBe('DEBUG') - }) - - it('Returns the padded input string without colors when useColors is false', () => { - const result = logString('INFO', false) - expect(result).toBe('INFO ') - }) -}) diff --git a/test/utils/method.test.ts b/test/utils/method.test.ts deleted file mode 100644 index ae0bbad..0000000 --- a/test/utils/method.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from 'bun:test' -import chalk from 'chalk' - -import methodString from '~/utils/method' - -describe('Method String', () => { - it('Displays a colored string for the GET method', () => { - const result = methodString('GET', true) - expect(result).toBe(chalk.green('GET ')) - }) - - it('Displays a colored string for the POST method', () => { - const result = methodString('POST', true) - expect(result).toBe(chalk.yellow('POST ')) - }) - - it('Outputs the original method string if it is not recognized', () => { - const result = methodString('INVALID_METHOD', true) - expect(result).toBe('INVALID_METHOD') // No coloring, returns the original string - }) -}) diff --git a/test/utils/path.test.ts b/test/utils/path.test.ts deleted file mode 100644 index 3bcea2a..0000000 --- a/test/utils/path.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, it } from 'bun:test' - -import { RequestInfo } from '~/types' -import pathString from '~/utils/path' - -describe('Path String', () => { - it('Extracts the pathname from a valid URL', () => { - const testPath: RequestInfo = { - url: 'https://www.example.com/path/to/resource', - headers: new Map(), - method: 'GET' - } - const result = pathString(testPath) - expect(result).toBe('/path/to/resource') - }) - - it('Handles malformed URL gracefully', () => { - const testPath: RequestInfo = { - url: 'invalid url', - headers: new Map(), - method: 'GET' - } - const result = pathString(testPath) - expect(result).toBeUndefined() - }) - - it('Returns undefined if the URL is missing', () => { - const testPath: RequestInfo = { - url: '', - headers: new Map(), - method: 'GET' - } - const result = pathString(testPath) - expect(result).toBeUndefined() - }) -}) diff --git a/test/utils/start.test.ts b/test/utils/start.test.ts deleted file mode 100644 index c0cca6e..0000000 --- a/test/utils/start.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, jest } from 'bun:test' - -import { Server } from '~/types' -import startServer from '~/utils/start' - -describe('Start String', () => { - let originalConsoleLog: typeof console.log - let mockConsoleLog: jest.Mock - - beforeEach(() => { - originalConsoleLog = console.log - mockConsoleLog = jest.fn() - console.log = mockConsoleLog - }) - - afterEach(() => { - console.log = originalConsoleLog - }) - - it('Correctly logs the expected message upon server start', () => { - const config: Server = { - hostname: 'localhost', - port: 3000, - protocol: 'http' - } - - startServer(config) - - const expectedMessage = `🦊 Elysia is running at http://localhost:3000` - - // Extract the arguments passed to console.log during the function call - const logMessage = mockConsoleLog.mock.calls - .map(args => args.join(' ')) - .join(' ') - - expect(logMessage).toMatch(expectedMessage) - }) -}) diff --git a/test/utils/status.test.ts b/test/utils/status.test.ts deleted file mode 100644 index 68b4554..0000000 --- a/test/utils/status.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from 'bun:test' -import chalk from 'chalk' - -import statusString from '~/utils/status' - -describe('Status String', () => { - it('Presents the status string in green for a 200 status code', () => { - const result = statusString(200, true) - expect(result).toBe(chalk.green('200')) - }) - - it('Presents the status string in cyan for a 301 status code', () => { - const result = statusString(301, true) - expect(result).toBe(chalk.cyan('301')) - }) - - it('Presents the status string in yellow for a 404 status code', () => { - const result = statusString(404, true) - expect(result).toBe(chalk.yellow('404')) - }) - - it('Presents the status string in red for a 500 status code', () => { - const result = statusString(500, true) - expect(result).toBe(chalk.red('500')) - }) - - it('Presents the status string in white for a 100 status code', () => { - const result = statusString(100, true) - expect(result).toBe(chalk.white('100')) - }) -}) diff --git a/tests/__mocks__/fs.ts b/tests/__mocks__/fs.ts new file mode 100644 index 0000000..7628355 --- /dev/null +++ b/tests/__mocks__/fs.ts @@ -0,0 +1,9 @@ +import { mock } from 'bun:test' + +export const mockAppend = mock(() => Promise.resolve()) +export const mockMkdir = mock(() => Promise.resolve()) + +export const promises = { + appendFile: mockAppend, + mkdir: mockMkdir +} diff --git a/tests/core/buildLogMessage.test.ts b/tests/core/buildLogMessage.test.ts new file mode 100644 index 0000000..f311e3f --- /dev/null +++ b/tests/core/buildLogMessage.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from 'bun:test' + +import { buildLogMessage } from '../../src/core/buildLogMessage' +import { LogData, LogLevel, Options, StoreData } from '../../src/types' +import { createMockRequest } from '../helpers' + +test('buildLogMessage', () => { + const level: LogLevel = 'INFO' + const request = createMockRequest() + const data: LogData = { status: 200, message: 'Test message' } + const store: StoreData = { beforeTime: BigInt(0) } + const options: Options = { + config: { + ip: true, + customLogFormat: '{level} {message} {ip}' + } + } + + const message = buildLogMessage(level, request, data, store, options, false) + expect(message).toContain('INFO') + expect(message).toContain('Test message') + expect(message).toContain('127.0.0.1') + + const colorMessage = buildLogMessage( + level, + request, + data, + store, + options, + true + ) + expect(colorMessage).toContain('\x1b[') // ANSI color codes +}) diff --git a/tests/core/filter.test.ts b/tests/core/filter.test.ts new file mode 100644 index 0000000..ea9a929 --- /dev/null +++ b/tests/core/filter.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from 'bun:test' + +import { filterLog } from '../../src/core/filter' +import { Options } from '../../src/types' + +test('filterLog', () => { + const options: Options = { + config: { + logFilter: { + level: 'ERROR', + method: 'POST', + status: 500 + } + } + } + + expect(filterLog('ERROR', 500, 'POST', options)).toBe(true) + expect(filterLog('INFO', 200, 'GET', options)).toBe(false) + expect(filterLog('WARNING', 400, 'PUT', options)).toBe(false) + + // Test with array filters + const arrayOptions: Options = { + config: { + logFilter: { + level: ['ERROR', 'WARNING'], + method: ['POST', 'PUT'], + status: [500, 400] + } + } + } + + expect(filterLog('ERROR', 500, 'POST', arrayOptions)).toBe(true) + expect(filterLog('WARNING', 400, 'PUT', arrayOptions)).toBe(true) + expect(filterLog('INFO', 200, 'GET', arrayOptions)).toBe(false) +}) diff --git a/tests/helpers.ts b/tests/helpers.ts new file mode 100644 index 0000000..1ed132a --- /dev/null +++ b/tests/helpers.ts @@ -0,0 +1,19 @@ +import { mock } from 'bun:test' + +import { RequestInfo } from '../src/types' + +export function createMockRequest( + method: string = 'GET', + url: string = 'http://localhost:3000/' +): RequestInfo { + return { + headers: { + get: (key: string) => (key === 'x-forwarded-for' ? '127.0.0.1' : null) + }, + method, + url + } +} + +export const mockConsoleLog = mock(() => {}) +export const mockConsoleError = mock(() => {}) diff --git a/tests/logixlysia.test.ts b/tests/logixlysia.test.ts new file mode 100644 index 0000000..e13598c --- /dev/null +++ b/tests/logixlysia.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'bun:test' + +import logixlysia from '../src/index' + +test('logixlysia', () => { + const elysia = logixlysia() + expect(elysia).toBeDefined() +}) diff --git a/tests/transports/logToTransports.test.ts b/tests/transports/logToTransports.test.ts new file mode 100644 index 0000000..d79e9fa --- /dev/null +++ b/tests/transports/logToTransports.test.ts @@ -0,0 +1,29 @@ +import { expect, mock, test } from 'bun:test' + +import { logToTransports } from '../../src/transports' +import { LogData, LogLevel, Options, StoreData } from '../../src/types' +import { createMockRequest } from '../helpers' + +test('logToTransports', async () => { + const level: LogLevel = 'INFO' + const request = createMockRequest() + const data: LogData = { status: 200, message: 'Test message' } + const store: StoreData = { beforeTime: BigInt(0) } + + const mockTransport = { + log: mock(() => Promise.resolve()) + } + + const options: Options = { + config: { + transports: [mockTransport] + } + } + + await logToTransports(level, request, data, store, options) + expect(mockTransport.log).toHaveBeenCalled() + + // Test with no transports + await logToTransports(level, request, data, store, {}) + expect(mockTransport.log).toHaveBeenCalledTimes(1) // Should not be called again +}) From 9c0feb2c5b0f18daef2372df97917586fb5581a9 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:30:24 +0700 Subject: [PATCH 3/6] chore(eslint): change rule simple sort from error to warn --- eslint.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 6fbbadc..e8c673f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,8 +18,8 @@ export default [ 'no-undef': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error' + 'simple-import-sort/imports': 'warn', + 'simple-import-sort/exports': 'warn' } } ] From e6a646665b1bcdc7abce1e95c89f6b244dc5fb2e Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:30:46 +0700 Subject: [PATCH 4/6] chore(tsconfig): remove path config --- tsconfig.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 53532a6..2c3750e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,9 +6,6 @@ "allowImportingTsExtensions": true, "module": "ESNext", "moduleResolution": "Bundler", - "paths": { - "~/*": ["./src/*"] - }, "resolveJsonModule": true, "types": ["bun-types"], "downlevelIteration": true, From 38588ad9218b70a33b2543c289af60c95d3016fa Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:33:19 +0700 Subject: [PATCH 5/6] chore(example): update path when without path config --- example/basic.ts | 2 +- example/transports/index.ts | 3 +-- example/transports/myCustomTransport.ts | 8 +++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/example/basic.ts b/example/basic.ts index d2b9ce8..3052f72 100644 --- a/example/basic.ts +++ b/example/basic.ts @@ -1,6 +1,6 @@ import { Elysia } from 'elysia' -import logixlysia from '~/index' +import logixlysia from '../src/index' const app = new Elysia({ name: 'Basic Example' diff --git a/example/transports/index.ts b/example/transports/index.ts index 5c7107d..f94383b 100644 --- a/example/transports/index.ts +++ b/example/transports/index.ts @@ -1,7 +1,6 @@ import { Elysia } from 'elysia' -import logixlysia from '~/index' - +import logixlysia from '../../src/index' import MyCustomTransport from './myCustomTransport' const app = new Elysia() diff --git a/example/transports/myCustomTransport.ts b/example/transports/myCustomTransport.ts index 96fd249..c6555fd 100644 --- a/example/transports/myCustomTransport.ts +++ b/example/transports/myCustomTransport.ts @@ -1,4 +1,10 @@ -import { LogData, LogLevel, RequestInfo, StoreData, Transport } from '~/types' +import { + LogData, + LogLevel, + RequestInfo, + StoreData, + Transport +} from '../../src/types' class MyCustomTransport implements Transport { async log( From 1bb2d07ae0c815742a9eb7c930ac8487ac287b2c Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:33:59 +0700 Subject: [PATCH 6/6] fix(logtofile): store error to log file fix #63 --- src/core/handleHttpError.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/core/handleHttpError.ts b/src/core/handleHttpError.ts index c0f3049..f43def1 100644 --- a/src/core/handleHttpError.ts +++ b/src/core/handleHttpError.ts @@ -1,3 +1,4 @@ +import { logToFile } from '../transports' import { HttpError, Options, RequestInfo, StoreData } from '../types' import { buildLogMessage } from './buildLogMessage' @@ -11,4 +12,19 @@ export function handleHttpError( console.error( buildLogMessage('ERROR', request, { status: statusCode }, store, options) ) + + const promises = [] + + if (options?.config?.logFilePath) { + promises.push( + logToFile( + options.config.logFilePath, + 'ERROR', + request, + { status: statusCode }, + store, + options + ) + ) + } }