From 1cb8b68dd65d9d7cc48344c2b874a7c709e57898 Mon Sep 17 00:00:00 2001 From: Nuung Date: Sat, 7 Jun 2025 19:51:50 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feature:=20user=20model=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=90=A8=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20type=20=EB=93=A4=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8,=20=ED=8A=B9=ED=9E=88=20me=20api=20=EB=A6=AC=EB=89=B4?= =?UTF-8?q?=EC=96=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/user.controller.ts | 13 +++++++------ src/routes/user.router.ts | 2 +- src/types/models/User.type.ts | 3 +++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 5d02751..6be7129 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -24,9 +24,8 @@ export class UserController { if (isProd) { baseOptions.sameSite = 'lax'; - baseOptions.domain = "velog-dashboard.kro.kr"; + baseOptions.domain = 'velog-dashboard.kro.kr'; baseOptions.maxAge = THREE_WEEKS_IN_MS; // 3주 - } else { baseOptions.domain = 'localhost'; } @@ -108,14 +107,16 @@ export class UserController { }; fetchCurrentUser: RequestHandler = async (req: Request, res: Response) => { - // 외부 API (velog) 호출로 username 을 가져와야 함, 게시글 바로가기 때문에 (username) - const { accessToken, refreshToken } = req.tokens; - const velogUser = await fetchVelogApi(accessToken, refreshToken); + const currnetUser = req.user; + + // 인가 middle 에서 만든 객체 그대로 재활용 + const username = currnetUser.username || ''; + const profile = { thumbnail: currnetUser.thumbnail || '' }; const response = new LoginResponseDto( true, '유저 정보 조회에 성공하였습니다.', - { id: req.user.id, username: velogUser.username, profile: velogUser.profile }, + { id: currnetUser.id, username: username, profile: profile }, null, ); diff --git a/src/routes/user.router.ts b/src/routes/user.router.ts index 8850f66..5559fea 100644 --- a/src/routes/user.router.ts +++ b/src/routes/user.router.ts @@ -103,7 +103,7 @@ router.post('/logout', authMiddleware.verify, userController.logout); * get: * tags: * - User - * summary: 사용자 정보 조회 + * summary: 사용자 정보 조회, auth 미들웨어 객체 그대로 사용 * responses: * '200': * description: 성공 diff --git a/src/types/models/User.type.ts b/src/types/models/User.type.ts index 2b173fd..7a3640c 100644 --- a/src/types/models/User.type.ts +++ b/src/types/models/User.type.ts @@ -8,6 +8,9 @@ export interface User { is_active: boolean; created_at: Date; updated_at: Date; + // 250607 추가 + username: string | null; + thumbnail: string | null; } From 84f016c94c59e33096da8d494808beb1400bc8b1 Mon Sep 17 00:00:00 2001 From: Nuung Date: Sat, 7 Jun 2025 20:00:37 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feature:=20fixture=20=EB=A5=BC=20utils=20?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=85=EB=A6=BD,=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__test__/auth.middleware.test.ts | 9 +-- src/repositories/__test__/fixtures.ts | 57 -------------- .../__test__/leaderboard.repo.test.ts | 4 +- src/repositories/__test__/post.repo.test.ts | 2 +- src/repositories/__test__/qr.repo.test.ts | 2 +- .../__test__/totalStats.repo.test.ts | 6 +- src/services/__test__/qr.service.test.ts | 18 +---- src/utils/fixtures.ts | 77 +++++++++++++++++++ 8 files changed, 88 insertions(+), 87 deletions(-) delete mode 100644 src/repositories/__test__/fixtures.ts create mode 100644 src/utils/fixtures.ts diff --git a/src/middlewares/__test__/auth.middleware.test.ts b/src/middlewares/__test__/auth.middleware.test.ts index 98f7556..6447854 100644 --- a/src/middlewares/__test__/auth.middleware.test.ts +++ b/src/middlewares/__test__/auth.middleware.test.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { authMiddleware } from '@/middlewares/auth.middleware'; import pool from '@/configs/db.config'; +import { mockUser } from '@/utils/fixtures'; // pool.query 모킹 jest.mock('@/configs/db.config', () => ({ @@ -184,14 +185,6 @@ describe('인증 미들웨어', () => { refreshToken: 'refresh-token' }; - // 사용자 정보 mock - const mockUser = { - id: 1, - username: 'testuser', - email: 'test@example.com', - velog_uuid: 'c7507240-093b-11ea-9aae-a58a86bb0520' - }; - // DB 쿼리 결과 모킹 (pool.query as jest.Mock).mockResolvedValueOnce({ rows: [mockUser] diff --git a/src/repositories/__test__/fixtures.ts b/src/repositories/__test__/fixtures.ts deleted file mode 100644 index ddc3dd6..0000000 --- a/src/repositories/__test__/fixtures.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { QueryResult } from "pg"; - -/** -* PostgreSQL 쿼리를 모킹하기 위한 mock Pool 객체 -* -* @description Jest 테스트에서 pg.Pool의 query 메서드를 모킹하는 데 사용됩니다. -* @example -* ```typescript -* // 성공적인 쿼리 결과 모킹 -* mockPool.query.mockResolvedValue(createMockQueryResult([{ id: 1, name: 'test' }])); -* -* // 에러 발생 모킹 -* mockPool.query.mockRejectedValue(new Error('Database error')); -* ``` -*/ -export const mockPool: { - query: jest.Mock>>, unknown[]>; -} = { - query: jest.fn(), -}; - -/** -* pg의 QueryResult 타입을 만족하는 mock 객체를 생성하기 위한 헬퍼 함수 -* -* @template T - 쿼리 결과 row의 타입 (Record를 확장해야 함) -* @param rows - 모킹할 데이터베이스 행들의 배열 -* @returns PostgreSQL QueryResult 형태의 mock 객체 -* -* @description -* PostgreSQL의 실제 쿼리 결과와 동일한 구조를 가진 mock 객체를 생성합니다. -* Jest 테스트에서 데이터베이스 쿼리 결과를 모킹할 때 사용됩니다. -* -* @example -* ```typescript -* // 사용자 데이터 모킹 -* const mockUsers = [ -* { id: 1, name: 'John', email: 'john@example.com' }, -* { id: 2, name: 'Jane', email: 'jane@example.com' } -* ]; -* const result = createMockQueryResult(mockUsers); -* -* // 빈 결과 모킹 -* const emptyResult = createMockQueryResult([]); -* -* // Jest mock에서 사용 -* mockPool.query.mockResolvedValue(createMockQueryResult(mockUsers)); -* ``` -*/ -export function createMockQueryResult>(rows: T[]): QueryResult { - return { - rows, - rowCount: rows.length, - command: '', - oid: 0, - fields: [], - } satisfies QueryResult; -} \ No newline at end of file diff --git a/src/repositories/__test__/leaderboard.repo.test.ts b/src/repositories/__test__/leaderboard.repo.test.ts index cda0cbb..be3f111 100644 --- a/src/repositories/__test__/leaderboard.repo.test.ts +++ b/src/repositories/__test__/leaderboard.repo.test.ts @@ -1,8 +1,8 @@ import { Pool } from 'pg'; import { DBError } from '@/exception'; -import { LeaderboardRepository } from '@/repositories/leaderboard.repository'; import { UserLeaderboardSortType, PostLeaderboardSortType } from '@/types'; -import { mockPool, createMockQueryResult } from './fixtures'; +import { LeaderboardRepository } from '@/repositories/leaderboard.repository'; +import { mockPool, createMockQueryResult } from '@/utils/fixtures'; jest.mock('pg'); diff --git a/src/repositories/__test__/post.repo.test.ts b/src/repositories/__test__/post.repo.test.ts index ba8df20..789da80 100644 --- a/src/repositories/__test__/post.repo.test.ts +++ b/src/repositories/__test__/post.repo.test.ts @@ -1,7 +1,7 @@ import { Pool } from 'pg'; import { PostRepository } from '@/repositories/post.repository'; import { DBError } from '@/exception'; -import { mockPool, createMockQueryResult } from './fixtures'; +import { mockPool, createMockQueryResult } from '@/utils/fixtures'; jest.mock('pg'); diff --git a/src/repositories/__test__/qr.repo.test.ts b/src/repositories/__test__/qr.repo.test.ts index a628955..e3e5c70 100644 --- a/src/repositories/__test__/qr.repo.test.ts +++ b/src/repositories/__test__/qr.repo.test.ts @@ -2,7 +2,7 @@ import { UserRepository } from '@/repositories/user.repository'; import { DBError } from '@/exception'; import { Pool } from 'pg'; import { QRLoginToken } from "@/types/models/QRLoginToken.type"; -import { mockPool } from './fixtures'; +import { mockPool } from '@/utils/fixtures'; jest.mock('pg'); diff --git a/src/repositories/__test__/totalStats.repo.test.ts b/src/repositories/__test__/totalStats.repo.test.ts index ba9f5a1..c9467a6 100644 --- a/src/repositories/__test__/totalStats.repo.test.ts +++ b/src/repositories/__test__/totalStats.repo.test.ts @@ -1,9 +1,9 @@ import { Pool } from 'pg'; -import { TotalStatsRepository } from '@/repositories/totalStats.repository'; import { DBError } from '@/exception'; -import { getKSTDateStringWithOffset } from '@/utils/date.util'; -import { mockPool, createMockQueryResult } from './fixtures'; import { TotalStatsType } from '@/types'; +import { TotalStatsRepository } from '@/repositories/totalStats.repository'; +import { getKSTDateStringWithOffset } from '@/utils/date.util'; +import { mockPool, createMockQueryResult } from '@/utils/fixtures'; // Mock dependencies jest.mock('@/configs/logger.config', () => ({ diff --git a/src/services/__test__/qr.service.test.ts b/src/services/__test__/qr.service.test.ts index ac2a81b..2490b65 100644 --- a/src/services/__test__/qr.service.test.ts +++ b/src/services/__test__/qr.service.test.ts @@ -1,9 +1,9 @@ +import { Pool } from 'pg'; +import { DBError } from '@/exception'; import { UserService } from '@/services/user.service'; import { UserRepository } from '@/repositories/user.repository'; -import { DBError } from '@/exception'; import { QRLoginToken } from '@/types/models/QRLoginToken.type'; -import { User } from '@/types'; -import { Pool } from 'pg'; +import { mockUser } from '@/utils/fixtures'; // AESEncryption 클래스 모킹 jest.mock('@/modules/token_encryption/aes_encryption', () => { @@ -68,18 +68,6 @@ describe('UserService의 QR 로그인 기능', () => { }); describe('useToken', () => { - const mockUser: User = { - id: 1, - velog_uuid: 'uuid-1', - access_token: 'encrypted-access-token', - refresh_token: 'encrypted-refresh-token', - email: 'test@example.com', - group_id: 1, - is_active: true, - created_at: new Date(), - updated_at: new Date() - }; - const mockQRToken: QRLoginToken = { id: 1, token: 'token', diff --git a/src/utils/fixtures.ts b/src/utils/fixtures.ts new file mode 100644 index 0000000..6a03cfb --- /dev/null +++ b/src/utils/fixtures.ts @@ -0,0 +1,77 @@ +import { QueryResult } from 'pg'; +import { User } from '@/types'; + +/** + * PostgreSQL 쿼리를 모킹하기 위한 mock Pool 객체 + * + * @description Jest 테스트에서 pg.Pool의 query 메서드를 모킹하는 데 사용됩니다. + * @example + * ```typescript + * // 성공적인 쿼리 결과 모킹 + * mockPool.query.mockResolvedValue(createMockQueryResult([{ id: 1, name: 'test' }])); + * + * // 에러 발생 모킹 + * mockPool.query.mockRejectedValue(new Error('Database error')); + * ``` + */ +export const mockPool: { + query: jest.Mock>>, unknown[]>; +} = { + query: jest.fn(), +}; + +/** + * 테스트용 모의 사용자 데이터, User 객체 + * + * @description 인증 관련 (미들웨어, QR 로그인 등) 유닛 테스트에서 공통적으로 사용되는 mock User 객체입니다. + */ +export const mockUser: User = { + id: 1, + velog_uuid: 'uuid-1', + access_token: 'encrypted-access-token', + refresh_token: 'encrypted-refresh-token', + email: 'test@example.com', + username: 'nuung', + thumbnail: 'https://nuung.com/test.jpg', + group_id: 1, + is_active: true, + created_at: new Date('2024-01-01T00:00:00Z'), + updated_at: new Date('2024-01-01T00:00:00Z'), +}; + +/** + * pg의 QueryResult 타입을 만족하는 mock 객체를 생성하기 위한 헬퍼 함수 + * + * @template T - 쿼리 결과 row의 타입 (Record를 확장해야 함) + * @param rows - 모킹할 데이터베이스 행들의 배열 + * @returns PostgreSQL QueryResult 형태의 mock 객체 + * + * @description + * PostgreSQL의 실제 쿼리 결과와 동일한 구조를 가진 mock 객체를 생성합니다. + * Jest 테스트에서 데이터베이스 쿼리 결과를 모킹할 때 사용됩니다. + * + * @example + * ```typescript + * // 사용자 데이터 모킹 + * const mockUsers = [ + * { id: 1, name: 'John', email: 'john@example.com' }, + * { id: 2, name: 'Jane', email: 'jane@example.com' } + * ]; + * const result = createMockQueryResult(mockUsers); + * + * // 빈 결과 모킹 + * const emptyResult = createMockQueryResult([]); + * + * // Jest mock에서 사용 + * mockPool.query.mockResolvedValue(createMockQueryResult(mockUsers)); + * ``` + */ +export function createMockQueryResult>(rows: T[]): QueryResult { + return { + rows, + rowCount: rows.length, + command: '', + oid: 0, + fields: [], + } satisfies QueryResult; +} From e043b6a6633673fd6ac29696684b3c6bb6851951 Mon Sep 17 00:00:00 2001 From: Nuung Date: Sat, 7 Jun 2025 20:09:15 +0900 Subject: [PATCH 3/3] =?UTF-8?q?modify:=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/user.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index 6be7129..4ee2dd9 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -107,16 +107,16 @@ export class UserController { }; fetchCurrentUser: RequestHandler = async (req: Request, res: Response) => { - const currnetUser = req.user; + const currentUser = req.user; // 인가 middle 에서 만든 객체 그대로 재활용 - const username = currnetUser.username || ''; - const profile = { thumbnail: currnetUser.thumbnail || '' }; + const username = currentUser.username || ''; + const profile = { thumbnail: currentUser.thumbnail || '' }; const response = new LoginResponseDto( true, '유저 정보 조회에 성공하였습니다.', - { id: currnetUser.id, username: username, profile: profile }, + { id: currentUser.id, username: username, profile: profile }, null, );