Skip to content

Commit 48120dd

Browse files
authored
Merge pull request boostcampwm-2024#507 from boostcampwm-2024/test/email-unit-test
✅ test: Email Worker Unit 테스트 작성
2 parents 487bcb2 + 1fb0669 commit 48120dd

File tree

8 files changed

+1287
-10
lines changed

8 files changed

+1287
-10
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: EMAIL-WORKER Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- "email-worker/**"
9+
- ".github/workflows/test_email-worker.yml"
10+
workflow_dispatch:
11+
12+
jobs:
13+
tests:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
# 1. 현재 PR 브랜치로 체크아웃 하기
18+
- name: Checkout code
19+
uses: actions/checkout@v3
20+
with:
21+
ref: ${{ github.event.pull_request.head.ref }}
22+
23+
# 2. Node.js 환경 설정
24+
- name: Set up Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: "22"
28+
cache: "npm"
29+
30+
# 3. 의존성 설치
31+
- name: Install dependencies
32+
working-directory: ./email-worker
33+
run: npm ci
34+
35+
# 4. Unit 테스트 실행
36+
- name: Run Unit Test
37+
working-directory: ./email-worker
38+
run: npm run test:unit
39+
40+
# TODO: E2E 테스트 실행

CLAUDE.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**Denamu (데나무)** is an RSS-based tech blog curation platform. It aggregates developer blog content from multiple platforms (Tistory, Velog, Medium) into a single service with real-time trending, search, and developer chat features.
8+
9+
- **Production URL**: https://denamu.dev
10+
- **Architecture**: Monorepo with microservices (Docker Compose orchestration)
11+
12+
## Repository Structure
13+
14+
```
15+
server/ # NestJS backend API (port 8080)
16+
client/ # React + Vite frontend (port 5173)
17+
feed-crawler/ # RSS feed crawler with AI tagging
18+
email-worker/ # Email processing via RabbitMQ
19+
docker-compose/ # Infrastructure configs (local, dev, prod)
20+
nginx/ # Reverse proxy configuration
21+
```
22+
23+
## Common Commands
24+
25+
### Root Level
26+
```bash
27+
npm run start:local # Start all services locally (no watch)
28+
npm run start:dev # Start dev environment with hot reload
29+
npm run start:was-dev # Start backend services only
30+
npm run commit # Commitizen for structured commits
31+
```
32+
33+
### Server (NestJS)
34+
```bash
35+
cd server
36+
npm run start:dev # Development with watch mode
37+
npm run build # Production build
38+
npm run lint # ESLint
39+
npm run test:unit # Unit tests
40+
npm run test:e2e # E2E tests (uses Testcontainers)
41+
npm run test:dto # DTO validation tests
42+
npm run migration:create # Create DB migrations
43+
```
44+
45+
### Client (React)
46+
```bash
47+
cd client
48+
npm run dev # Development server
49+
npm run build # Production build
50+
npm run lint # ESLint
51+
npm run test # Vitest unit tests
52+
npm run test:coverage # Coverage report
53+
```
54+
55+
### Feed Crawler
56+
```bash
57+
cd feed-crawler
58+
npm run start:dev # Development mode
59+
npm run test:unit # Unit tests
60+
npm run test:e2e # Integration tests
61+
```
62+
63+
## Tech Stack
64+
65+
- **Backend**: NestJS, TypeORM, MySQL 8.0, Redis 7.2, RabbitMQ 3.13
66+
- **Frontend**: React 18, TypeScript, Vite, TanStack Query, Zustand, Tailwind CSS, Radix UI
67+
- **Real-time**: Socket.IO (WebSocket chat)
68+
- **AI**: Anthropic Claude SDK (automatic content tagging in feed-crawler)
69+
- **Monitoring**: Prometheus, Grafana, Winston logging
70+
- **Auth**: JWT + OAuth (Google, GitHub) via Passport.js
71+
72+
## Architecture Notes
73+
74+
### Backend (Server)
75+
- Module-based NestJS architecture: each feature has Module, Controller, Service
76+
- TypeORM with MySQL for data persistence
77+
- Redis for caching and session management
78+
- Global exception filters and interceptors for consistent error handling
79+
- Swagger API documentation available
80+
81+
### Frontend (Client)
82+
- TanStack Query for server state, Zustand for client state
83+
- Custom hooks pattern for logic reuse
84+
- Socket.IO client for real-time chat
85+
86+
### Feed Crawler
87+
- tsyringe for dependency injection (not NestJS)
88+
- Scheduled jobs via node-schedule for RSS polling
89+
- Claude AI integration for automatic tag generation
90+
- RabbitMQ for event publishing
91+
92+
### Email Worker
93+
- RabbitMQ consumer for async email processing
94+
- Nodemailer for email delivery
95+
96+
## Code Review Convention
97+
98+
Uses Pn-level system (Korean language reviews):
99+
- **P1**: Critical - security issues, business logic errors (must fix before deploy)
100+
- **P2**: Important - code quality/functionality issues (must fix)
101+
- **P3**: Medium - potential bug risks, improvements (strongly consider)
102+
- **P4**: Light - readability suggestions (optional)
103+
- **P5**: Questions - optional suggestions
104+
105+
## Environment Variables
106+
107+
Key environment variable groups (see `.env.example` files):
108+
- Database: `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`
109+
- Redis: `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`
110+
- RabbitMQ: `RABBITMQ_HOST`, `RABBITMQ_PORT`, `RABBITMQ_USER`, `RABBITMQ_PASSWORD`
111+
- JWT: `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`
112+
- OAuth: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`
113+
114+
## CI/CD
115+
116+
GitHub Actions workflows deploy to self-hosted runner via GHCR:
117+
- `deploy_server.yml` - NestJS backend
118+
- `deploy_client.yml` - React frontend
119+
- `deploy_feed-crawler.yml` - Crawler service
120+
- `deploy_email-worker.yml` - Email worker
121+
- Test workflows run on PR: `test_server_dto.yml`, `test_server_e2e.yml`, `test_feed-crawler.yml`

email-worker/test/definitions.json

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"vhosts": [
3+
{
4+
"name": "/"
5+
}
6+
],
7+
"permissions": [
8+
{
9+
"user": "guest",
10+
"vhost": "/",
11+
"configure": ".*",
12+
"write": ".*",
13+
"read": ".*"
14+
}
15+
],
16+
"exchanges": [
17+
{
18+
"name": "EmailExchange",
19+
"vhost": "/",
20+
"type": "direct",
21+
"durable": true
22+
},
23+
{
24+
"name": "CrawlingExchange",
25+
"vhost": "/",
26+
"type": "topic",
27+
"durable": true
28+
},
29+
{
30+
"name": "DeadLetterExchange",
31+
"vhost": "/",
32+
"type": "topic",
33+
"durable": true
34+
}
35+
],
36+
"queues": [
37+
{
38+
"name": "email.send.queue",
39+
"vhost": "/",
40+
"durable": true,
41+
"arguments": {
42+
"x-dead-letter-exchange": "DeadLetterExchange",
43+
"x-dead-letter-routing-key": "email.deadLetter"
44+
}
45+
},
46+
{
47+
"name": "email.send.wait.5s",
48+
"vhost": "/",
49+
"durable": true,
50+
"arguments": {
51+
"x-message-ttl": 5000,
52+
"x-dead-letter-exchange": "",
53+
"x-dead-letter-routing-key": "email.send.queue"
54+
}
55+
},
56+
{
57+
"name": "email.send.wait.10s",
58+
"vhost": "/",
59+
"durable": true,
60+
"arguments": {
61+
"x-message-ttl": 10000,
62+
"x-dead-letter-exchange": "",
63+
"x-dead-letter-routing-key": "email.send.queue"
64+
}
65+
},
66+
{
67+
"name": "email.send.wait.20s",
68+
"vhost": "/",
69+
"durable": true,
70+
"arguments": {
71+
"x-message-ttl": 20000,
72+
"x-dead-letter-exchange": "",
73+
"x-dead-letter-routing-key": "email.send.queue"
74+
}
75+
},
76+
{
77+
"name": "crawling.full.queue",
78+
"vhost": "/",
79+
"durable": true,
80+
"arguments": {
81+
"x-dead-letter-exchange": "DeadLetterExchange",
82+
"x-dead-letter-routing-key": "crawling.full.deadLetter"
83+
}
84+
},
85+
{
86+
"name": "email.deadLetter.queue",
87+
"vhost": "/",
88+
"durable": true,
89+
"arguments": {}
90+
},
91+
{
92+
"name": "crawling.full.deadLetter.queue",
93+
"vhost": "/",
94+
"durable": true,
95+
"arguments": {}
96+
}
97+
],
98+
"bindings": [
99+
{
100+
"source": "EmailExchange",
101+
"vhost": "/",
102+
"destination": "email.send.queue",
103+
"destination_type": "queue",
104+
"routing_key": "email.send",
105+
"arguments": {}
106+
},
107+
{
108+
"source": "CrawlingExchange",
109+
"vhost": "/",
110+
"destination": "crawling.full.queue",
111+
"destination_type": "queue",
112+
"routing_key": "crawling.full",
113+
"arguments": {}
114+
},
115+
{
116+
"source": "DeadLetterExchange",
117+
"vhost": "/",
118+
"destination": "email.deadLetter.queue",
119+
"destination_type": "queue",
120+
"routing_key": "email.deadLetter",
121+
"arguments": {}
122+
},
123+
{
124+
"source": "DeadLetterExchange",
125+
"vhost": "/",
126+
"destination": "crawling.full.deadLetter.queue",
127+
"destination_type": "queue",
128+
"routing_key": "crawling.full.deadLetter",
129+
"arguments": {}
130+
}
131+
]
132+
}

email-worker/test/jest-unit.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"moduleFileExtensions": ["js", "json", "ts"],
3+
"testRegex": "test/.*\\.(spec).ts$",
4+
"transform": {
5+
"^.+\\.(t|j)s$": "ts-jest"
6+
},
7+
"testEnvironment": "node",
8+
"rootDir": "..",
9+
"coverageDirectory": "test/coverage",
10+
"testTimeout": 10000
11+
}

0 commit comments

Comments
 (0)