From 0cb309f7e4c4171d90695a13452d5f78b1f19a95 Mon Sep 17 00:00:00 2001 From: Nuung Date: Sat, 19 Apr 2025 16:20:27 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feature:=20=EB=B0=B0=EC=B9=98,=20soft=20del?= =?UTF-8?q?ete=20=EA=B8=B0=EB=8A=A5=EA=B3=BC=20is=20active=20false=20?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EA=B0=80=EB=A1=9C=20=EC=9D=B4=EC=97=90=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EC=A1=B0=EA=B1=B4=EC=97=90=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/repositories/__test__/post.repo.test.ts | 80 +++++++++++++++++++++ src/repositories/post.repository.ts | 6 +- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/repositories/__test__/post.repo.test.ts b/src/repositories/__test__/post.repo.test.ts index 0bcc0a7..d3bc4e9 100644 --- a/src/repositories/__test__/post.repo.test.ts +++ b/src/repositories/__test__/post.repo.test.ts @@ -56,6 +56,28 @@ describe('PostRepository', () => { expect(result.posts).toEqual(mockPosts); expect(result.posts[0].id).toBeGreaterThan(result.posts[1].id); }); + + it('쿼리에 is_active = TRUE 조건이 포함되어야 한다', async () => { + const mockPosts = [ + { id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 }, + ]; + + mockPool.query.mockResolvedValue({ + rows: mockPosts, + rowCount: mockPosts.length, + command: '', + oid: 0, + fields: [], + } as QueryResult); + + await repo.findPostsByUserId(1); + + // 쿼리 호출 확인 + expect(mockPool.query).toHaveBeenCalledWith( + expect.stringContaining("p.is_active = TRUE"), + expect.anything() + ); + }); }); describe('findPostsByUserIdWithGrowthMetrics', () => { @@ -157,6 +179,28 @@ describe('PostRepository', () => { mockPool.query.mockRejectedValue(new Error('DB connection failed')); await expect(repo.findPostsByUserIdWithGrowthMetrics(1)).rejects.toThrow(DBError); }); + + it('쿼리에 is_active = TRUE 조건이 포함되어야 한다', async () => { + const mockPosts = [ + { id: 1, view_growth: 20, like_growth: 5 }, + ]; + + mockPool.query.mockResolvedValue({ + rows: mockPosts, + rowCount: mockPosts.length, + command: '', + oid: 0, + fields: [], + } as QueryResult); + + await repo.findPostsByUserIdWithGrowthMetrics(1); + + // 쿼리 호출 확인 + expect(mockPool.query).toHaveBeenCalledWith( + expect.stringContaining("p.is_active = TRUE"), + expect.anything() + ); + }); }); describe('getTotalPostCounts', () => { @@ -172,6 +216,24 @@ describe('PostRepository', () => { const count = await repo.getTotalPostCounts(1); expect(count).toBe(10); }); + + it('is_active = TRUE인 게시물만 카운트해야 한다', async () => { + mockPool.query.mockResolvedValue({ + rows: [{ count: '5' }], + rowCount: 1, + command: '', + oid: 0, + fields: [], + } as QueryResult); + + await repo.getTotalPostCounts(1); + + // 쿼리 호출 확인 + expect(mockPool.query).toHaveBeenCalledWith( + expect.stringContaining("is_active = TRUE"), + expect.anything() + ); + }); }); describe('에러 발생 시 처리', () => { @@ -204,6 +266,24 @@ describe('PostRepository', () => { const result = await repo.getYesterdayAndTodayViewLikeStats(1); expect(result).toEqual(mockStats); }); + + it('is_active = TRUE인 게시물의 통계만 반환해야 한다', async () => { + mockPool.query.mockResolvedValue({ + rows: [{ daily_view_count: 20, daily_like_count: 15 }], + rowCount: 1, + command: '', + oid: 0, + fields: [], + } as QueryResult); + + await repo.getYesterdayAndTodayViewLikeStats(1); + + // 쿼리 호출 확인 + expect(mockPool.query).toHaveBeenCalledWith( + expect.stringContaining("p.is_active = TRUE"), + expect.anything() + ); + }); }); describe('findPostByPostId', () => { diff --git a/src/repositories/post.repository.ts b/src/repositories/post.repository.ts index 367723d..2273766 100644 --- a/src/repositories/post.repository.ts +++ b/src/repositories/post.repository.ts @@ -78,6 +78,7 @@ export class PostRepository { WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date ) yesterday_stats ON p.id = yesterday_stats.post_id WHERE p.user_id = $1 + AND p.is_active = TRUE AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL) ${cursorCondition} ORDER BY ${orderBy} @@ -182,6 +183,7 @@ export class PostRepository { WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date ) yesterday_stats ON p.id = yesterday_stats.post_id WHERE p.user_id = $1 + AND p.is_active = TRUE AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL) ${cursorCondition} ORDER BY ${orderByExpression} @@ -213,7 +215,7 @@ export class PostRepository { async getTotalPostCounts(id: number) { try { - const query = 'SELECT COUNT(*) FROM "posts_post" WHERE user_id = $1'; + const query = 'SELECT COUNT(*) FROM "posts_post" WHERE user_id = $1 AND is_active = TRUE'; const result = await this.pool.query(query, [id]); return parseInt(result.rows[0].count, 10); } catch (error) { @@ -244,6 +246,7 @@ export class PostRepository { WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date ) yesterday_stats ON p.id = yesterday_stats.post_id WHERE p.user_id = $1 + AND p.is_active = TRUE `; const values = [userId]; @@ -304,6 +307,7 @@ export class PostRepository { FROM posts_post p JOIN posts_postdailystatistics pds ON p.id = pds.post_id WHERE p.post_uuid = $1 + AND p.is_active = TRUE AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date >= ($2 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date <= ($3 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date ORDER BY pds.date ASC From 03525a027f4a790d8429ca48f098489f720f03ce Mon Sep 17 00:00:00 2001 From: Nuung Date: Tue, 22 Apr 2025 23:56:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?modify:=20pg=EC=9D=98=20QueryResult=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=84=20=EB=A7=8C=EC=A1=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20mock=20=EA=B0=9D=EC=B2=B4=EB=A5=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=ED=97=AC?= =?UTF-8?q?=ED=8D=BC=20=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/repositories/__test__/post.repo.test.ts | 128 +++++--------------- 1 file changed, 31 insertions(+), 97 deletions(-) diff --git a/src/repositories/__test__/post.repo.test.ts b/src/repositories/__test__/post.repo.test.ts index d3bc4e9..180f4c9 100644 --- a/src/repositories/__test__/post.repo.test.ts +++ b/src/repositories/__test__/post.repo.test.ts @@ -4,6 +4,17 @@ import { DBError } from '@/exception'; jest.mock('pg'); +// pg의 QueryResult 타입을 만족하는 mock 객체를 생성하기 위한 헬퍼 함수 생성 +function createMockQueryResult>(rows: T[]): QueryResult { + return { + rows, + rowCount: rows.length, + command: '', + oid: 0, + fields: [], + } satisfies QueryResult; +} + const mockPool: { query: jest.Mock>>, unknown[]>; } = { @@ -15,6 +26,7 @@ describe('PostRepository', () => { beforeEach(() => { repo = new PostRepository(mockPool as unknown as Pool); + jest.clearAllMocks(); }); describe('findPostsByUserId', () => { @@ -24,13 +36,7 @@ describe('PostRepository', () => { { id: 2, post_released_at: '2025-03-02T00:00:00Z', daily_view_count: 20, daily_like_count: 15 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); const result = await repo.findPostsByUserId(1, undefined, 'released_at', false); @@ -44,13 +50,7 @@ describe('PostRepository', () => { { id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); const result = await repo.findPostsByUserId(1, undefined, 'released_at', false); expect(result.posts).toEqual(mockPosts); @@ -62,13 +62,7 @@ describe('PostRepository', () => { { id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); await repo.findPostsByUserId(1); @@ -101,13 +95,7 @@ describe('PostRepository', () => { }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); const result = await repo.findPostsByUserIdWithGrowthMetrics(1); @@ -135,13 +123,7 @@ describe('PostRepository', () => { }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); const result = await repo.findPostsByUserIdWithGrowthMetrics(1, undefined, false); expect(result.posts).toEqual(mockPosts); @@ -159,13 +141,7 @@ describe('PostRepository', () => { }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); const result = await repo.findPostsByUserIdWithGrowthMetrics(1, "10,2", false); expect(result.posts).toEqual(mockPosts); @@ -185,13 +161,7 @@ describe('PostRepository', () => { { id: 1, view_growth: 20, like_growth: 5 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockPosts, - rowCount: mockPosts.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts)); await repo.findPostsByUserIdWithGrowthMetrics(1); @@ -205,29 +175,17 @@ describe('PostRepository', () => { describe('getTotalPostCounts', () => { it('사용자의 총 게시글 수를 반환해야 한다', async () => { - mockPool.query.mockResolvedValue({ - rows: [{ count: '10' }], - rowCount: 1, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult([{ count: '10' }])); const count = await repo.getTotalPostCounts(1); expect(count).toBe(10); }); it('is_active = TRUE인 게시물만 카운트해야 한다', async () => { - mockPool.query.mockResolvedValue({ - rows: [{ count: '5' }], - rowCount: 1, - command: '', - oid: 0, - fields: [], - } as QueryResult); - + mockPool.query.mockResolvedValue(createMockQueryResult([{ count: '5' }])); + await repo.getTotalPostCounts(1); - + // 쿼리 호출 확인 expect(mockPool.query).toHaveBeenCalledWith( expect.stringContaining("is_active = TRUE"), @@ -255,35 +213,23 @@ describe('PostRepository', () => { last_updated_date: '2025-03-08T00:00:00Z', }; - mockPool.query.mockResolvedValue({ - rows: [mockStats], - rowCount: 1, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult([mockStats])); const result = await repo.getYesterdayAndTodayViewLikeStats(1); expect(result).toEqual(mockStats); }); it('is_active = TRUE인 게시물의 통계만 반환해야 한다', async () => { - mockPool.query.mockResolvedValue({ - rows: [{ daily_view_count: 20, daily_like_count: 15 }], - rowCount: 1, - command: '', - oid: 0, - fields: [], - } as QueryResult); - + mockPool.query.mockResolvedValue(createMockQueryResult([{ daily_view_count: 20, daily_like_count: 15 }])); + await repo.getYesterdayAndTodayViewLikeStats(1); - + // 쿼리 호출 확인 expect(mockPool.query).toHaveBeenCalledWith( expect.stringContaining("p.is_active = TRUE"), expect.anything() ); - }); + }); }); describe('findPostByPostId', () => { @@ -292,13 +238,7 @@ describe('PostRepository', () => { { date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockStats, - rowCount: mockStats.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockStats)); const result = await repo.findPostByPostId(1); expect(result).toEqual(mockStats); @@ -311,16 +251,10 @@ describe('PostRepository', () => { { date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 }, ]; - mockPool.query.mockResolvedValue({ - rows: mockStats, - rowCount: mockStats.length, - command: '', - oid: 0, - fields: [], - } as QueryResult); + mockPool.query.mockResolvedValue(createMockQueryResult(mockStats)); const result = await repo.findPostByPostUUID('uuid-1234', '2025-03-01', '2025-03-08'); expect(result).toEqual(mockStats); }); }); -}); +}); \ No newline at end of file