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: 18 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ docker compose -f compose.dev.yaml exec backtest-be-fast pytest tests/unit
# Frontend tests
docker compose -f compose.dev.yaml exec backtest-fe npm test

# Frontend quality checks (all four run in CI)
docker compose -f compose.dev.yaml exec backtest-fe npm run lint
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check # prod code
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check:test # test code
docker compose -f compose.dev.yaml exec backtest-fe npm run test:run

# Reproduce the CI gate exactly
docker build --target test ./backtest_fe
docker build --target test ./backtest_be_fast

# API docs: http://localhost:8000/api/v1/docs
# Frontend: http://localhost:5173
```
Expand All @@ -111,6 +121,14 @@ docker compose -f compose.dev.yaml exec backtest-fe npm test
- **Frontend:** Vitest + RTL for components, Playwright for E2E
- **Mocking:** External APIs (yfinance) in unit tests, real calls in `@pytest.mark.external`
- **Strategy values:** Use `buy_hold_strategy`, NOT `buy_and_hold` in test fixtures
- **Baseline:** BE 141 unit tests, FE 113 tests — all green. A failure is a regression.
- **Test files are type-checked** via `tsconfig.test.json` (`npm run type-check:test`); `tsconfig.build.json` excludes them.
- **Never set `isolate: false`** in `vitest.config.ts` — shared happy-dom + vitest's duration-based file reordering makes the suite flaky.

## Frontend Stack Constraints
- **React 19 / Vite 7 / Recharts 3 / React Router 7 / Tailwind CSS 4.**
- **Tailwind 4 is CSS-first:** no `tailwind.config.js`; config lives in `src/index.css`. Do NOT move theme color literals into `@theme` — `useTheme` injects them at runtime via `root.style.setProperty()`. Use `.app-container`, not `.container`.
- **`VITE_API_BASE_URL` must be empty** — the service layer already passes full `/api/v1/...` paths.

## Documentation
Detailed architecture docs in each service's `docs/` directory:
Expand Down
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
docker compose -f compose.dev.yaml up -d --build
docker compose -f compose.dev.yaml exec backtest-be-fast pytest tests/unit -v
docker compose -f compose.dev.yaml exec backtest-fe npm test

# FE quality checks (all four run in CI)
docker compose -f compose.dev.yaml exec backtest-fe npm run lint
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check # prod code
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check:test # test code
docker compose -f compose.dev.yaml exec backtest-fe npm run test:run

# Reproduce the CI gate exactly (same as Jenkins 'Quality Gate' stage)
docker build --target test ./backtest_fe
docker build --target test ./backtest_be_fast
```

## Architecture
Expand All @@ -37,6 +47,14 @@ docker compose -f compose.dev.yaml exec backtest-fe npm test

6. **`cachetools>=5.3.0`** required in BE (TTLCache for data_repository).

7. **`VITE_API_BASE_URL` must be empty.** The service layer passes full paths (`/api/v1/...`) to axios, so a `/api` base yields `/api/api/v1/backtest` and 404s. `client.ts` has a defensive interceptor that strips the duplicate, but that is a safety net — do not rely on it by setting a base.

8. **Tailwind 4, CSS-first config.** There is no `tailwind.config.js`; config lives in `src/index.css`. Do NOT move theme color literals into `@theme` — `useTheme` injects them at runtime via `root.style.setProperty()`, and baking them in kills theme switching. Dark mode is `@custom-variant dark (&:is(.dark *))`. Use `.app-container`, not `.container` (v4 emits its own with different max-widths).

9. **Never set `isolate: false` in `vitest.config.ts`.** All test files would share one happy-dom environment, and vitest reorders files by cached durations, so the suite becomes flaky — the same commit alternated between `113 passed` and `3 failed`.

10. **FE build must pin `NODE_ENV=production`.** `Dockerfile.dev` sets `NODE_ENV=development`, which leaks into `docker compose exec ... npm run build` and makes vite bundle the React dev build. The `build` scripts set it explicitly; keep it when editing them.

## Sub-Agent Usage

- **`Explore`** for broad codebase research (where does X live, how is Y wired)
Expand All @@ -49,6 +67,14 @@ Always verify changes in Docker containers (`docker compose exec`) before declar

- **BE markers:** `@pytest.mark.unit` (no DB), `@pytest.mark.integration` (DB), `@pytest.mark.external` (real API)
- **FE:** Vitest + React Testing Library; Playwright for E2E
- **Current baseline:** BE 141 unit tests, FE 113 tests — both fully green. Any failure is a regression, not pre-existing noise.
- **Test files are type-checked** via `tsconfig.test.json` / `npm run type-check:test`. `tsconfig.build.json` deliberately excludes them.

## CI

`Jenkinsfile` runs a `Quality Gate` stage (FE and BE in parallel) before building images. Each Dockerfile has a `test` stage that CI invokes with `--target test`; those stages are outside the final image's dependency chain, so a plain `docker build` does not run them and produces the same artifacts as before.

The gate blocks **deployment**, not merging — the pipeline checks out `*/main` and the repo uses no branch protection or GitHub checks.

## Commit Convention

Expand Down
23 changes: 23 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ pipeline {
}
}

stage('Quality Gate') {
steps {
script {
// 각 Dockerfile의 test 스테이지를 돌린다.
// - FE: lint / type-check / type-check:test / vitest
// - BE: pytest tests/unit (DB 불필요)
// 실패하면 이미지 빌드와 배포에 도달하지 못한다.
//
// test 스테이지는 최종 이미지의 의존 경로에 없으므로 --target으로
// 명시해야 실행된다. deps/base 레이어는 뒤이은 이미지 빌드가
// 그대로 재사용하므로 의존성 설치가 두 번 돌지 않는다.
parallel(
'Frontend': {
sh 'docker build --target test ./backtest_fe'
},
'Backend': {
sh 'docker build --target test ./backtest_be_fast'
}
)
}
}
}

stage('Login GHCR') {
steps {
script {
Expand Down
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@

| 구분 | 기술 |
|:-----|:-----|
| **Backend** | Python 3.11, FastAPI, SQLAlchemy, pandas, numpy, backtesting.py |
| **Frontend** | TypeScript, React, Vite, Zustand, Recharts, shadcn/ui, Tailwind CSS |
| **Backend** | Python 3.11, FastAPI, SQLAlchemy, pandas, numpy, backtesting.py 0.3.3 |
| **Frontend** | TypeScript 5, React 19, Vite 7, Zustand, Recharts 3, React Router 7, shadcn/ui, Tailwind CSS 4 |
| **Database** | MySQL 8.0 |
| **Infra** | Docker, Docker Compose, Nginx, Jenkins |
| **Test** | Pytest (BE), Vitest, React Testing Library, Playwright (FE) |
| **Test** | Pytest (BE), Vitest 4, React Testing Library, Playwright (FE) |

---

Expand All @@ -35,9 +35,9 @@ backtest/
│ ├── Dockerfile # 프로덕션 Docker 이미지
│ └── requirements.txt # Python 의존성
├── backtest_fe/ # Frontend (React + Vite)
│ ├── src/ # 소스 코드
│ ├── __tests__/ # 테스트 코드
│ └── Dockerfile # 프로덕션 Docker 이미지
│ ├── src/ # 소스 코드 (테스트는 각 모듈 옆 __tests__/에 위치)
│ ├── e2e/ # Playwright E2E
│ └── Dockerfile # 프로덕션 Docker 이미지 (test 스테이지 포함)
├── database/ # DB 스키마 및 초기화 스크립트
├── compose.dev.yaml # 개발용 Docker Compose
├── Jenkinsfile # CI/CD 파이프라인
Expand Down Expand Up @@ -180,8 +180,22 @@ docker compose -f compose.dev.yaml exec backtest-fe npm test

# UI 모드
docker compose -f compose.dev.yaml exec backtest-fe npm run test:ui

# 린트 및 타입 체크
docker compose -f compose.dev.yaml exec backtest-fe npm run lint
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check # 프로덕션 코드
docker compose -f compose.dev.yaml exec backtest-fe npm run type-check:test # 테스트 코드
```

### CI 게이트를 그대로 재현

```bash
docker build --target test ./backtest_fe # lint → type-check ×2 → vitest
docker build --target test ./backtest_be_fast # pytest tests/unit
```

현재 기준선은 BE 141건, FE 113건이며 모두 통과합니다. 실패가 보이면 회귀입니다.

---

## GHCR에 이미지 Push
Expand All @@ -204,9 +218,14 @@ docker push ghcr.io/kyj0503/backtest-fe:latest
### 자동 Push (Jenkins)

`main` 브랜치에 Push하면 Jenkins가 자동으로:
1. Backend/Frontend 이미지 빌드
2. GHCR에 Push (`latest` + 빌드 번호 태그)
3. home-server 배포 트리거
1. **Quality Gate** — FE/BE 각 Dockerfile의 `test` 스테이지를 병렬 실행
(FE: lint, type-check, type-check:test, vitest / BE: `pytest tests/unit`)
2. Backend/Frontend 이미지 빌드
3. GHCR에 Push (`latest` + 빌드 번호 태그)
4. home-server 배포 트리거
5. 헬스 체크

Quality Gate가 실패하면 이미지 빌드와 배포에 도달하지 못합니다. 다만 이 게이트는 **배포**를 막는 것이며, 파이프라인이 `*/main`을 체크아웃하고 브랜치 보호를 쓰지 않으므로 병합 자체를 막지는 않습니다.

---

Expand Down
20 changes: 19 additions & 1 deletion backtest_be_fast/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# syntax=docker/dockerfile:1
# Python 3.11 슬림 이미지 사용
FROM python:3.11-slim
# base: 의존성까지만 설치한 공통 기반 (test / runtime이 공유)
FROM python:3.11-slim AS base

# 작업 디렉터리 설정
WORKDIR /app
Expand Down Expand Up @@ -40,6 +41,23 @@ COPY requirements-test.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements-test.txt

# 품질 게이트
#
# runtime이 base에서 갈라져 나오므로 이 스테이지는 최종 이미지의 의존 경로에
# 없다. 따라서 `docker build`(타깃 미지정)로는 실행되지 않고, CI가
# `docker build --target test`로 명시적으로 호출한다.
# DB가 필요 없는 unit 마커만 돌린다 (tests/integration은 MySQL을 요구).
FROM base AS test

COPY app ./app
COPY tests ./tests
COPY pytest.ini ./

RUN pytest tests/unit -q

# runtime: 실제 배포 이미지 (마지막 스테이지 = 기본 빌드 타깃)
FROM base AS runtime

# 애플리케이션 코드 복사
COPY app ./app
COPY scripts ./scripts
Expand Down
32 changes: 17 additions & 15 deletions backtest_be_fast/docs/TEST_COVERAGE_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

Comprehensive unit tests have been created for the following backend modules, following pytest best practices with `@pytest.mark.unit` and `@pytest.mark.asyncio` markers. All external I/O (yfinance, DB) is mocked.

**Total Tests Created: 59**
**Total Tests Created: 59** (이 문서가 다루는 4개 모듈 기준)

> 현재 `tests/unit` 전체는 **141건**입니다. 이 문서는 아래 4개 모듈에 한정된 보고서이며, 전체 목록은 [UNIT_TEST_QUICK_REFERENCE.md](./UNIT_TEST_QUICK_REFERENCE.md)를 참고하십시오.
**Test Files: 4**
**All Tests: PASSING ✅**

Expand Down Expand Up @@ -44,7 +46,7 @@ Tests the core `BacktestEngine` that wraps backtesting.py library.

---

### 2. tests/unit/test_currency_converter.py (18 tests)
### 2. tests/unit/test_currency_converter.py (19 tests)

Tests the `CurrencyConverter` for multi-currency support (13 currencies including USD, KRW, JPY, EUR, GBP).

Expand All @@ -65,7 +67,7 @@ Tests the `CurrencyConverter` for multi-currency support (13 currencies includin
- `test_unsupported_currency_returns_data_unchanged` - Unsupported currency logs warning, returns original
- `test_conversion_error_returns_original_data` - Network errors return original data

- **TestLoadAndPrepareExchangeRates** (4 tests)
- **TestLoadAndPrepareExchangeRates** (5 tests)
- `test_usd_raises_valueerror` - USD raises ValueError (no conversion needed)
- `test_unsupported_currency_raises_valueerror` - Unsupported currency raises ValueError
- `test_successful_load_returns_dataframe` - Successful load returns DataFrame with 'Close' column
Expand All @@ -84,7 +86,7 @@ Tests the `CurrencyConverter` for multi-currency support (13 currencies includin

---

### 3. tests/unit/test_data_repository.py (17 tests)
### 3. tests/unit/test_data_repository.py (14 tests)

Tests the `YfinanceDataRepository` with 3-tier caching: memory (TTLCache) → DB → yfinance.

Expand Down Expand Up @@ -122,7 +124,7 @@ Tests the `YfinanceDataRepository` with 3-tier caching: memory (TTLCache) → DB

---

### 4. tests/unit/test_portfolio_manager_helpers.py (14 tests)
### 4. tests/unit/test_portfolio_manager_helpers.py (16 tests)

Tests static helper methods in `PortfolioManagerService` (pure functions, no I/O).

Expand All @@ -140,7 +142,7 @@ Tests static helper methods in `PortfolioManagerService` (pure functions, no I/O
- `test_calculate_daily_return_stats_single_return` - Single return edge case (volatility = 0)
- `test_calculate_daily_return_stats_with_zeros` - Zero returns don't count as positive/negative

- **TestFormatIndividualResultsList** (5 tests)
- **TestFormatIndividualResultsList** (7 tests)
- `test_format_strategy_mode_returns_correct_structure` - Correct format for 'strategy' mode
- `test_format_buy_hold_mode_returns_correct_structure` - Correct format for 'buy_hold' mode
- `test_format_buy_hold_mode_with_negative_return` - Negative return handled correctly
Expand All @@ -161,13 +163,13 @@ Tests static helper methods in `PortfolioManagerService` (pure functions, no I/O

### Run All New Tests:
```bash
source venv/bin/activate
cd /home/coontec/source/backtest/backtest_be_fast
python -m pytest tests/unit/test_backtest_engine.py \
tests/unit/test_currency_converter.py \
tests/unit/test_data_repository.py \
tests/unit/test_portfolio_manager_helpers.py \
-v --tb=short
# 저장소 루트에서 (이 프로젝트는 Docker로 실행됩니다)
docker compose -f compose.dev.yaml exec backtest-be-fast \
pytest tests/unit/test_backtest_engine.py \
tests/unit/test_currency_converter.py \
tests/unit/test_data_repository.py \
tests/unit/test_portfolio_manager_helpers.py \
-v --tb=short
```

### Results:
Expand Down Expand Up @@ -246,7 +248,7 @@ python -m pytest tests/unit/test_backtest_engine.py \
## Key Testing Principles Applied

1. **Unit Testing Best Practices**
- Tests are fast (<0.5s total for 59 tests)
- Tests are fast (<0.5s total for these 59 tests; 전체 141건도 0.5초 내)
- Tests are isolated (no shared state)
- Tests are deterministic (no random data)
- Each test has a single responsibility
Expand Down Expand Up @@ -287,6 +289,6 @@ python -m pytest tests/unit/test_backtest_engine.py \

## Summary

All 59 unit tests pass successfully, covering critical backend modules with comprehensive test scenarios including happy paths, error handling, and edge cases. Tests follow pytest best practices with proper markers, fixtures, and mocking strategies. The test suite runs fast (0.39s) and provides confidence in code quality without external dependencies.
이 문서가 다루는 4개 모듈의 59건은 모두 통과합니다. `tests/unit` 전체 **141건**도 모두 통과하며, CI의 `Quality Gate` 스테이지(`docker build --target test ./backtest_be_fast`)가 이를 강제합니다. 실패가 보이면 회귀입니다.

**Status: ✅ COMPLETE AND PASSING**
Loading