feat(T007/phase3): 엔드투엔드 양도세 신고 CLI (scripts/run_tax_report.py) #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [master, main, "feature/**"] | |
| pull_request: | |
| branches: [master, main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint-and-test: | |
| name: ruff + pytest (Python ${{ matrix.python-version }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12"] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: pyproject.toml | |
| - name: Install package (dev extras) | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: ruff check | |
| run: ruff check sentinelq/ tests/ | |
| - name: ruff format --check | |
| run: ruff format --check sentinelq/ tests/ | |
| - name: pytest with coverage | |
| run: | | |
| pytest tests/ -v \ | |
| --cov=sentinelq.tax \ | |
| --cov=sentinelq.portfolio \ | |
| --cov-report=term-missing \ | |
| --cov-report=xml \ | |
| -m "not integration" | |
| - name: Enforce baseline.json regression floor | |
| # baseline.json의 test.total을 회귀 차단 floor로 사용한다. | |
| # 수집된 테스트(passed + skipped)가 total 미만이면 실패 (테스트 유실 회귀). | |
| # skip은 skip_tolerance 이내까지 허용 (예: KIS cache 부재로 인한 합법적 skip). | |
| run: | | |
| python - <<'PY' | |
| import json, subprocess, sys, re | |
| baseline = json.load(open(".claude/baseline.json")) | |
| total_floor = baseline["test"]["total"] | |
| skip_tol = baseline["test"].get("skip_tolerance", 0) | |
| result = subprocess.run( | |
| ["pytest", "tests/", "-q", "-m", "not integration", "--no-header"], | |
| capture_output=True, text=True | |
| ) | |
| output = result.stdout + result.stderr | |
| passed = int(re.search(r"(\d+) passed", output).group(1)) if re.search(r"(\d+) passed", output) else 0 | |
| skipped_m = re.search(r"(\d+) skipped", output) | |
| skipped = int(skipped_m.group(1)) if skipped_m else 0 | |
| failed_m = re.search(r"(\d+) failed", output) | |
| failed = int(failed_m.group(1)) if failed_m else 0 | |
| collected = passed + skipped + failed | |
| print(f"baseline: total={total_floor}, skip_tol={skip_tol}") | |
| print(f"current : passed={passed}, skipped={skipped}, failed={failed}, collected={collected}") | |
| ok = True | |
| if failed > 0: | |
| print(f"::error::Test failures: {failed}") | |
| ok = False | |
| if collected < total_floor: | |
| print(f"::error::Regression: collected {collected} < baseline total {total_floor} (테스트 유실)") | |
| ok = False | |
| if skipped > skip_tol: | |
| print(f"::error::Too many skips: {skipped} > tolerance {skip_tol}") | |
| ok = False | |
| if not ok: | |
| sys.exit(1) | |
| print("OK: at or above baseline floor.") | |
| PY | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-${{ matrix.python-version }} | |
| path: coverage.xml | |
| if-no-files-found: ignore | |
| secrets-scan: | |
| name: gitleaks secrets scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: gitleaks | |
| uses: gitleaks/gitleaks-action@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITLEAKS_CONFIG: .gitleaks.toml |