Files
hr-portal/frontend/TDD_GUIDE.md
Porsche Chen 360533393f feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage
Major Features:
-  Multi-tenant architecture (tenant isolation)
-  Employee CRUD with lifecycle management (onboarding/offboarding)
-  Department tree structure with email domain management
-  Company info management (single-record editing)
-  System functions CRUD (permission management)
-  Email account management (multi-account per employee)
-  Keycloak SSO integration (auth.lab.taipei)
-  Redis session storage (10.1.0.254:6379)
  - Solves Cookie 4KB limitation
  - Cross-system session sharing
  - Sliding expiration (8 hours)
  - Automatic token refresh

Technical Stack:
Backend:
- FastAPI + SQLAlchemy
- PostgreSQL 16 (10.1.0.20:5433)
- Keycloak Admin API integration
- Docker Mailserver integration (SSH)
- Alembic migrations

Frontend:
- Next.js 14 (App Router)
- NextAuth 4 with Keycloak Provider
- Redis session storage (ioredis)
- Tailwind CSS

Infrastructure:
- Redis 7 (10.1.0.254:6379) - Session + Cache
- Keycloak 26.1.0 (auth.lab.taipei)
- Docker Mailserver (10.1.0.254)

Architecture Highlights:
- Session管理由 Keycloak + Redis 統一控制
- 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session
- Token 自動刷新,異質服務整合
- 未來可無縫遷移到雲端

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-23 20:12:43 +08:00

6.4 KiB
Raw Blame History

TDD 開發指南 - HR Portal 前端

建立日期: 2026-02-21 測試框架: Vitest + React Testing Library


📋 目錄結構

frontend/
├── services/
│   ├── __tests__/
│   │   └── onboarding.service.test.ts   # API Service 測試
│   └── onboarding.service.ts            # API Service 實作
├── components/
│   ├── __tests__/
│   │   └── OnboardingForm.test.tsx      # 組件測試 (待建立)
│   └── OnboardingForm.tsx               # 組件實作 (待建立)
├── types/
│   └── onboarding.ts                    # TypeScript 型別定義
├── vitest.config.ts                     # Vitest 配置
└── vitest.setup.ts                      # 測試環境設置

🚀 快速開始

1. 安裝依賴

cd q:/porscheworld_develop/hr-portal/frontend
npm install

需要安裝的測試相關套件:

{
  "devDependencies": {
    "@testing-library/jest-dom": "^6.1.5",
    "@testing-library/react": "^14.1.2",
    "@testing-library/user-event": "^14.5.1",
    "@vitejs/plugin-react": "^4.2.1",
    "@vitest/ui": "^1.0.4",
    "jsdom": "^23.0.1",
    "vitest": "^1.0.4"
  }
}

2. 執行測試

# 執行所有測試
npm test

# 執行測試並開啟 UI 介面
npm run test:ui

# 執行測試並生成覆蓋率報告
npm run test:coverage

# 監視模式(自動重跑)
npm test -- --watch

🎯 TDD 工作流程

Red-Green-Refactor 循環

1. RED    → 先寫失敗的測試
2. GREEN  → 寫最少的程式碼讓測試通過
3. REFACTOR → 重構程式碼,保持測試通過

範例API Service TDD

Step 1: RED - 寫失敗的測試

// services/__tests__/onboarding.service.test.ts
import { describe, it, expect } from 'vitest'
import { onboardingService } from '../onboarding.service'

describe('OnboardingService', () => {
  it('should successfully onboard an employee', async () => {
    const request = {
      resume_id: 1,
      keycloak_user_id: '550e8400-...',
      keycloak_username: 'wang.ming',
      hire_date: '2026-02-21',
      departments: [{ department_id: 9, position: '工程師' }],
      role_ids: [1],
    }

    const result = await onboardingService.onboardEmployee(request)

    expect(result).toHaveProperty('message')
    expect(result).toHaveProperty('employee')
  })
})

執行測試:

npm test
# ❌ 測試失敗onboardingService 不存在

Step 2: GREEN - 實作最少程式碼

// services/onboarding.service.ts
import axios from 'axios'

class OnboardingService {
  async onboardEmployee(request: OnboardingRequest) {
    const response = await axios.post('/api/v1/emp-lifecycle/onboard', request)
    return response.data
  }
}

export const onboardingService = new OnboardingService()

執行測試:

npm test
# ✅ 測試通過

Step 3: REFACTOR - 重構

// 新增型別定義、錯誤處理、環境變數等

📦 已完成的測試

API Service 層

檔案: services/__tests__/onboarding.service.test.ts

測試案例:

  1. onboardEmployee - 成功到職
  2. onboardEmployee - API 失敗處理
  3. getEmployeeStatus - 查詢員工狀態
  4. getEmployeeStatus - 員工不存在
  5. offboardEmployee - 成功離職

執行測試:

npm test services/__tests__/onboarding.service.test.ts

🔧 下一步:組件測試

待建立的測試

1. OnboardingForm 組件測試

// components/__tests__/OnboardingForm.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { OnboardingForm } from '../OnboardingForm'

describe('OnboardingForm', () => {
  it('should render all required fields', () => {
    render(<OnboardingForm />)

    expect(screen.getByLabelText('員工姓名')).toBeInTheDocument()
    expect(screen.getByLabelText('Keycloak 使用者名稱')).toBeInTheDocument()
    expect(screen.getByLabelText('到職日期')).toBeInTheDocument()
  })

  it('should validate required fields', async () => {
    render(<OnboardingForm />)

    const submitButton = screen.getByRole('button', { name: /送出/i })
    fireEvent.click(submitButton)

    expect(await screen.findByText('此欄位為必填')).toBeInTheDocument()
  })

  it('should submit form successfully', async () => {
    const onSubmit = vi.fn()
    render(<OnboardingForm onSubmit={onSubmit} />)

    // Fill form...
    fireEvent.click(screen.getByRole('button', { name: /送出/i }))

    expect(onSubmit).toHaveBeenCalled()
  })
})

2. EmployeeStatusCard 組件測試

// 顯示員工詳細資訊的卡片組件

📊 測試覆蓋率目標

類型 目標 當前
API Service 100% 100%
Components 80% 0%
Hooks 80% 0%
Utilities 90% 0%

🎨 測試最佳實踐

1. 測試命名

// ✅ Good: 描述行為,不是實作
it('should display error when API fails', () => {})

// ❌ Bad: 測試實作細節
it('should call axios.post', () => {})

2. AAA 模式

it('should do something', () => {
  // Arrange (準備)
  const data = { ... }

  // Act (執行)
  const result = doSomething(data)

  // Assert (斷言)
  expect(result).toBe(expected)
})

3. 隔離測試

// ✅ Good: 每個測試獨立
describe('MyComponent', () => {
  beforeEach(() => {
    // 每個測試前重置
    cleanup()
  })
})

// ❌ Bad: 測試間有依賴

4. Mock 外部依賴

// Mock axios
vi.mock('axios')

// Mock Next.js router
vi.mock('next/navigation')

🐛 常見問題

Q1: ReferenceError: describe is not defined

解決: 確認 vitest.config.ts 中有設定 globals: true

Q2: TypeError: Cannot read property 'xxx' of undefined

解決: 檢查是否正確 mock 了依賴

Q3: 測試執行很慢

解決:

  • 使用 vi.mock() 避免真實 API 呼叫
  • 使用 --run 參數執行一次性測試

📚 參考資源


下一步: 執行 npm install 安裝測試依賴,然後執行 npm test 確認測試環境正常運作。