Skip to content

Commit 62a2b6b

Browse files
authored
[25.04.19 / TASK-157] Feature: 배치, soft delete 기능과 is active false 의 추가로 이에 모든 조건에 해당 조건 추가 (#25)
* feature: 배치, soft delete 기능과 is active false 의 추가로 이에 모든 조건에 해당 조건 추가 * modify: pg의 QueryResult 타입을 만족하는 mock 객체를 생성하기 위한 헬퍼 함수 생성
1 parent 5f14e86 commit 62a2b6b

File tree

2 files changed

+83
-65
lines changed

2 files changed

+83
-65
lines changed

src/repositories/__test__/post.repo.test.ts

Lines changed: 78 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { DBError } from '@/exception';
44

55
jest.mock('pg');
66

7+
// pg의 QueryResult 타입을 만족하는 mock 객체를 생성하기 위한 헬퍼 함수 생성
8+
function createMockQueryResult<T extends Record<string, unknown>>(rows: T[]): QueryResult<T> {
9+
return {
10+
rows,
11+
rowCount: rows.length,
12+
command: '',
13+
oid: 0,
14+
fields: [],
15+
} satisfies QueryResult<T>;
16+
}
17+
718
const mockPool: {
819
query: jest.Mock<Promise<QueryResult<Record<string, unknown>>>, unknown[]>;
920
} = {
@@ -15,6 +26,7 @@ describe('PostRepository', () => {
1526

1627
beforeEach(() => {
1728
repo = new PostRepository(mockPool as unknown as Pool);
29+
jest.clearAllMocks();
1830
});
1931

2032
describe('findPostsByUserId', () => {
@@ -24,13 +36,7 @@ describe('PostRepository', () => {
2436
{ id: 2, post_released_at: '2025-03-02T00:00:00Z', daily_view_count: 20, daily_like_count: 15 },
2537
];
2638

27-
mockPool.query.mockResolvedValue({
28-
rows: mockPosts,
29-
rowCount: mockPosts.length,
30-
command: '',
31-
oid: 0,
32-
fields: [],
33-
} as QueryResult);
39+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
3440

3541
const result = await repo.findPostsByUserId(1, undefined, 'released_at', false);
3642

@@ -44,18 +50,28 @@ describe('PostRepository', () => {
4450
{ id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 },
4551
];
4652

47-
mockPool.query.mockResolvedValue({
48-
rows: mockPosts,
49-
rowCount: mockPosts.length,
50-
command: '',
51-
oid: 0,
52-
fields: [],
53-
} as QueryResult);
53+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
5454

5555
const result = await repo.findPostsByUserId(1, undefined, 'released_at', false);
5656
expect(result.posts).toEqual(mockPosts);
5757
expect(result.posts[0].id).toBeGreaterThan(result.posts[1].id);
5858
});
59+
60+
it('쿼리에 is_active = TRUE 조건이 포함되어야 한다', async () => {
61+
const mockPosts = [
62+
{ id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 },
63+
];
64+
65+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
66+
67+
await repo.findPostsByUserId(1);
68+
69+
// 쿼리 호출 확인
70+
expect(mockPool.query).toHaveBeenCalledWith(
71+
expect.stringContaining("p.is_active = TRUE"),
72+
expect.anything()
73+
);
74+
});
5975
});
6076

6177
describe('findPostsByUserIdWithGrowthMetrics', () => {
@@ -79,13 +95,7 @@ describe('PostRepository', () => {
7995
},
8096
];
8197

82-
mockPool.query.mockResolvedValue({
83-
rows: mockPosts,
84-
rowCount: mockPosts.length,
85-
command: '',
86-
oid: 0,
87-
fields: [],
88-
} as QueryResult);
98+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
8999

90100
const result = await repo.findPostsByUserIdWithGrowthMetrics(1);
91101

@@ -113,13 +123,7 @@ describe('PostRepository', () => {
113123
},
114124
];
115125

116-
mockPool.query.mockResolvedValue({
117-
rows: mockPosts,
118-
rowCount: mockPosts.length,
119-
command: '',
120-
oid: 0,
121-
fields: [],
122-
} as QueryResult);
126+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
123127

124128
const result = await repo.findPostsByUserIdWithGrowthMetrics(1, undefined, false);
125129
expect(result.posts).toEqual(mockPosts);
@@ -137,13 +141,7 @@ describe('PostRepository', () => {
137141
},
138142
];
139143

140-
mockPool.query.mockResolvedValue({
141-
rows: mockPosts,
142-
rowCount: mockPosts.length,
143-
command: '',
144-
oid: 0,
145-
fields: [],
146-
} as QueryResult);
144+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
147145

148146
const result = await repo.findPostsByUserIdWithGrowthMetrics(1, "10,2", false);
149147
expect(result.posts).toEqual(mockPosts);
@@ -157,21 +155,43 @@ describe('PostRepository', () => {
157155
mockPool.query.mockRejectedValue(new Error('DB connection failed'));
158156
await expect(repo.findPostsByUserIdWithGrowthMetrics(1)).rejects.toThrow(DBError);
159157
});
158+
159+
it('쿼리에 is_active = TRUE 조건이 포함되어야 한다', async () => {
160+
const mockPosts = [
161+
{ id: 1, view_growth: 20, like_growth: 5 },
162+
];
163+
164+
mockPool.query.mockResolvedValue(createMockQueryResult(mockPosts));
165+
166+
await repo.findPostsByUserIdWithGrowthMetrics(1);
167+
168+
// 쿼리 호출 확인
169+
expect(mockPool.query).toHaveBeenCalledWith(
170+
expect.stringContaining("p.is_active = TRUE"),
171+
expect.anything()
172+
);
173+
});
160174
});
161175

162176
describe('getTotalPostCounts', () => {
163177
it('사용자의 총 게시글 수를 반환해야 한다', async () => {
164-
mockPool.query.mockResolvedValue({
165-
rows: [{ count: '10' }],
166-
rowCount: 1,
167-
command: '',
168-
oid: 0,
169-
fields: [],
170-
} as QueryResult);
178+
mockPool.query.mockResolvedValue(createMockQueryResult([{ count: '10' }]));
171179

172180
const count = await repo.getTotalPostCounts(1);
173181
expect(count).toBe(10);
174182
});
183+
184+
it('is_active = TRUE인 게시물만 카운트해야 한다', async () => {
185+
mockPool.query.mockResolvedValue(createMockQueryResult([{ count: '5' }]));
186+
187+
await repo.getTotalPostCounts(1);
188+
189+
// 쿼리 호출 확인
190+
expect(mockPool.query).toHaveBeenCalledWith(
191+
expect.stringContaining("is_active = TRUE"),
192+
expect.anything()
193+
);
194+
});
175195
});
176196

177197
describe('에러 발생 시 처리', () => {
@@ -193,17 +213,23 @@ describe('PostRepository', () => {
193213
last_updated_date: '2025-03-08T00:00:00Z',
194214
};
195215

196-
mockPool.query.mockResolvedValue({
197-
rows: [mockStats],
198-
rowCount: 1,
199-
command: '',
200-
oid: 0,
201-
fields: [],
202-
} as QueryResult);
216+
mockPool.query.mockResolvedValue(createMockQueryResult([mockStats]));
203217

204218
const result = await repo.getYesterdayAndTodayViewLikeStats(1);
205219
expect(result).toEqual(mockStats);
206220
});
221+
222+
it('is_active = TRUE인 게시물의 통계만 반환해야 한다', async () => {
223+
mockPool.query.mockResolvedValue(createMockQueryResult([{ daily_view_count: 20, daily_like_count: 15 }]));
224+
225+
await repo.getYesterdayAndTodayViewLikeStats(1);
226+
227+
// 쿼리 호출 확인
228+
expect(mockPool.query).toHaveBeenCalledWith(
229+
expect.stringContaining("p.is_active = TRUE"),
230+
expect.anything()
231+
);
232+
});
207233
});
208234

209235
describe('findPostByPostId', () => {
@@ -212,13 +238,7 @@ describe('PostRepository', () => {
212238
{ date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 },
213239
];
214240

215-
mockPool.query.mockResolvedValue({
216-
rows: mockStats,
217-
rowCount: mockStats.length,
218-
command: '',
219-
oid: 0,
220-
fields: [],
221-
} as QueryResult);
241+
mockPool.query.mockResolvedValue(createMockQueryResult(mockStats));
222242

223243
const result = await repo.findPostByPostId(1);
224244
expect(result).toEqual(mockStats);
@@ -231,16 +251,10 @@ describe('PostRepository', () => {
231251
{ date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 },
232252
];
233253

234-
mockPool.query.mockResolvedValue({
235-
rows: mockStats,
236-
rowCount: mockStats.length,
237-
command: '',
238-
oid: 0,
239-
fields: [],
240-
} as QueryResult);
254+
mockPool.query.mockResolvedValue(createMockQueryResult(mockStats));
241255

242256
const result = await repo.findPostByPostUUID('uuid-1234', '2025-03-01', '2025-03-08');
243257
expect(result).toEqual(mockStats);
244258
});
245259
});
246-
});
260+
});

src/repositories/post.repository.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class PostRepository {
7878
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
7979
) yesterday_stats ON p.id = yesterday_stats.post_id
8080
WHERE p.user_id = $1
81+
AND p.is_active = TRUE
8182
AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL)
8283
${cursorCondition}
8384
ORDER BY ${orderBy}
@@ -182,6 +183,7 @@ export class PostRepository {
182183
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
183184
) yesterday_stats ON p.id = yesterday_stats.post_id
184185
WHERE p.user_id = $1
186+
AND p.is_active = TRUE
185187
AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL)
186188
${cursorCondition}
187189
ORDER BY ${orderByExpression}
@@ -213,7 +215,7 @@ export class PostRepository {
213215

214216
async getTotalPostCounts(id: number) {
215217
try {
216-
const query = 'SELECT COUNT(*) FROM "posts_post" WHERE user_id = $1';
218+
const query = 'SELECT COUNT(*) FROM "posts_post" WHERE user_id = $1 AND is_active = TRUE';
217219
const result = await this.pool.query(query, [id]);
218220
return parseInt(result.rows[0].count, 10);
219221
} catch (error) {
@@ -244,6 +246,7 @@ export class PostRepository {
244246
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
245247
) yesterday_stats ON p.id = yesterday_stats.post_id
246248
WHERE p.user_id = $1
249+
AND p.is_active = TRUE
247250
`;
248251
const values = [userId];
249252

@@ -304,6 +307,7 @@ export class PostRepository {
304307
FROM posts_post p
305308
JOIN posts_postdailystatistics pds ON p.id = pds.post_id
306309
WHERE p.post_uuid = $1
310+
AND p.is_active = TRUE
307311
AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date >= ($2 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date
308312
AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date <= ($3 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date
309313
ORDER BY pds.date ASC

0 commit comments

Comments
 (0)