Skip to content

Commit eab3acc

Browse files
committed
test: 로깅 유틸 함수에 대한 테스트 추가
1 parent 0f91d62 commit eab3acc

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed

src/types/logging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44
export interface LogContext {
55
requestId: string;
6-
userId?: string;
6+
userId?: number;
77
method: string;
88
url: string;
99
userAgent?: string;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { Request, Response } from 'express';
2+
import { Socket } from 'net';
3+
import {
4+
createLogContext,
5+
logError,
6+
logAccess,
7+
getClientIp,
8+
getLogLevel,
9+
} from '@/utils/logging.util';
10+
import { CustomError } from '@/exception';
11+
import { User } from '@/types';
12+
import logger from '@/configs/logger.config';
13+
14+
// logger 모킹
15+
jest.mock('@/configs/logger.config', () => ({
16+
error: jest.fn(),
17+
warn: jest.fn(),
18+
info: jest.fn(),
19+
}));
20+
21+
describe('Logging Utilities', () => {
22+
let mockRequest: Partial<Request>;
23+
let mockResponse: Partial<Response>;
24+
25+
beforeEach(() => {
26+
mockRequest = {
27+
headers: {'x-forwarded-for': '127.0.0.1'},
28+
method: 'GET',
29+
originalUrl: '/api/test',
30+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
31+
user: { id: 123, velog_uuid: 'user123' } as User,
32+
requestId: 'test-request-id',
33+
startTime: Date.now() - 100,
34+
};
35+
36+
mockResponse = {
37+
statusCode: 200,
38+
get: jest.fn(),
39+
};
40+
});
41+
42+
afterEach(() => {
43+
jest.clearAllMocks();
44+
});
45+
46+
describe('getClientIp', () => {
47+
it('x-forwarded-for 헤더에서 IP를 추출해야 한다', () => {
48+
mockRequest.headers = { 'x-forwarded-for': '192.168.1.1, 10.0.0.1' };
49+
expect(getClientIp(mockRequest as Request)).toBe('192.168.1.1');
50+
});
51+
52+
it('x-real-ip 헤더에서 IP를 추출해야 한다', () => {
53+
mockRequest.headers = { 'x-real-ip': '203.0.113.1' };
54+
expect(getClientIp(mockRequest as Request)).toBe('203.0.113.1');
55+
});
56+
57+
it('헤더가 없으면 unknown을 반환해야 한다', () => {
58+
mockRequest.headers = {};
59+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
60+
mockRequest.socket = { remoteAddress: undefined } as Socket;
61+
expect(getClientIp(mockRequest as Request)).toBe('unknown');
62+
});
63+
});
64+
65+
describe('getLogLevel', () => {
66+
it('200은 info 레벨을 반환해야 한다', () => {
67+
expect(getLogLevel(200)).toBe('info');
68+
});
69+
70+
it('404는 warn 레벨을 반환해야 한다', () => {
71+
expect(getLogLevel(404)).toBe('warn');
72+
});
73+
74+
it('500은 error 레벨을 반환해야 한다', () => {
75+
expect(getLogLevel(500)).toBe('error');
76+
});
77+
});
78+
79+
describe('createLogContext', () => {
80+
it('요청에서 올바른 로그 컨텍스트를 생성해야 한다', () => {
81+
const context = createLogContext(mockRequest as Request);
82+
83+
expect(context.requestId).toBe('test-request-id');
84+
expect(context.userId).toBe(123);
85+
expect(context.method).toBe('GET');
86+
expect(context.url).toBe('/api/test');
87+
expect(context.userAgent).toBeUndefined();
88+
expect(context.ip).toBe('127.0.0.1');
89+
});
90+
});
91+
92+
describe('logError', () => {
93+
it('일반 에러를 올바르게 로깅해야 한다', () => {
94+
const error = new Error('Test error');
95+
mockResponse.statusCode = 500; // error 레벨을 위해 500으로 설정
96+
97+
logError(mockRequest as Request, mockResponse as Response, error);
98+
99+
expect(logger.error).toHaveBeenCalledWith(
100+
expect.objectContaining({
101+
message: expect.objectContaining({
102+
logger: 'error',
103+
message: 'Test error',
104+
statusCode: 500,
105+
requestId: 'test-request-id',
106+
userId: 123,
107+
method: 'GET',
108+
url: '/api/test',
109+
ip: '127.0.0.1',
110+
})
111+
})
112+
);
113+
});
114+
115+
it('CustomError의 경우 에러 코드를 포함해야 한다', () => {
116+
const customError = new CustomError('Custom error', 'CUSTOM_ERROR', 400);
117+
mockResponse.statusCode = 400;
118+
119+
logError(mockRequest as Request, mockResponse as Response, customError);
120+
121+
expect(logger.error).toHaveBeenCalledWith(
122+
expect.objectContaining({
123+
message: expect.objectContaining({
124+
errorCode: 'CUSTOM_ERROR',
125+
})
126+
})
127+
);
128+
});
129+
130+
it('500이거나 예상하지 못한 에러는 스택 트레이스를 포함해야 한다', () => {
131+
const error = new Error('Test error');
132+
mockResponse.statusCode = 500;
133+
134+
logError(mockRequest as Request, mockResponse as Response, error);
135+
136+
expect(logger.error).toHaveBeenCalledWith(
137+
expect.objectContaining({
138+
message: expect.objectContaining({
139+
stack: expect.any(String),
140+
})
141+
})
142+
);
143+
});
144+
});
145+
146+
describe('logAccess', () => {
147+
it('액세스 로그를 올바르게 생성해야 한다', () => {
148+
(mockResponse.get as jest.Mock).mockReturnValue('1024');
149+
150+
logAccess(mockRequest as Request, mockResponse as Response);
151+
152+
expect(logger.info).toHaveBeenCalledWith(
153+
expect.objectContaining({
154+
logger: 'access',
155+
statusCode: 200,
156+
responseTime: expect.any(Number),
157+
responseSize: 1024,
158+
})
159+
);
160+
});
161+
});
162+
});

src/utils/logging.util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const getClientIp = (req: Request): string => {
2222
export const createLogContext = (req: Request): LogContext => {
2323
return {
2424
requestId: req.requestId || randomUUID(),
25-
userId: req.user?.velog_uuid,
25+
userId: req.user?.id,
2626
method: req.method,
2727
url: req.originalUrl || req.url,
2828
userAgent: req.headers['user-agent'],

0 commit comments

Comments
 (0)