Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
DATABASE_URL=postgres://user:password@host:5432/schema
S3_BUCKET=stdev-kr
AWS_REGION=ap-northeast-2
AWS_ACCESS_KEY=example
AWS_SECRET_KEY=example
BETTER_AUTH_SECRET=replace-with-generated-secret
BETTER_AUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=example.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=example
AWS_ACCESS_KEY=test-access-key
AWS_SECRET_KEY=test-secret-key
BETTER_AUTH_SECRET=test-better-auth-secret-32-chars-min
BETTER_AUTH_URL=http://127.0.0.1:3100
GOOGLE_CLIENT_ID=test-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=test-google-client-secret

NEXT_PUBLIC_CHANNEL_PLUGIN_KEY=example
NEXT_PUBLIC_GTM_ID=GTM-example
NEXT_PUBLIC_GA_ID=G-example
NEXT_PUBLIC_CHANNEL_PLUGIN_KEY=test-channel-key
NEXT_PUBLIC_GTM_ID=GTM-TEST
NEXT_PUBLIC_GA_ID=G-TEST
24 changes: 24 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# E2E test env loaded by Playwright, prepare script, and seed script.
# Values must match docker-compose.test.yml (postgres + minio services).
# Safe to commit — everything here is a dummy credential.

DATABASE_URL=postgresql://stdev:stdev@127.0.0.1:5433/stdev_test

BETTER_AUTH_SECRET=test-better-auth-secret-32-chars-min-abcd
BETTER_AUTH_URL=http://127.0.0.1:3100

GOOGLE_CLIENT_ID=test-google-client-id
GOOGLE_CLIENT_SECRET=test-google-client-secret

NEXT_PUBLIC_CHANNEL_PLUGIN_KEY=test-channel-key
NEXT_PUBLIC_GTM_ID=GTM-TEST
NEXT_PUBLIC_GA_ID=G-TEST

AWS_REGION=ap-northeast-2
AWS_ACCESS_KEY=test-access-key
AWS_SECRET_KEY=test-secret-key
S3_BUCKET=stdev-kr

# Point @aws-sdk/client-s3 at the MinIO sidecar from docker-compose.test.yml.
# In production this var is unset, so S3Client uses the real AWS endpoint.
AWS_ENDPOINT_URL_S3=http://127.0.0.1:9000
106 changes: 101 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ jobs:
name: Install pnpm
with:
run_install: false
- name: Install Node.js ${{ env.NODE_VERSION }}
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'

- name: Copy example env file
run: cp .env.example .env
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Copy test env for Next.js build
run: cp .env.test .env
- name: Build the project
run: pnpm build

Expand All @@ -43,13 +43,109 @@ jobs:
name: Install pnpm
with:
run_install: false
- name: Install Node.js ${{ env.NODE_VERSION }}
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint the project
run: pnpm lint

test:
name: Unit + Component + Integration (mocked) Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate Prisma client
run: pnpm db:generate
- name: Run Vitest with coverage
run: pnpm test:ci
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-${{ github.sha }}
path: coverage/
retention-days: 7
if-no-files-found: warn

e2e:
name: Playwright E2E Tests
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright browser
run: pnpm test:e2e:install
- name: Copy test env for Next.js build
run: cp .env.test .env
- name: Run Playwright with dummy DB
run: pnpm test:e2e
- name: Stop dummy DB
if: always()
run: docker compose -f docker-compose.test.yml down
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ github.sha }}
path: playwright-report/
retention-days: 7
if-no-files-found: ignore
- name: Upload E2E test results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-test-results-${{ github.sha }}
path: test-results/e2e*
retention-days: 7
if-no-files-found: ignore

coverage:
name: Upload coverage to Codecov
runs-on: ubuntu-latest
needs: [test]
if: always() && needs.test.result == 'success'
steps:
- uses: actions/checkout@v4
- name: Download coverage artifact
uses: actions/download-artifact@v4
with:
name: coverage-${{ github.sha }}
path: coverage/
- name: Upload to Codecov
uses: codecov/codecov-action@v5
with:
files: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

# testing
/coverage
/coverage-db
/playwright-report
/test-results
/.vitest-cache
/playwright/.cache

# next.js
/.next/
Expand All @@ -32,6 +37,7 @@ yarn-error.log*
# env files (can opt-in for commiting if needed)
.env*
!.env.example
!.env.test

# vercel
.vercel
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,64 @@ pnpm dev

브라우저에서 [http://localhost:3000](http://localhost:3000)로 이동합니다.

## Test

테스트는 목적에 따라 단위/컴포넌트 테스트, 커버리지 테스트, E2E 테스트로 나누어 실행합니다.

### 단위/컴포넌트/통합 테스트

브라우저나 실제 DB 연결이 필요 없는 Vitest 테스트를 실행합니다. 컴포넌트 테스트는 React Testing Library와 jsdom 환경에서 실행됩니다.

```bash
pnpm test
```

개발 중에는 watch mode를 사용할 수 있습니다.

```bash
pnpm test:watch
```

### 커버리지 확인

전체 Vitest 테스트를 실행하고 V8 coverage 리포트를 생성합니다. 프로젝트 커버리지 기준은 95% 이상입니다.

```bash
pnpm test:coverage
```

CI에서 사용하는 JUnit 리포트까지 함께 생성하려면 아래 명령을 실행합니다.

```bash
pnpm test:ci
```

### E2E 테스트

Playwright E2E 테스트는 실제 브라우저, Next.js 서버, 테스트용 Postgres DB, MinIO (S3 mock)가 필요합니다. 처음 실행하는 환경에서는 Chromium 브라우저를 먼저 설치합니다.

```bash
pnpm test:e2e:install
```

그 다음 E2E 테스트를 실행합니다. 이 명령은 레포에 커밋된 `.env.test`를 사용하고, `docker-compose.test.yml`의 Postgres와 MinIO를 띄운 뒤 Prisma migration과 dummy data seed를 적용한 다음 Playwright를 실행합니다. `.env.test`에 `AWS_ENDPOINT_URL_S3=http://127.0.0.1:9000`이 설정돼 있어 S3Client는 실제 AWS가 아닌 로컬 MinIO로 붙습니다.

```bash
pnpm test:e2e
```

UI 모드로 테스트를 확인하려면 아래 명령을 사용합니다.

```bash
pnpm test:e2e:ui
```

### 테스트 파일 위치

- 단위/컴포넌트 테스트: 대상 파일 옆의 `*.test.ts` 또는 `*.test.tsx`
- 통합 테스트: `src/tests/**`
- E2E 테스트: `src/e2e/**`

## How to deploy

Github 레포지토리 설정에서 `Actions secrets and variables` 페이지로 이동한 후 `Repository secrets`에 아래 값을 입력합니다.
Expand Down
58 changes: 58 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
coverage:
precision: 2
round: down
range: 90..100
status:
project:
default:
target: 95%
threshold: 1%
if_ci_failed: error
only_pulls: false
branches:
- main
patch:
default:
target: 90%
threshold: 1%
if_ci_failed: error

comment:
layout: 'reach,diff,flags,files,footer'
behavior: default
require_changes: false

flags:
unittests:
paths:
- src/

ignore:
- '.next/**/*'
- 'prisma/migrations/**/*'
- 'prisma.config.ts'
- 'next.config.ts'
- 'src/scripts/**/*'
- 'src/generated/**/*'
- 'src/**/*.d.ts'
- 'src/tests/**/*'
- 'src/e2e/**/*'
- 'src/app/**/loading.tsx'
- 'src/app/**/not-found.tsx'
- 'src/app/**/forbidden.tsx'
- 'src/app/**/unauthorized.tsx'
- 'src/app/**/layout.tsx'
- 'src/app/**/providers.tsx'
- 'src/app/api/auth/**/*'
- 'src/app/(stdev)/sitemap.ts'
- 'playwright.config.ts'
- 'vitest.config.ts'
- 'vitest.config.integration.ts'
- 'eslint.config.mjs'
- 'prettier.config.mjs'
- 'public/service-worker.js'

codecov:
require_ci_to_pass: true
notify:
wait_for_ci: true
42 changes: 42 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
postgres:
image: postgres:17-alpine
container_name: stdev-test-postgres
environment:
POSTGRES_USER: stdev
POSTGRES_PASSWORD: stdev
POSTGRES_DB: stdev_test
ports:
- '5433:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U stdev -d stdev_test']
interval: 3s
timeout: 5s
retries: 10
tmpfs:
- /var/lib/postgresql/data

# MinIO acts as a local S3-compatible endpoint for Playwright E2E tests so
# `@aws-sdk/client-s3` calls from the Next.js server never touch real AWS.
# The entrypoint is overridden to pre-create the `stdev-kr` bucket as a
# top-level directory (MinIO single-drive mode treats those as buckets)
# before execing the server, so no extra init sidecar is needed.
minio:
image: minio/minio:latest
container_name: stdev-test-minio
environment:
MINIO_ROOT_USER: test-access-key
MINIO_ROOT_PASSWORD: test-secret-key
ports:
- '9000:9000'
- '9001:9001'
entrypoint: sh
command: -c 'mkdir -p /data/stdev-kr && exec minio server /data --console-address ":9001"'
healthcheck:
test: ['CMD', 'curl', '-f', 'http://127.0.0.1:9000/minio/health/live']
interval: 3s
timeout: 5s
retries: 15
start_period: 3s
tmpfs:
- /data
Loading
Loading