Skip to content

Commit bc1a377

Browse files
authored
[25.03.09 / TASK-136] Feature - post repo order test, 레포 계층 테스팅과 CI 파이프라인 (#20)
* modify: post repo 에서 query order 추가 및 post repo 계층 테스트 코드 작성 완 * feature: ci 파이프라인 구성 * feature: ci 파이프라인 구성 업데이트 * modify: ci 파이프라인 구성 업데이트 v3 * modify: 코드레빗이 actions/cache 캐시 버전 4 쓰라고 하네요
1 parent 60c8f66 commit bc1a377

File tree

5 files changed

+208
-4
lines changed

5 files changed

+208
-4
lines changed

.github/workflows/test-ci.yaml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Test CI
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: ['main']
7+
pull_request:
8+
branches: ['main']
9+
10+
jobs:
11+
build-and-test:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
node-version: [20, 21, 22, 23]
16+
fail-fast: false # 한 버전 실패 시 전체 중단 방지
17+
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Node.js ${{ matrix.node-version }}
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
27+
- name: Enable Corepack
28+
run: corepack enable
29+
30+
- name: Setup pnpm
31+
uses: pnpm/action-setup@v2
32+
with:
33+
version: 9
34+
run_install: false
35+
36+
- name: Get pnpm store directory
37+
id: pnpm-cache
38+
shell: bash
39+
run: |
40+
echo "store-path=$(pnpm store path)" >> $GITHUB_OUTPUT
41+
42+
- name: Setup pnpm cache
43+
uses: actions/cache@v4
44+
with:
45+
path: ${{ steps.pnpm-cache.outputs.store-path }}
46+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
47+
restore-keys: |
48+
${{ runner.os }}-pnpm-store-
49+
50+
- name: Install dependencies
51+
run: pnpm install --frozen-lockfile
52+
53+
- name: Run lint
54+
run: pnpm run lint
55+
56+
- name: Run tests
57+
run: pnpm run test
58+
59+
- name: Run build
60+
run: pnpm run build

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Velog Dashboard Project
22

3-
- 백엔드 API 서버
3+
- Velog dashboard V2 백엔드, API 서버
4+
- ***`node 20+`***
45

56
## Project Setup Guide
67

src/__test__/sum.test.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Pool, QueryResult } from 'pg';
2+
import { PostRepository } from '@/repositories/post.repository';
3+
import { DBError } from '@/exception';
4+
5+
jest.mock('pg');
6+
7+
const mockPool: {
8+
query: jest.Mock<Promise<QueryResult<Record<string, unknown>>>, unknown[]>;
9+
} = {
10+
query: jest.fn(),
11+
};
12+
13+
describe('PostRepository', () => {
14+
let repo: PostRepository;
15+
16+
beforeEach(() => {
17+
repo = new PostRepository(mockPool as unknown as Pool);
18+
});
19+
20+
describe('findPostsByUserId', () => {
21+
it('사용자의 게시글과 nextCursor를 반환해야 한다', async () => {
22+
const mockPosts = [
23+
{ id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 },
24+
{ id: 2, post_released_at: '2025-03-02T00:00:00Z', daily_view_count: 20, daily_like_count: 15 },
25+
];
26+
27+
mockPool.query.mockResolvedValue({
28+
rows: mockPosts,
29+
rowCount: mockPosts.length,
30+
command: '',
31+
oid: 0,
32+
fields: [],
33+
} as QueryResult);
34+
35+
const result = await repo.findPostsByUserId(1, undefined, 'released_at', false);
36+
37+
expect(result.posts).toEqual(mockPosts);
38+
expect(result).toHaveProperty('nextCursor');
39+
});
40+
41+
it('정렬 순서를 보장해야 한다', async () => {
42+
const mockPosts = [
43+
{ id: 2, post_released_at: '2025-03-02T00:00:00Z', daily_view_count: 20, daily_like_count: 15 },
44+
{ id: 1, post_released_at: '2025-03-01T00:00:00Z', daily_view_count: 10, daily_like_count: 5 },
45+
];
46+
47+
mockPool.query.mockResolvedValue({
48+
rows: mockPosts,
49+
rowCount: mockPosts.length,
50+
command: '',
51+
oid: 0,
52+
fields: [],
53+
} as QueryResult);
54+
55+
const result = await repo.findPostsByUserId(1, undefined, 'released_at', false);
56+
expect(result.posts).toEqual(mockPosts);
57+
expect(result.posts[0].id).toBeGreaterThan(result.posts[1].id);
58+
});
59+
});
60+
61+
describe('getTotalPostCounts', () => {
62+
it('사용자의 총 게시글 수를 반환해야 한다', async () => {
63+
mockPool.query.mockResolvedValue({
64+
rows: [{ count: '10' }],
65+
rowCount: 1,
66+
command: '',
67+
oid: 0,
68+
fields: [],
69+
} as QueryResult);
70+
71+
const count = await repo.getTotalPostCounts(1);
72+
expect(count).toBe(10);
73+
});
74+
});
75+
76+
describe('에러 발생 시 처리', () => {
77+
it('쿼리 실행 중 에러가 발생하면 DBError를 던져야 한다', async () => {
78+
mockPool.query.mockRejectedValue(new Error('DB connection failed'));
79+
80+
await expect(repo.findPostsByUserId(1)).rejects.toThrow(DBError);
81+
await expect(repo.getTotalPostCounts(1)).rejects.toThrow(DBError);
82+
});
83+
});
84+
85+
describe('getYesterdayAndTodayViewLikeStats', () => {
86+
it('어제와 오늘의 조회수 및 좋아요 수를 반환해야 한다', async () => {
87+
const mockStats = {
88+
daily_view_count: 20,
89+
daily_like_count: 15,
90+
yesterday_views: 10,
91+
yesterday_likes: 8,
92+
last_updated_date: '2025-03-08T00:00:00Z',
93+
};
94+
95+
mockPool.query.mockResolvedValue({
96+
rows: [mockStats],
97+
rowCount: 1,
98+
command: '',
99+
oid: 0,
100+
fields: [],
101+
} as QueryResult);
102+
103+
const result = await repo.getYesterdayAndTodayViewLikeStats(1);
104+
expect(result).toEqual(mockStats);
105+
});
106+
});
107+
108+
describe('findPostByPostId', () => {
109+
it('특정 post ID에 대한 통계를 반환해야 한다', async () => {
110+
const mockStats = [
111+
{ date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 },
112+
];
113+
114+
mockPool.query.mockResolvedValue({
115+
rows: mockStats,
116+
rowCount: mockStats.length,
117+
command: '',
118+
oid: 0,
119+
fields: [],
120+
} as QueryResult);
121+
122+
const result = await repo.findPostByPostId(1);
123+
expect(result).toEqual(mockStats);
124+
});
125+
});
126+
127+
describe('findPostByPostUUID', () => {
128+
it('특정 post UUID에 대한 통계를 반환해야 한다', async () => {
129+
const mockStats = [
130+
{ date: '2025-03-08T00:00:00Z', daily_view_count: 50, daily_like_count: 30 },
131+
];
132+
133+
mockPool.query.mockResolvedValue({
134+
rows: mockStats,
135+
rowCount: mockStats.length,
136+
command: '',
137+
oid: 0,
138+
fields: [],
139+
} as QueryResult);
140+
141+
const result = await repo.findPostByPostUUID('uuid-1234', '2025-03-01', '2025-03-08');
142+
expect(result).toEqual(mockStats);
143+
});
144+
});
145+
});

src/repositories/post.repository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export class PostRepository {
169169
pds.daily_like_count
170170
FROM posts_postdailystatistics pds
171171
WHERE pds.post_id = $1
172+
ORDER BY pds.date ASC
172173
`;
173174

174175
const values: (number | string)[] = [postId];

0 commit comments

Comments
 (0)