浏览代码

Merge branch 'mini-test' into monorepo

yourname 3 周之前
父节点
当前提交
7a741b6b88
共有 58 个文件被更改,包括 1709 次插入2053 次删除
  1. 2 1
      .claude/settings.local.json
  2. 12 8
      .github/workflows/component-tests.yml
  3. 12 1
      .github/workflows/e2e-tests.yml
  4. 3 6
      .github/workflows/integration-tests.yml
  5. 125 0
      .github/workflows/server-tests.yml
  6. 1 0
      .gitignore
  7. 29 4
      docs/architecture/source-tree.md
  8. 64 20
      docs/architecture/testing-strategy.md
  9. 38 0
      mini/jest.config.js
  10. 13 1
      mini/package.json
  11. 95 0
      mini/src/components/ui/dialog.tsx
  12. 1 0
      mini/tests/__mocks__/fileMock.js
  13. 1 0
      mini/tests/__mocks__/styleMock.js
  14. 100 0
      mini/tests/__mocks__/taroMock.ts
  15. 37 0
      mini/tests/components/Button.test.tsx
  16. 43 0
      mini/tests/example.test.tsx
  17. 432 0
      mini/tests/setup.ts
  18. 3 2
      mini/tsconfig.json
  19. 8 2
      packages/server/package.json
  20. 1 1
      packages/server/src/modules/auth/auth.service.ts
  21. 6 2
      packages/server/src/utils/jwt.util.ts
  22. 6 6
      packages/server/tests/integration/auth.integration.test.ts
  23. 3 3
      packages/server/tests/integration/backup.integration.test.ts
  24. 9 9
      packages/server/tests/integration/files.integration.test.ts
  25. 3 3
      packages/server/tests/integration/minio.integration.test.ts
  26. 6 6
      packages/server/tests/integration/users.integration.test.ts
  27. 17 0
      packages/server/tests/unit/example.test.ts
  28. 6 6
      packages/server/tests/unit/modules/file.service.test.ts
  29. 3 3
      packages/server/tests/unit/modules/minio.service.test.ts
  30. 2 2
      packages/server/tests/unit/modules/user.service.test.ts
  31. 7 7
      packages/server/tests/unit/utils/backup.test.ts
  32. 4 4
      packages/server/tests/unit/utils/restore.test.ts
  33. 3 3
      packages/server/tests/utils/integration-test-db.ts
  34. 1 4
      packages/server/tests/utils/integration-test-utils.ts
  35. 21 0
      packages/server/tests/utils/setup.ts
  36. 3 2
      packages/server/tsconfig.json
  37. 51 0
      packages/server/vitest.config.ts
  38. 0 58
      packages/test-utils/package.json
  39. 0 54
      packages/test-utils/src/client/index.ts
  40. 0 92
      packages/test-utils/src/client/test-query.tsx
  41. 0 49
      packages/test-utils/src/client/test-router.tsx
  42. 0 57
      packages/test-utils/src/index.ts
  43. 0 92
      packages/test-utils/src/server/index.ts
  44. 0 99
      packages/test-utils/src/server/integration-test-db.ts
  45. 0 164
      packages/test-utils/src/server/service-mocks.ts
  46. 0 158
      packages/test-utils/src/server/service-stubs.ts
  47. 0 88
      packages/test-utils/src/server/test-auth.ts
  48. 0 157
      packages/test-utils/src/server/test-db.ts
  49. 0 48
      packages/test-utils/src/setup.ts
  50. 0 90
      packages/test-utils/src/test-utils.ts
  51. 0 41
      packages/test-utils/tsconfig.json
  52. 538 56
      pnpm-lock.yaml
  53. 0 2
      web/package.json
  54. 0 75
      web/tests/utils/server/integration-test-utils.ts
  55. 0 164
      web/tests/utils/server/service-mocks.ts
  56. 0 158
      web/tests/utils/server/service-stubs.ts
  57. 0 88
      web/tests/utils/server/test-auth.ts
  58. 0 157
      web/tests/utils/server/test-db.ts

+ 2 - 1
.claude/settings.local.json

@@ -40,7 +40,8 @@
       "Bash(pnpm db:backup:*)",
       "Bash(pnpm db:backup:list:*)",
       "Bash(pnpm db:backup:cleanup:*)",
-      "Bash(pnpm db:restore)"
+      "Bash(pnpm db:restore)",
+      "Bash(pnpm test:coverage:*)"
     ],
     "deny": [],
     "ask": []

+ 12 - 8
.github/workflows/component-tests.yml

@@ -4,16 +4,16 @@ on:
   push:
     branches: [ main, develop ]
     paths:
-      - 'src/client/**'
-      - 'src/client/__integration_tests__/**'
-      - 'vitest.config.components.ts'
+      - 'web/src/client/**'
+      - 'web/tests/unit/**'
+      - 'web/vitest.config.components.ts'
       - '.github/workflows/component-tests.yml'
   pull_request:
     branches: [ main ]
     paths:
-      - 'src/client/**'
-      - 'src/client/__integration_tests__/**'
-      - 'vitest.config.components.ts'
+      - 'web/src/client/**'
+      - 'web/tests/unit/**'
+      - 'web/vitest.config.components.ts'
       - '.github/workflows/component-tests.yml'
   workflow_dispatch:
 
@@ -41,7 +41,9 @@ jobs:
       run: pnpm install --frozen-lockfile
 
     - name: Run component tests
-      run: pnpm test:components
+      run: |
+        cd web
+        pnpm test:components
 
     - name: Upload test results
       if: always()
@@ -55,7 +57,9 @@ jobs:
 
     - name: Generate coverage report
       if: success()
-      run: pnpm test:components:coverage
+      run: |
+        cd web
+        pnpm test:components:coverage
 
     - name: Upload coverage to Codecov
       if: success()

+ 12 - 1
.github/workflows/e2e-tests.yml

@@ -3,8 +3,16 @@ name: E2E Tests
 on:
   push:
     branches: [ main, develop ]
+    paths:
+      - 'web/tests/e2e/**'
+      - 'web/src/**'
+      - '.github/workflows/e2e-tests.yml'
   pull_request:
     branches: [ main ]
+    paths:
+      - 'web/tests/e2e/**'
+      - 'web/src/**'
+      - '.github/workflows/e2e-tests.yml'
   workflow_dispatch:
 
 jobs:
@@ -63,6 +71,7 @@ jobs:
     - name: Run E2E tests
       run: |
         export NODE_ENV=test
+        cd web
         pnpm test:e2e --project=chromium
       env:
         E2E_BASE_URL: http://localhost:8080
@@ -91,7 +100,9 @@ jobs:
 
     - name: Analyze test results
       if: always()
-      run: pnpm test:analyze
+      run: |
+        cd web
+        pnpm test:analyze
 
     - name: Notify on failure
       if: failure()

+ 3 - 6
.github/workflows/integration-tests.yml

@@ -4,16 +4,12 @@ on:
   push:
     branches: [ main, develop ]
     paths:
-      - 'src/server/api/**'
-      - 'src/server/__test_utils__/**'
-      - 'src/client/__integration_tests__/**'
+      - 'web/tests/integration/**'
       - '.github/workflows/integration-tests.yml'
   pull_request:
     branches: [ main ]
     paths:
-      - 'src/server/api/**'
-      - 'src/server/__test_utils__/**'
-      - 'src/client/__integration_tests__/**'
+      - 'web/tests/integration/**'
       - '.github/workflows/integration-tests.yml'
   workflow_dispatch:
 
@@ -70,6 +66,7 @@ jobs:
     - name: Run integration tests
       run: |
         export NODE_ENV=test
+        cd web
         pnpm test:integration
 
     - name: Upload test results

+ 125 - 0
.github/workflows/server-tests.yml

@@ -0,0 +1,125 @@
+name: Server Tests
+
+on:
+  push:
+    branches: [ main, develop ]
+    paths:
+      - 'packages/server/**'
+      - '.github/workflows/server-tests.yml'
+  pull_request:
+    branches: [ main ]
+    paths:
+      - 'packages/server/**'
+      - '.github/workflows/server-tests.yml'
+  workflow_dispatch:
+
+jobs:
+  server-tests:
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+
+    services:
+      postgres:
+        image: postgres:17
+        env:
+          POSTGRES_DB: test_d8dai
+          POSTGRES_USER: postgres
+          POSTGRES_PASSWORD: test_password
+        options: >-
+          --health-cmd="pg_isready -U postgres"
+          --health-interval=10s
+          --health-timeout=5s
+          --health-retries=3
+        ports:
+          - 5432:5432
+
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v4
+
+    - name: Setup Node.js
+      uses: actions/setup-node@v4
+      with:
+        node-version: '20'
+        cache: 'pnpm'
+
+    - name: Install pnpm
+      uses: pnpm/action-setup@v2
+      with:
+        version: 8
+
+    - name: Install dependencies
+      run: pnpm install --frozen-lockfile
+
+    - name: Setup test environment
+      run: |
+        cp .env.test.example .env.test
+        echo "TEST_DATABASE_URL=postgresql://postgres:test_password@localhost:5432/test_d8dai" >> .env.test
+        echo "NODE_ENV=test" >> .env.test
+        echo "JWT_SECRET=test_jwt_secret_1234567890" >> .env.test
+
+    - name: Run database migrations
+      run: |
+        export NODE_ENV=test
+        cd packages/server
+        pnpm db:migrate
+
+    - name: Run server unit tests
+      run: |
+        export NODE_ENV=test
+        cd packages/server
+        pnpm test:unit
+
+    - name: Run server integration tests
+      run: |
+        export NODE_ENV=test
+        cd packages/server
+        pnpm test:integration
+
+    - name: Generate server test coverage
+      run: |
+        export NODE_ENV=test
+        cd packages/server
+        pnpm test:coverage
+
+    - name: Upload test results
+      if: always()
+      uses: actions/upload-artifact@v4
+      with:
+        name: server-test-results
+        path: |
+          packages/server/coverage/
+          packages/server/test-results/
+        retention-days: 7
+
+    - name: Upload coverage to Codecov
+      if: success()
+      uses: codecov/codecov-action@v3
+      with:
+        file: ./packages/server/coverage/coverage-final.json
+        flags: server-tests
+
+    - name: Generate test summary
+      if: always()
+      uses: test-summary/action@v2
+      with:
+        paths: packages/server/test-results/junit.xml
+
+    - name: Notify on failure
+      if: failure()
+      uses: 8398a7/action-slack@v3
+      with:
+        status: ${{ job.status }}
+        channel: '#ci-notifications'
+        webhook_url: ${{ secrets.SLACK_WEBHOOK }}
+      env:
+        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
+
+    - name: Send test results to GitHub
+      if: always()
+      uses: dorny/test-reporter@v1
+      with:
+        name: Server Tests
+        path: packages/server/test-results/junit.xml
+        reporter: jest-junit
+        fail-on-error: false

+ 1 - 0
.gitignore

@@ -54,3 +54,4 @@ scripts/time_logger.sh
 loop.txt
 .nfs*
 tsconfig.tsbuildinfo
+mini/tests/__snapshots__/*

+ 29 - 4
docs/architecture/source-tree.md

@@ -3,6 +3,7 @@
 ## 版本信息
 | 版本 | 日期 | 描述 | 作者 |
 |------|------|------|------|
+| 3.1 | 2025-11-09 | 更新测试结构,清理重复测试文件 | James |
 | 3.0 | 2025-10-22 | 更新为 monorepo 结构,添加 packages/server | Winston |
 
 ## 实际项目结构
@@ -109,6 +110,21 @@ d8d-mini-starter/
 │       │   │   └── restore.ts                   # 数据库恢复
 │       │   ├── data-source.ts                   # 数据库连接
 │       │   └── index.ts                         # 服务器入口
+│       ├── tests/              # 服务器测试
+│       │   ├── integration/    # 集成测试
+│       │   │   ├── auth.integration.test.ts      # 认证集成测试
+│       │   │   ├── backup.integration.test.ts    # 备份集成测试
+│       │   │   ├── files.integration.test.ts     # 文件集成测试
+│       │   │   ├── minio.integration.test.ts     # MinIO集成测试
+│       │   │   └── users.integration.test.ts     # 用户集成测试
+│       │   └── unit/           # 单元测试
+│       │       ├── modules/    # 业务模块测试
+│       │       │   ├── file.service.test.ts      # 文件服务测试
+│       │       │   ├── minio.service.test.ts     # MinIO服务测试
+│       │       │   └── user.service.test.ts      # 用户服务测试
+│       │       └── utils/      # 工具函数测试
+│       │           ├── backup.test.ts            # 备份工具测试
+│       │           └── restore.test.ts           # 恢复工具测试
 │       └── package.json
 ├── web/                        # Web应用 (Hono + React SSR)
 │   ├── src/
@@ -148,8 +164,13 @@ d8d-mini-starter/
 │   │   └── share/              # 共享代码
 │   ├── tests/
 │   │   ├── e2e/                # E2E测试 (Playwright)
-│   │   ├── integration/        # 集成测试
+│   │   ├── integration/        # Web集成测试
+│   │   │   └── client/         # 客户端集成测试
 │   │   └── unit/               # 单元测试
+│   │       └── client/         # 客户端单元测试
+│   │           ├── pages/      # 页面组件测试
+│   │           │   └── Users.test.tsx           # 用户页面测试
+│   │           └── debug.test.tsx               # 调试测试
 │   └── package.json
 ├── docs/                       # 项目文档
 │   └── architecture/           # 架构文档
@@ -173,9 +194,13 @@ d8d-mini-starter/
 - **数据库**: 使用PostgreSQL + TypeORM
 - **存储**: 使用MinIO进行文件存储
 - **测试结构**:
-  - 单元测试位于`tests/unit/`
-  - 集成测试位于`tests/integration/`
-  - E2E测试位于`tests/e2e/` (Playwright)
+  - **packages/server**: 独立的服务器测试
+    - 单元测试位于`packages/server/tests/unit/`
+    - 集成测试位于`packages/server/tests/integration/`
+  - **web**: Web应用测试
+    - 单元测试位于`web/tests/unit/`
+    - 集成测试位于`web/tests/integration/`
+    - E2E测试位于`web/tests/e2e/` (Playwright)
 - **开发环境**: 多八多云端开发容器,包含Node.js 20.19.2、PostgreSQL 17、Redis 7、MinIO
 - **构建工具**: 使用Vite + pnpm,支持SSR构建
 - **架构优势**:

+ 64 - 20
docs/architecture/testing-strategy.md

@@ -3,20 +3,30 @@
 ## 版本信息
 | 版本 | 日期 | 描述 | 作者 |
 |------|------|------|------|
+| 2.7 | 2025-11-09 | 更新为monorepo测试架构,清理重复测试文件 | James |
 | 2.6 | 2025-10-15 | 完成遗留测试文件迁移到统一的tests目录结构 | Winston |
 | 2.5 | 2025-10-14 | 更新测试文件位置到统一的tests目录结构 | Claude |
 | 2.4 | 2025-09-20 | 更新测试策略与主架构文档版本一致 | Winston |
 
 ## 概述
 
-本文档定义了D8D Starter项目的完整测试策略,基于现有的测试基础设施和最佳实践。测试策略遵循测试金字塔模型,确保代码质量、功能稳定性和系统可靠性。
+本文档定义了D8D Starter项目的完整测试策略,基于monorepo架构和现有的测试基础设施。测试策略遵循测试金字塔模型,确保代码质量、功能稳定性和系统可靠性。
+
+### 测试架构更新 (v2.7)
+
+项目已重构为monorepo结构,测试架构相应调整为:
+- **packages/server**: 独立的API服务器包,包含单元测试和集成测试
+- **web**: Web应用,包含组件测试、集成测试和E2E测试
+- **CI/CD**: 独立的工作流分别处理server和web的测试
 
 ## 测试金字塔策略
 
 ### 单元测试 (Unit Tests)
 - **范围**: 单个函数、类或组件
 - **目标**: 验证独立单元的correctness
-- **位置**: `tests/unit/**/*.test.{ts,tsx}`
+- **位置**:
+  - `packages/server/tests/unit/**/*.test.ts` (服务器单元测试)
+  - `web/tests/unit/**/*.test.{ts,tsx}` (Web组件单元测试)
 - **框架**: Vitest
 - **覆盖率目标**: ≥ 80%
 - **执行频率**: 每次代码变更
@@ -24,7 +34,9 @@
 ### 集成测试 (Integration Tests)
 - **范围**: 多个组件/服务协作
 - **目标**: 验证模块间集成和交互
-- **位置**: `tests/integration/**/*.test.{ts,tsx}`
+- **位置**:
+  - `packages/server/tests/integration/**/*.test.ts` (服务器集成测试)
+  - `web/tests/integration/**/*.test.{ts,tsx}` (Web集成测试)
 - **框架**: Vitest + Testing Library + hono/testing
 - **覆盖率目标**: ≥ 60%
 - **执行频率**: 每次API变更
@@ -32,7 +44,7 @@
 ### E2E测试 (End-to-End Tests)
 - **范围**: 完整用户流程
 - **目标**: 验证端到端业务流程
-- **位置**: `tests/e2e/**/*.test.{ts,tsx}`
+- **位置**: `web/tests/e2e/**/*.test.{ts,tsx}`
 - **框架**: Playwright
 - **覆盖率目标**: 关键用户流程100%
 - **执行频率**: 每日或每次重大变更
@@ -72,31 +84,42 @@ export default defineConfig({
 
 ### CI/CD环境
 ```yaml
-# GitHub Actions 测试配置
+# GitHub Actions 测试配置 (Monorepo架构)
 name: Test Pipeline
 
 jobs:
-  unit-tests:
+  server-tests:
     runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres:17
+        env:
+          POSTGRES_PASSWORD: test_password
+          POSTGRES_DB: test_d8dai
     steps:
-      - run: npm run test:api
-      - run: npm run test:components
+      - run: cd packages/server && pnpm test:unit
+      - run: cd packages/server && pnpm test:integration
 
-  integration-tests:
+  web-integration-tests:
     runs-on: ubuntu-latest
     services:
       postgres:
-        image: postgres:15
+        image: postgres:17
         env:
-          POSTGRES_PASSWORD: postgres
-          POSTGRES_DB: test_db
+          POSTGRES_PASSWORD: test_password
+          POSTGRES_DB: test_d8dai
     steps:
-      - run: npm run test:integration
+      - run: cd web && pnpm test:integration
 
-  e2e-tests:
+  web-component-tests:
     runs-on: ubuntu-latest
     steps:
-      - run: npm run test:e2e:chromium
+      - run: cd web && pnpm test:components
+
+  web-e2e-tests:
+    runs-on: ubuntu-latest
+    steps:
+      - run: cd web && pnpm test:e2e:chromium
 ```
 
 ## 测试覆盖率标准
@@ -147,21 +170,41 @@ const inactiveUser = createTestUser({ active: false });
 ## 测试执行流程
 
 ### 本地开发测试
+
+#### packages/server
+```bash
+# 运行所有测试
+pnpm test
+
+# 运行单元测试
+pnpm test:unit
+
+# 运行集成测试
+pnpm test:integration
+
+# 生成覆盖率报告
+pnpm test:coverage
+```
+
+#### web
 ```bash
 # 运行所有测试
-npm test
+pnpm test
 
 # 运行单元测试
-npm run test:unit
+pnpm test:unit
 
 # 运行集成测试
-npm run test:integration
+pnpm test:integration
+
+# 运行组件测试
+pnpm test:components
 
 # 运行E2E测试
-npm run test:e2e:chromium
+pnpm test:e2e:chromium
 
 # 生成覆盖率报告
-npm run test:coverage
+pnpm test:coverage
 ```
 
 ### CI/CD流水线测试
@@ -283,6 +326,7 @@ describe('UserService', () => {
 ### 更新日志
 | 日期 | 版本 | 描述 |
 |------|------|------|
+| 2025-11-09 | 2.7 | 更新为monorepo测试架构,清理重复测试文件 |
 | 2025-10-15 | 2.6 | 完成遗留测试文件迁移到统一的tests目录结构 |
 | 2025-10-14 | 2.5 | 重构测试文件结构,统一到tests目录 |
 | 2025-09-20 | 2.4 | 更新版本与主架构文档一致 |

+ 38 - 0
mini/jest.config.js

@@ -0,0 +1,38 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'jsdom',
+  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^~/(.*)$': '<rootDir>/tests/$1',
+    '^@tarojs/taro$': '<rootDir>/tests/__mocks__/taroMock.ts',
+    '\.(css|less|scss|sass)$': '<rootDir>/tests/__mocks__/styleMock.js',
+    '\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+      '<rootDir>/tests/__mocks__/fileMock.js'
+  },
+  testMatch: [
+    '<rootDir>/tests/**/*.spec.{ts,tsx}',
+    '<rootDir>/tests/**/*.test.{ts,tsx}'
+  ],
+  collectCoverageFrom: [
+    'src/**/*.{ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/**/index.{ts,tsx}',
+    '!src/**/*.stories.{ts,tsx}'
+  ],
+  coverageDirectory: 'coverage',
+  coverageReporters: ['text', 'lcov', 'html'],
+  testPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/coverage/'
+  ],
+  transform: {
+    '^.+\\.(ts|tsx)$': 'babel-jest',
+    '^.+\\.(js|jsx)$': 'babel-jest'
+  },
+  transformIgnorePatterns: [
+    '/node_modules/(?!(swiper|@tarojs)/)'
+  ],
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
+}

+ 13 - 1
mini/package.json

@@ -31,6 +31,11 @@
     "dev:qq": "npm run build:qq -- --watch",
     "dev:jd": "npm run build:jd -- --watch",
     "dev:harmony-hybrid": "npm run build:harmony-hybrid -- --watch",
+    "test": "jest",
+    "test:watch": "jest --watch",
+    "test:coverage": "jest --coverage",
+    "test:components": "jest tests/components",
+    "test:pages": "jest tests/pages",
     "typecheck": "tsc --noEmit --project ."
   },
   "browserslist": {
@@ -111,6 +116,13 @@
     "typescript": "^5.4.5",
     "weapp-tailwindcss": "^4.2.5",
     "webpack": "5.91.0",
-    "webpack-plugin-iframe-communicator": "^0.0.10"
+    "webpack-plugin-iframe-communicator": "^0.0.10",
+    "@testing-library/jest-dom": "^6.8.0",
+    "@testing-library/react": "^16.3.0",
+    "@testing-library/user-event": "^14.6.1",
+    "@types/jest": "^29.5.14",
+    "jest": "^30.2.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "ts-jest": "^29.4.5"
   }
 }

+ 95 - 0
mini/src/components/ui/dialog.tsx

@@ -0,0 +1,95 @@
+import { useEffect } from 'react'
+import { View, Text } from '@tarojs/components'
+import { cn } from '@/utils/cn'
+
+interface DialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  children: React.ReactNode
+}
+
+export function Dialog({ open, onOpenChange, children }: DialogProps) {
+  useEffect(() => {
+    if (open) {
+      // 在 Taro 中,我们可以使用模态框或者自定义弹窗
+      // 这里使用自定义实现
+    }
+  }, [open])
+
+  const handleBackdropClick = () => {
+    onOpenChange(false)
+  }
+
+  const handleContentClick = (e: any) => {
+    // 阻止事件冒泡,避免点击内容区域时关闭弹窗
+    e.stopPropagation()
+  }
+
+  if (!open) return null
+
+  return (
+    <View
+      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
+      onClick={handleBackdropClick}
+    >
+      <View
+        className="relative bg-white rounded-lg shadow-lg max-w-md w-full mx-4"
+        onClick={handleContentClick}
+      >
+        {children}
+      </View>
+    </View>
+  )
+}
+
+interface DialogContentProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogContent({ className, children }: DialogContentProps) {
+  return (
+    <View className={cn("p-6", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface DialogHeaderProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogHeader({ className, children }: DialogHeaderProps) {
+  return (
+    <View className={cn("mb-4", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface DialogTitleProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogTitle({ className, children }: DialogTitleProps) {
+  return (
+    <Text className={cn("text-lg font-semibold text-gray-900", className)}>
+      {children}
+    </Text>
+  )
+}
+
+interface DialogFooterProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogFooter({ className, children }: DialogFooterProps) {
+  return (
+    <View className={cn("flex justify-end space-x-2", className)}>
+      {children}
+    </View>
+  )
+}

+ 1 - 0
mini/tests/__mocks__/fileMock.js

@@ -0,0 +1 @@
+module.exports = 'test-file-stub'

+ 1 - 0
mini/tests/__mocks__/styleMock.js

@@ -0,0 +1 @@
+module.exports = {}

+ 100 - 0
mini/tests/__mocks__/taroMock.ts

@@ -0,0 +1,100 @@
+/**
+ * Taro API Mock 文件
+ * 通过 jest.config.js 的 moduleNameMapper 重定向 @tarojs/taro 到这里
+ */
+
+// 创建所有 Taro API 的 mock 函数
+export const mockShowToast = jest.fn()
+export const mockShowLoading = jest.fn()
+export const mockHideLoading = jest.fn()
+export const mockNavigateTo = jest.fn()
+export const mockNavigateBack = jest.fn()
+export const mockSwitchTab = jest.fn()
+export const mockShowModal = jest.fn()
+export const mockReLaunch = jest.fn()
+export const mockOpenCustomerServiceChat = jest.fn()
+export const mockUseRouter = jest.fn()
+export const mockRequestPayment = jest.fn()
+export const mockGetEnv = jest.fn()
+export const mockUseLoad = jest.fn()
+export const mockUseShareAppMessage = jest.fn()
+export const mockUseShareTimeline = jest.fn()
+export const mockGetCurrentInstance = jest.fn()
+
+// 环境类型常量
+export const ENV_TYPE = {
+  WEAPP: 'WEAPP',
+  WEB: 'WEB',
+  RN: 'RN',
+  SWAN: 'SWAN',
+  ALIPAY: 'ALIPAY',
+  TT: 'TT',
+  QQ: 'QQ',
+  JD: 'JD',
+  HARMONY: 'HARMONY'
+}
+
+// 导出所有 mock 函数,便于在测试中访问
+export default {
+  // UI 相关
+  showToast: mockShowToast,
+  showLoading: mockShowLoading,
+  hideLoading: mockHideLoading,
+  showModal: mockShowModal,
+
+  // 导航相关
+  navigateTo: mockNavigateTo,
+  navigateBack: mockNavigateBack,
+  switchTab: mockSwitchTab,
+  reLaunch: mockReLaunch,
+  useRouter: () => mockUseRouter(),
+  useLoad: (callback: any) => mockUseLoad(callback),
+
+  // 微信相关
+  openCustomerServiceChat: mockOpenCustomerServiceChat,
+  requestPayment: mockRequestPayment,
+
+  // 系统信息
+  getSystemInfoSync: () => ({
+    statusBarHeight: 20
+  }),
+  getMenuButtonBoundingClientRect: () => ({
+    width: 87,
+    height: 32,
+    top: 48,
+    right: 314,
+    bottom: 80,
+    left: 227
+  }),
+  getEnv: mockGetEnv,
+
+  // 分享相关
+  useShareAppMessage: mockUseShareAppMessage,
+  useShareTimeline: mockUseShareTimeline,
+
+  // 实例相关
+  getCurrentInstance: mockGetCurrentInstance,
+
+  // 环境类型常量
+  ENV_TYPE
+}
+
+// 为命名导入导出所有函数
+export {
+  mockShowToast as showToast,
+  mockShowLoading as showLoading,
+  mockHideLoading as hideLoading,
+  mockShowModal as showModal,
+  mockNavigateTo as navigateTo,
+  mockNavigateBack as navigateBack,
+  mockSwitchTab as switchTab,
+  mockReLaunch as reLaunch,
+  mockUseRouter as useRouter,
+  mockUseLoad as useLoad,
+  mockOpenCustomerServiceChat as openCustomerServiceChat,
+  mockRequestPayment as requestPayment,
+  mockGetEnv as getEnv,
+  mockUseShareAppMessage as useShareAppMessage,
+  mockUseShareTimeline as useShareTimeline,
+  mockGetCurrentInstance as getCurrentInstance
+}

+ 37 - 0
mini/tests/components/Button.test.tsx

@@ -0,0 +1,37 @@
+import { render, screen, fireEvent } from '@testing-library/react'
+import { Button } from '@tarojs/components'
+
+describe('Button 组件测试', () => {
+  test('应该正确渲染按钮', () => {
+    render(<Button>测试按钮</Button>)
+
+    const button = screen.getByRole('button')
+    expect(button).toBeInTheDocument()
+    expect(button).toHaveTextContent('测试按钮')
+  })
+
+  test('应该响应点击事件', () => {
+    const handleClick = jest.fn()
+
+    render(<Button onClick={handleClick}>可点击按钮</Button>)
+
+    const button = screen.getByRole('button')
+    fireEvent.click(button)
+
+    expect(handleClick).toHaveBeenCalledTimes(1)
+  })
+
+  test('应该禁用按钮', () => {
+    render(<Button disabled>禁用按钮</Button>)
+
+    const button = screen.getByRole('button')
+    expect(button).toBeDisabled()
+  })
+
+  test('应该应用自定义类名', () => {
+    render(<Button className="custom-class">自定义按钮</Button>)
+
+    const button = screen.getByRole('button')
+    expect(button).toHaveClass('custom-class')
+  })
+})

+ 43 - 0
mini/tests/example.test.tsx

@@ -0,0 +1,43 @@
+import { render, screen, fireEvent } from '@testing-library/react'
+import { Text, View } from '@tarojs/components'
+
+// 简单的测试组件
+const TestComponent = () => {
+  return (
+    <View className="test-component">
+      <Text className="btn">点击我</Text>
+    </View>
+  )
+}
+
+describe('Taro 组件测试示例', () => {
+  test('应该正确渲染组件', () => {
+    render(<TestComponent />)
+
+    const button = screen.getByText('点击我')
+    expect(button).toBeInTheDocument()
+    expect(button).toHaveClass('btn')
+  })
+
+  test('应该响应点击事件', () => {
+    const handleClick = jest.fn()
+
+    const InteractiveComponent = () => (
+      <View className="test-component">
+        <Text className="btn" onClick={handleClick}>点击我</Text>
+      </View>
+    )
+
+    render(<InteractiveComponent />)
+
+    const button = screen.getByText('点击我')
+    fireEvent.click(button)
+
+    expect(handleClick).toHaveBeenCalledTimes(1)
+  })
+
+  test('应该匹配快照', () => {
+    const { container } = render(<TestComponent />)
+    expect(container.firstChild).toMatchSnapshot()
+  })
+})

+ 432 - 0
mini/tests/setup.ts

@@ -0,0 +1,432 @@
+import '@testing-library/jest-dom'
+
+/* eslint-disable react/display-name */
+
+// 设置环境变量
+process.env.TARO_ENV = 'h5'
+process.env.TARO_PLATFORM = 'web'
+process.env.SUPPORT_TARO_POLYFILL = 'disabled'
+
+// Mock Taro 组件
+// eslint-disable-next-line react/display-name
+jest.mock('@tarojs/components', () => {
+  const React = require('react')
+  const MockView = React.forwardRef((props: any, ref: any) => {
+    const { children, ...restProps } = props
+    return React.createElement('div', { ...restProps, ref }, children)
+  })
+  MockView.displayName = 'MockView'
+
+  const MockScrollView = React.forwardRef((props: any, ref: any) => {
+    const {
+      children,
+      onScroll,
+      onTouchStart,
+      onScrollEnd,
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      scrollY,
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      showScrollbar,
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      scrollTop,
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      scrollWithAnimation,
+      ...restProps
+    } = props
+    return React.createElement('div', {
+      ...restProps,
+      ref,
+      onScroll: (e: any) => {
+        if (onScroll) onScroll(e)
+      },
+      onTouchStart: (e: any) => {
+        if (onTouchStart) onTouchStart(e)
+      },
+      onTouchEnd: () => {
+        if (onScrollEnd) onScrollEnd()
+      },
+      style: {
+        overflow: 'auto',
+        height: '200px',
+        ...restProps.style
+      }
+    }, children)
+  })
+  MockScrollView.displayName = 'MockScrollView'
+
+  return {
+    View: MockView,
+    ScrollView: MockScrollView,
+    Text: (() => {
+      const MockText = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('span', { ...restProps, ref }, children)
+      })
+      MockText.displayName = 'MockText'
+      return MockText
+    })(),
+    Button: (() => {
+      const MockButton = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('button', { ...restProps, ref }, children)
+      })
+      MockButton.displayName = 'MockButton'
+      return MockButton
+    })(),
+    Input: (() => {
+      const MockInput = React.forwardRef((props: any, ref: any) => {
+        const { ...restProps } = props
+        return React.createElement('input', { ...restProps, ref })
+      })
+      MockInput.displayName = 'MockInput'
+      return MockInput
+    })(),
+    Textarea: (() => {
+      const MockTextarea = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('textarea', { ...restProps, ref }, children)
+      })
+      MockTextarea.displayName = 'MockTextarea'
+      return MockTextarea
+    })(),
+    Image: (() => {
+      const MockImage = React.forwardRef((props: any, ref: any) => {
+        const { ...restProps } = props
+        return React.createElement('img', { ...restProps, ref })
+      })
+      MockImage.displayName = 'MockImage'
+      return MockImage
+    })(),
+    Form: (() => {
+      const MockForm = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('form', { ...restProps, ref }, children)
+      })
+      MockForm.displayName = 'MockForm'
+      return MockForm
+    })(),
+    Label: (() => {
+      const MockLabel = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('label', { ...restProps, ref }, children)
+      })
+      MockLabel.displayName = 'MockLabel'
+      return MockLabel
+    })(),
+    Picker: (() => {
+      const MockPicker = React.forwardRef((props: any, ref: any) => {
+        const { children, ...restProps } = props
+        return React.createElement('div', { ...restProps, ref }, children)
+      })
+      MockPicker.displayName = 'MockPicker'
+      return MockPicker
+    })(),
+    Switch: (() => {
+      const MockSwitch = React.forwardRef((props: any, ref: any) => {
+        const { ...restProps } = props
+        return React.createElement('input', { type: 'checkbox', ...restProps, ref })
+      })
+      MockSwitch.displayName = 'MockSwitch'
+      return MockSwitch
+    })(),
+    Slider: (() => {
+      const MockSlider = React.forwardRef((props: any, ref: any) => {
+        const { ...restProps } = props
+        return React.createElement('input', { type: 'range', ...restProps, ref })
+      })
+      MockSlider.displayName = 'MockSlider'
+      return MockSlider
+    })(),
+    Radio: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('input', { type: 'radio', ...restProps, ref }, children)
+    }),
+    RadioGroup: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Checkbox: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('input', { type: 'checkbox', ...restProps, ref }, children)
+    }),
+    CheckboxGroup: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Progress: React.forwardRef((props: any, ref: any) => {
+      const { ...restProps } = props
+      return React.createElement('progress', { ...restProps, ref })
+    }),
+    RichText: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    MovableArea: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    MovableView: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Swiper: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    SwiperItem: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Navigator: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('a', { ...restProps, ref }, children)
+    }),
+    Audio: React.forwardRef((props: any, ref: any) => {
+      const { ...restProps } = props
+      return React.createElement('audio', { ...restProps, ref })
+    }),
+    Video: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('video', { ...restProps, ref }, children)
+    }),
+    Camera: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    LivePlayer: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    LivePusher: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Map: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Canvas: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('canvas', { ...restProps, ref }, children)
+    }),
+    OpenData: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    WebView: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('iframe', { ...restProps, ref }, children)
+    }),
+    Ad: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    OfficialAccount: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    CoverView: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    CoverImage: React.forwardRef((props: any, ref: any) => {
+      const { ...restProps } = props
+      return React.createElement('img', { ...restProps, ref })
+    }),
+    FunctionalPageNavigator: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    AdContent: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    MatchMedia: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    PageContainer: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    ShareElement: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    KeyboardAccessory: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    RootPortal: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    PageMeta: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    NavigationBar: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Block: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Import: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Include: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Template: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Slot: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    NativeSlot: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    CustomWrapper: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    Editor: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    VoipRoom: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    }),
+    AdCustom: React.forwardRef((props: any, ref: any) => {
+      const { children, ...restProps } = props
+      return React.createElement('div', { ...restProps, ref }, children)
+    })
+  }
+})
+
+// 模拟 MutationObserver
+// @ts-ignore
+global.MutationObserver = class {
+  disconnect() {}
+  observe(_element: any, _initObject: any) {}
+  takeRecords() { return [] }
+}
+
+// 模拟 IntersectionObserver
+// @ts-ignore
+global.IntersectionObserver = class {
+  constructor(fn: (args: any[]) => void) {
+    setTimeout(() => {
+      fn([{ isIntersecting: true }])
+    }, 1000)
+  }
+
+  observe() {}
+  unobserve() {}
+  disconnect() {}
+  takeRecords() { return [] }
+  root: null = null
+  rootMargin: string = ''
+  thresholds: number[] = []
+}
+
+// 模拟 ResizeObserver
+// @ts-ignore
+global.ResizeObserver = class {
+  observe() {}
+  unobserve() {}
+  disconnect() {}
+}
+
+// 模拟 matchMedia
+Object.defineProperty(window, 'matchMedia', {
+  writable: true,
+  value: jest.fn().mockImplementation(query => ({
+    matches: false,
+    media: query,
+    onchange: null,
+    addListener: jest.fn(), // deprecated
+    removeListener: jest.fn(), // deprecated
+    addEventListener: jest.fn(),
+    removeEventListener: jest.fn(),
+    dispatchEvent: jest.fn(),
+  })),
+})
+
+// 模拟 getComputedStyle
+Object.defineProperty(window, 'getComputedStyle', {
+  value: () => ({
+    getPropertyValue: (prop: string) => {
+      return {
+        'font-size': '16px',
+        'font-family': 'Arial',
+        color: 'rgb(0, 0, 0)',
+        'background-color': 'rgb(255, 255, 255)',
+        width: '100px',
+        height: '100px',
+        top: '0px',
+        left: '0px',
+        right: '0px',
+        bottom: '0px',
+        x: '0px',
+        y: '0px'
+      }[prop] || ''
+    }
+  })
+})
+
+// 模拟 Element.prototype.getBoundingClientRect
+Element.prototype.getBoundingClientRect = jest.fn(() => ({
+  width: 100,
+  height: 100,
+  top: 0,
+  left: 0,
+  bottom: 100,
+  right: 100,
+  x: 0,
+  y: 0,
+  toJSON: () => ({
+    width: 100,
+    height: 100,
+    top: 0,
+    left: 0,
+    bottom: 100,
+    right: 100,
+    x: 0,
+    y: 0
+  })
+}))
+
+// 静默 console.error 在测试中
+const originalConsoleError = console.error
+console.error = (...args: any[]) => {
+  // 检查是否在测试环境中(通过 Jest 环境变量判断)
+  const isTestEnv = process.env.JEST_WORKER_ID !== undefined ||
+                    typeof jest !== 'undefined'
+
+  // 在测试环境中静默错误输出,除非是重要错误
+  if (isTestEnv && !args[0]?.includes?.('重要错误')) {
+    return
+  }
+  originalConsoleError(...args)
+}
+
+// Mock 常用 UI 组件
+jest.mock('@/components/ui/dialog', () => {
+  const React = require('react')
+  return {
+    Dialog: ({ open, children }: any) => open ? React.createElement('div', { 'data-testid': 'dialog' }, children) : null,
+    DialogContent: ({ children, className }: any) => React.createElement('div', { className }, children),
+    DialogHeader: ({ children, className }: any) => React.createElement('div', { className }, children),
+    DialogTitle: ({ children, className }: any) => React.createElement('div', { className }, children),
+    DialogFooter: ({ children, className }: any) => React.createElement('div', { className }, children)
+  }
+})

+ 3 - 2
mini/tsconfig.json

@@ -21,10 +21,11 @@
       "node_modules/@types"
     ],
     "paths": {
-      "@/*": ["./src/*"]
+      "@/*": ["./src/*"],
+      "~/*": ["./tests/*"]
     }
   },
-  "include": ["./src", "./types", "./config"],
+  "include": ["./src", "./types", "./config", "./tests"],
   "exclude": [
     "node_modules",
     "dist"

+ 8 - 2
packages/server/package.json

@@ -20,7 +20,12 @@
   "scripts": {
     "build": "tsc",
     "dev": "tsc --watch",
-    "typecheck": "tsc --noEmit"
+    "typecheck": "tsc --noEmit",
+    "test": "vitest",
+    "test:unit": "vitest run tests/unit",
+    "test:integration": "vitest run tests/integration",
+    "test:coverage": "vitest --coverage",
+    "test:typecheck": "tsc --noEmit"
   },
   "dependencies": {
     "@asteasolutions/zod-to-openapi": "^8.1.0",
@@ -48,7 +53,8 @@
     "@types/pg": "^8.11.10",
     "tsc-alias": "^1.8.10",
     "typescript": "^5.8.3",
-    "@d8d/test-utils": "workspace:*"
+    "vitest": "^3.2.4",
+    "@vitest/coverage-v8": "^3.2.4"
   },
   "files": [
     "src"

+ 1 - 1
packages/server/src/modules/auth/auth.service.ts

@@ -70,7 +70,7 @@ export class AuthService {
   }
 
   generateToken(user: User, expiresIn?: string): string {
-    return JWTUtil.generateToken(user, expiresIn ? { expiresIn } as any : {});
+    return JWTUtil.generateToken(user, {}, expiresIn);
   }
 
   verifyToken(token: string): any {

+ 6 - 2
packages/server/src/utils/jwt.util.ts

@@ -22,9 +22,10 @@ export class JWTUtil {
    * 生成 JWT token
    * @param user 用户实体
    * @param additionalPayload 额外的 payload 数据
+   * @param expiresIn 过期时间
    * @returns JWT token
    */
-  static generateToken(user: UserEntity, additionalPayload: Partial<JWTPayload> = {}): string {
+  static generateToken(user: UserEntity, additionalPayload: Partial<JWTPayload> = {}, expiresIn?: string): string {
     if (!user.id || !user.username) {
       throw new Error('用户ID和用户名不能为空');
     }
@@ -38,7 +39,10 @@ export class JWTUtil {
     };
 
     try {
-      return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN as SignOptions['expiresIn']});
+      const options: SignOptions = {
+        expiresIn: expiresIn || JWT_EXPIRES_IN as SignOptions['expiresIn']
+      };
+      return jwt.sign(payload, JWT_SECRET, options);
     } catch (error) {
       logger.error('生成JWT token失败:', error);
       throw new Error('生成token失败');

+ 6 - 6
web/tests/integration/server/auth.integration.test.ts → packages/server/tests/integration/auth.integration.test.ts

@@ -4,12 +4,12 @@ import {
   IntegrationTestDatabase,
   setupIntegrationDatabaseHooks,
   TestDataFactory
-} from '@d8d/test-utils/server';
-import { UserEntity } from '@d8d/server/modules/users/user.entity';
-import { authRoutes } from '@d8d/server/api';
-import { AuthService } from '@d8d/server/modules/auth/auth.service';
-import { UserService } from '@d8d/server/modules/users/user.service';
-import { DisabledStatus } from '@d8d/server/share/types';
+} from '../utils/integration-test-db';
+import { UserEntity } from '../../src/modules/users/user.entity';
+import { authRoutes } from '../../src/api';
+import { AuthService } from '../../src/modules/auth/auth.service';
+import { UserService } from '../../src/modules/users/user.service';
+import { DisabledStatus } from '../../src/share/types';
 
 // 设置集成测试钩子
 setupIntegrationDatabaseHooks()

+ 3 - 3
web/tests/integration/server/backup.integration.test.ts → packages/server/tests/integration/backup.integration.test.ts

@@ -1,6 +1,6 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { databaseBackup } from '@d8d/server/utils/backup'
-import { databaseRestore } from '@d8d/server/utils/restore'
+import { databaseBackup } from '../../src/utils/backup'
+import { databaseRestore } from '../../src/utils/restore'
 import path from 'path'
 
 // Mock pg-dump-restore for integration tests
@@ -43,7 +43,7 @@ vi.mock('node-cron', async (importOriginal) => {
 })
 
 // Mock logger
-vi.mock('@d8d/server/utils/logger', () => ({
+vi.mock('../../src/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),

+ 9 - 9
web/tests/integration/server/files/files.integration.test.ts → packages/server/tests/integration/files.integration.test.ts

@@ -1,14 +1,14 @@
 import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
 import { testClient } from 'hono/testing';
-import { FileService } from '@d8d/server/modules/files/file.service';
-import { authMiddleware } from '@d8d/server/middleware/auth.middleware';
-import { fileApiRoutes } from '@d8d/server/api';
-import { ConcreteCrudService } from '@d8d/server/utils/concrete-crud.service';
-
-vi.mock('@d8d/server/modules/files/file.service');
-vi.mock('@d8d/server/middleware/auth.middleware');
-vi.mock('@d8d/server/data-source');
-vi.mock('@d8d/server/utils/concrete-crud.service');
+import { FileService } from '../../src/modules/files/file.service';
+import { authMiddleware } from '../../src/middleware/auth.middleware';
+import { fileApiRoutes } from '../../src/api';
+import { ConcreteCrudService } from '../../src/utils/concrete-crud.service';
+
+vi.mock('../../src/modules/files/file.service');
+vi.mock('../../src/middleware/auth.middleware');
+vi.mock('../../src/data-source');
+vi.mock('../../src/utils/concrete-crud.service');
 
 describe('File API Integration Tests', () => {
   let client: ReturnType<typeof testClient<typeof fileApiRoutes>>['api']['v1'];

+ 3 - 3
web/tests/integration/server/files/minio.integration.test.ts → packages/server/tests/integration/minio.integration.test.ts

@@ -1,11 +1,11 @@
 import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
-import { MinioService } from '@d8d/server/modules/files/minio.service';
+import { MinioService } from '../../src/modules/files/minio.service';
 import { Client } from 'minio';
-import { logger } from '@d8d/server/utils/logger';
+import { logger } from '../../src/utils/logger';
 
 // Mock dependencies
 vi.mock('minio');
-vi.mock('@d8d/server/utils/logger');
+vi.mock('../../src/utils/logger');
 
 // Mock process.env using vi.stubEnv for proper isolation
 beforeEach(() => {

+ 6 - 6
web/tests/integration/server/users.integration.test.ts → packages/server/tests/integration/users.integration.test.ts

@@ -3,12 +3,12 @@ import { testClient } from 'hono/testing';
 import {
   IntegrationTestDatabase,
   setupIntegrationDatabaseHooks,
-  TestDataFactory,
-  IntegrationTestAssertions
-} from '@d8d/test-utils/server';
-import { userRoutes } from '@d8d/server/api';
-import { AuthService } from '@d8d/server/modules/auth/auth.service';
-import { UserService } from '@d8d/server/modules/users/user.service';
+  TestDataFactory
+} from '../utils/integration-test-db';
+import { IntegrationTestAssertions } from '../utils/integration-test-utils';
+import { userRoutes } from '../../src/api';
+import { AuthService } from '../../src/modules/auth/auth.service';
+import { UserService } from '../../src/modules/users/user.service';
 
 
 // 设置集成测试钩子

+ 17 - 0
packages/server/tests/unit/example.test.ts

@@ -0,0 +1,17 @@
+import { describe, it, expect } from 'vitest'
+
+describe('示例单元测试', () => {
+  it('应该通过基本的数学运算测试', () => {
+    expect(1 + 1).toBe(2)
+  })
+
+  it('应该验证字符串操作', () => {
+    const str = 'hello'
+    expect(str.toUpperCase()).toBe('HELLO')
+  })
+
+  it('应该处理异步操作', async () => {
+    const result = await Promise.resolve(42)
+    expect(result).toBe(42)
+  })
+})

+ 6 - 6
web/tests/unit/server/modules/files/file.service.test.ts → packages/server/tests/unit/modules/file.service.test.ts

@@ -1,13 +1,13 @@
 import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
 import { DataSource } from 'typeorm';
-import { FileService } from '@d8d/server/modules/files/file.service';
-import { File } from '@d8d/server/modules/files/file.entity';
-import { MinioService } from '@d8d/server/modules/files/minio.service';
-import { logger } from '@d8d/server/utils/logger';
+import { FileService } from '@/modules/files/file.service';
+import { File } from '@/modules/files/file.entity';
+import { MinioService } from '@/modules/files/minio.service';
+import { logger } from '@/utils/logger';
 
 // Mock dependencies
-vi.mock('@d8d/server/modules/files/minio.service');
-vi.mock('@d8d/server/utils/logger');
+vi.mock('@/modules/files/minio.service');
+vi.mock('@/utils/logger');
 vi.mock('uuid', () => ({
   v4: () => 'test-uuid-123'
 }));

+ 3 - 3
web/tests/unit/server/modules/files/minio.service.test.ts → packages/server/tests/unit/modules/minio.service.test.ts

@@ -1,11 +1,11 @@
 import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
-import { MinioService } from '@d8d/server/modules/files/minio.service';
+import { MinioService } from '@/modules/files/minio.service';
 import { Client } from 'minio';
-import { logger } from '@d8d/server/utils/logger';
+import { logger } from '@/utils/logger';
 
 // Mock dependencies
 vi.mock('minio');
-vi.mock('@d8d/server/utils/logger');
+vi.mock('@/utils/logger');
 
 // Mock process.env using vi.stubEnv for proper isolation
 beforeEach(() => {

+ 2 - 2
web/tests/unit/server/modules/user.service.test.ts → packages/server/tests/unit/modules/user.service.test.ts

@@ -1,5 +1,5 @@
-import { UserService } from '@d8d/server/modules/users/user.service';
-import { UserEntity as User } from '@d8d/server/modules/users/user.entity';
+import { UserService } from '@/modules/users/user.service';
+import { UserEntity as User } from '@/modules/users/user.entity';
 import * as bcrypt from 'bcrypt';
 import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
 

+ 7 - 7
web/tests/unit/server/utils/backup.test.ts → packages/server/tests/unit/utils/backup.test.ts

@@ -1,5 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { DatabaseBackup } from '@d8d/server/utils/backup'
+import { DatabaseBackup } from '@/utils/backup'
 import path from 'path'
 
 // Mock pg-dump-restore
@@ -25,7 +25,7 @@ vi.mock('fs', () => ({
 }))
 
 // Mock logger
-vi.mock('@d8d/server/utils/logger', () => ({
+vi.mock('@/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),
@@ -66,7 +66,7 @@ describe('DatabaseBackup', () => {
 
     it('应该在创建目录失败时抛出错误', async () => {
       const fs = await import('fs')
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       vi.mocked(fs.promises.mkdir).mockRejectedValueOnce(new Error('创建目录失败'))
 
@@ -147,7 +147,7 @@ describe('DatabaseBackup', () => {
   describe('cleanupOldBackups', () => {
     it('应该清理7天前的旧备份', async () => {
       const fs = await import('fs')
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       const now = Date.now()
       const oldFileTime = now - (8 * 24 * 60 * 60 * 1000) // 8天前
@@ -167,7 +167,7 @@ describe('DatabaseBackup', () => {
 
     it('应该在清理失败时记录错误但不抛出', async () => {
       const fs = await import('fs')
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 
@@ -178,7 +178,7 @@ describe('DatabaseBackup', () => {
 
   describe('startScheduledBackups', () => {
     it('应该启动定时备份任务', async () => {
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       backup.startScheduledBackups()
 
@@ -189,7 +189,7 @@ describe('DatabaseBackup', () => {
 
   describe('stopScheduledBackups', () => {
     it('应该停止定时备份任务', async () => {
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       // 先启动再停止
       backup.startScheduledBackups()

+ 4 - 4
web/tests/unit/server/utils/restore.test.ts → packages/server/tests/unit/utils/restore.test.ts

@@ -1,5 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { DatabaseRestore } from '@d8d/server/utils/restore'
+import { DatabaseRestore } from '@/utils/restore'
 import path from 'path'
 
 // Mock pg-dump-restore
@@ -22,7 +22,7 @@ vi.mock('fs', async (importOriginal) => {
 })
 
 // Mock logger
-vi.mock('@d8d/server/utils/logger', () => ({
+vi.mock('@/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),
@@ -110,7 +110,7 @@ describe('DatabaseRestore', () => {
 
     it('应该在读取目录失败时返回null', async () => {
       const fs = await import('fs')
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 
@@ -141,7 +141,7 @@ describe('DatabaseRestore', () => {
 
     it('应该在读取目录失败时返回空数组', async () => {
       const fs = await import('fs')
-      const { logger } = await import('@d8d/server/utils/logger')
+      const { logger } = await import('@/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 

+ 3 - 3
web/tests/utils/server/integration-test-db.ts → packages/server/tests/utils/integration-test-db.ts

@@ -1,8 +1,8 @@
 import { DataSource } from 'typeorm';
 import { beforeEach, afterEach } from 'vitest';
-import { UserEntity } from '@d8d/server/modules/users/user.entity';
-import { Role } from '@d8d/server/modules/users/role.entity';
-import { AppDataSource } from '@d8d/server/data-source';
+import { UserEntity } from '../../src/modules/users/user.entity';
+import { Role } from '../../src/modules/users/role.entity';
+import { AppDataSource } from '../../src/data-source';
 
 /**
  * 集成测试数据库工具类 - 使用真实PostgreSQL数据库

+ 1 - 4
packages/test-utils/src/server/integration-test-utils.ts → packages/server/tests/utils/integration-test-utils.ts

@@ -1,8 +1,5 @@
-
 import { IntegrationTestDatabase } from './integration-test-db';
-import { UserEntity } from '@d8d/server/modules/users/user.entity';
-
-
+import { UserEntity } from '../../src/modules/users/user.entity';
 
 /**
  * 集成测试断言工具

+ 21 - 0
packages/server/tests/utils/setup.ts

@@ -0,0 +1,21 @@
+import { beforeAll, afterAll } from 'vitest'
+
+/**
+ * 全局测试设置文件
+ * 这个文件在运行任何测试之前执行
+ */
+
+// 全局测试设置
+beforeAll(async () => {
+  // 设置全局测试环境变量
+  process.env.NODE_ENV = 'test'
+
+  // 可以在这里初始化全局测试资源
+  console.log('测试环境初始化完成')
+})
+
+// 全局测试清理
+afterAll(async () => {
+  // 清理全局测试资源
+  console.log('测试环境清理完成')
+})

+ 3 - 2
packages/server/tsconfig.json

@@ -14,7 +14,7 @@
     "noUncheckedIndexedAccess": true,
     "noImplicitOverride": true,
     "outDir": "./dist",
-    "rootDir": "./src",
+    "rootDir": ".",
     "declaration": true,
     "skipLibCheck": true,
     
@@ -28,7 +28,8 @@
     }
   },
   "include": [
-    "src/**/*"
+    "src/**/*",
+    "tests/**/*"
   ],
   "exclude": [
     "node_modules",

+ 51 - 0
packages/server/vitest.config.ts

@@ -0,0 +1,51 @@
+import { defineConfig } from 'vitest/config'
+import { resolve } from 'path'
+
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'node',
+    include: [
+      'tests/unit/**/*.test.{ts,js}',
+      'tests/integration/**/*.test.{ts,js}'
+    ],
+    exclude: [
+      '**/node_modules/**',
+      '**/dist/**',
+      '**/build/**',
+      '**/coverage/**'
+    ],
+    alias: {
+      '@': resolve(__dirname, './src'),
+    },
+    testTimeout: 10000,
+    setupFiles: ['./tests/utils/setup.ts'],
+    // 覆盖率配置
+    coverage: {
+      provider: 'v8',
+      reporter: ['text', 'lcov', 'html'],
+      reportsDirectory: './coverage',
+      exclude: [
+        '**/node_modules/**',
+        '**/dist/**',
+        '**/build/**',
+        '**/coverage/**',
+        '**/*.d.ts',
+        'tests/**',
+        'scripts/**',
+        '**/index.ts',
+        '**/types.ts',
+        'vitest.config.ts',
+        'data-source.ts'
+      ],
+      thresholds: {
+        branches: 65,
+        functions: 65,
+        lines: 65,
+        statements: 65
+      }
+    },
+    // 关闭并行测试以避免数据库连接冲突
+    fileParallelism: false
+  },
+})

+ 0 - 58
packages/test-utils/package.json

@@ -1,58 +0,0 @@
-{
-  "name": "@d8d/test-utils",
-  "version": "1.0.0",
-  "type": "module",
-  "description": "D8D Shared Test Utilities",
-  "main": "src/index.ts",
-  "types": "src/index.ts",
-  "exports": {
-    ".": {
-      "import": "./src/index.ts",
-      "require": "./src/index.ts",
-      "types": "./src/index.ts"
-    },
-    "./server": {
-      "import": "./src/server/index.ts",
-      "require": "./src/server/index.ts",
-      "types": "./src/server/index.ts"
-    },
-    "./client": {
-      "import": "./src/client/index.ts",
-      "require": "./src/client/index.ts",
-      "types": "./src/client/index.ts"
-    }
-  },
-  "scripts": {
-    "build": "tsc",
-    "dev": "tsc --watch",
-    "typecheck": "tsc --noEmit",
-    "test": "vitest",
-    "test:run": "vitest run"
-  },
-  "dependencies": {
-    "@testing-library/jest-dom": "^6.8.0",
-    "@testing-library/react": "^16.3.0",
-    "@testing-library/user-event": "^14.6.1",
-    "@types/node": "^20.10.5",
-    "next-themes": "^0.4.6"
-  },
-  "devDependencies": {
-    "@hono/zod-openapi": "^1.0.2",
-    "@tanstack/react-query": "^5.83.0",
-    "@types/react": "^19.1.8",
-    "@types/react-dom": "^19.1.6",
-    "react-router-dom": "^7.7.0",
-    "typeorm": "^0.3.25",
-    "typescript": "^5.8.3",
-    "vitest": "^3.2.4"
-  },
-  "peerDependencies": {
-    "@d8d/server": "workspace:*",
-    "hono": "^4.8.5",
-    "react": "^19.1.0",
-    "react-dom": "^19.1.0"
-  },
-  "files": [
-    "src"
-  ]
-}

+ 0 - 54
packages/test-utils/src/client/index.ts

@@ -1,54 +0,0 @@
-/**
- * 客户端测试工具
- *
- * 为 React 组件和前端测试提供工具函数
- */
-
-/**
- * React Query 测试工具
- */
-export {
-  createTestQueryClient,
-  TestQueryProvider,
-  mockUseQuery,
-  mockUseMutation,
-  waitForQueryToFinish,
-  mockNetworkError,
-  mockServerError
-} from './test-query.js';
-
-
-/**
- * React Router 测试工具
- */
-export {
-  TestRouter,
-  useTestLocation,
-  createTestNavigation
-} from './test-router.js';
-
-/**
- * 客户端测试配置
- */
-export interface ClientTestConfig {
-  /** 是否启用严格模式 */
-  strictMode?: boolean;
-  /** 路由配置 */
-  router?: {
-    initialEntries?: string[];
-    initialIndex?: number;
-  };
-  /** 查询客户端配置 */
-  queryClient?: any;
-}
-
-/**
- * 默认客户端测试配置
- */
-export const defaultClientTestConfig: ClientTestConfig = {
-  strictMode: true,
-  router: {
-    initialEntries: ['/'],
-    initialIndex: 0
-  }
-};

+ 0 - 92
packages/test-utils/src/client/test-query.tsx

@@ -1,92 +0,0 @@
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { ReactNode } from 'react';
-import { vi } from 'vitest';
-
-/**
- * 创建测试用的QueryClient(带默认配置)
- */
-export function createTestQueryClient(options = {}) {
-  return new QueryClient({
-    defaultOptions: {
-      queries: {
-        retry: false,
-        gcTime: 0,
-        staleTime: 0,
-      },
-      mutations: {
-        retry: false,
-      },
-    },
-    ...options
-  });
-}
-
-/**
- * QueryProvider包装组件
- */
-export function TestQueryProvider({
-  children,
-  client
-}: {
-  children: ReactNode;
-  client?: QueryClient
-}) {
-  const queryClient = client || createTestQueryClient();
-  
-  return (
-    <QueryClientProvider client={queryClient}>
-      {/* @ts-ignore */}
-      {children}
-    </QueryClientProvider>
-  );
-}
-
-/**
- * Mock查询Hook
- */
-export function mockUseQuery(data: any, isLoading = false, error: any = null) {
-  return vi.fn().mockReturnValue({
-    data,
-    isLoading,
-    isError: !!error,
-    error,
-    isSuccess: !isLoading && !error,
-    refetch: vi.fn(),
-  });
-}
-
-/**
- * Mock变更Hook
- */
-export function mockUseMutation() {
-  return vi.fn().mockReturnValue({
-    mutate: vi.fn(),
-    mutateAsync: vi.fn().mockResolvedValue({}),
-    isLoading: false,
-    isError: false,
-    error: null,
-    isSuccess: false,
-    reset: vi.fn(),
-  });
-}
-
-/**
- * 等待查询完成
- */
-export async function waitForQueryToFinish(delay = 100) {
-  await new Promise(resolve => setTimeout(resolve, delay));
-}
-
-/**
- * 模拟网络错误
- */
-export function mockNetworkError() {
-  return new Error('Network error');
-}
-
-/**
- * 模拟服务器错误
- */
-export function mockServerError() {
-  return new Error('Server error');
-}

+ 0 - 49
packages/test-utils/src/client/test-router.tsx

@@ -1,49 +0,0 @@
-import { ReactNode } from 'react';
-import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom';
-import { vi } from 'vitest';
-
-/**
- * 测试路由器的包装组件
- */
-export function TestRouter({
-  children,
-  initialPath = '/',
-  routes = []
-}: {
-  children: ReactNode;
-  initialPath?: string;
-  routes?: Array<{ path: string; element: ReactNode }>;
-}) {
-  return (
-    <MemoryRouter initialEntries={[initialPath]}>
-      <Routes>
-        {routes.map((route, index) => (
-          // @ts-ignore
-          <Route key={index} path={route.path} element={route.element} />
-        ))}
-        {/* @ts-ignore */}
-        <Route path="*" element={children} />
-      </Routes>
-    </MemoryRouter>
-  );
-}
-
-/**
- * 获取当前路由位置的Hook
- */
-export function useTestLocation() {
-  const location = useLocation();
-  return location;
-}
-
-/**
- * 创建测试导航函数
- */
-export function createTestNavigation() {
-  return {
-    navigate: vi.fn(),
-    goBack: vi.fn(),
-    goForward: vi.fn(),
-    replace: vi.fn()
-  };
-}

+ 0 - 57
packages/test-utils/src/index.ts

@@ -1,57 +0,0 @@
-/**
- * D8D Shared Test Utilities
- *
- * 统一的测试工具包,为服务器和客户端测试提供共享工具函数
- */
-
-// 通用测试工具
-export {
-  createTestServer,
-  createMockUser,
-  wait
-} from './test-utils.js';
-export * from './setup.js';
-
-// 服务器测试工具
-export {
-  createMockDataSource,
-  createMockRepository,
-  createMockAuthContext
-} from './server/index.js';
-
-// 客户端测试配置
-export type { ClientTestConfig } from './client/index.js';
-export { defaultClientTestConfig } from './client/index.js';
-
-// 服务器测试配置
-export type { ServerTestConfig } from './server/index.js';
-export { defaultServerTestConfig } from './server/index.js';
-
-/**
- * 测试工具包配置接口
- */
-export interface TestUtilsConfig {
-  /** 测试环境配置 */
-  environment: 'test' | 'development' | 'production';
-  /** 是否启用详细日志 */
-  verbose?: boolean;
-  /** 测试数据库配置 */
-  database?: {
-    host: string;
-    port: number;
-    name: string;
-  };
-}
-
-/**
- * 测试工具包基础配置
- */
-export const defaultTestConfig: TestUtilsConfig = {
-  environment: 'test',
-  verbose: false,
-  database: {
-    host: 'localhost',
-    port: 5432,
-    name: 'test_db'
-  }
-};

+ 0 - 92
packages/test-utils/src/server/index.ts

@@ -1,92 +0,0 @@
-/**
- * 服务器端测试工具
- *
- * 为 Hono 服务器和 API 测试提供工具函数
- */
-
-/**
- * 集成测试数据库工具
- */
-export {
-  IntegrationTestDatabase,
-  TestDataFactory,
-  setupIntegrationDatabaseHooks
-} from './integration-test-db.js';
-
-/**
- * 集成测试工具函数
- */
-export {
-  IntegrationTestAssertions
-} from './integration-test-utils.js';
-
-/**
- * 认证测试工具
- */
-export {
-  createMockAuthContext,
-  createMockJwtPayload,
-  createMockAuthMiddleware,
-  createMockPermissionMiddleware
-} from './test-auth.js';
-
-/**
- * 数据库测试工具
- */
-export {
-  createMockDataSource,
-  createMockEntityManager,
-  createMockRepository,
-  createMockQueryBuilder,
-  setupDatabaseHooks
-} from './test-db.js';
-
-/**
- * 服务模拟工具
- */
-export {
-  createHttpServiceMock,
-  createAuthServiceMock,
-  createEmailServiceMock,
-  createStorageServiceMock,
-  createPaymentServiceMock,
-  createSmsServiceMock,
-  createThirdPartyApiMock,
-  mockNetworkDelay,
-  mockHttpError,
-  mockTimeoutError
-} from './service-mocks.js';
-
-/**
- * 服务存根工具
- */
-export {
-  createMockUserService,
-  createMockAuthService,
-  createMockRoleService,
-  createMockCrudService,
-  createMockEmailService,
-  createMockFileService,
-  serviceStubs,
-  setupServiceMocks
-} from './service-stubs.js';
-
-/**
- * 服务器测试配置
- */
-export interface ServerTestConfig {
-  /** API 基础路径 */
-  baseUrl: string;
-  /** 认证令牌 */
-  authToken?: string;
-  /** 请求超时时间(毫秒) */
-  timeout?: number;
-}
-
-/**
- * 默认服务器测试配置
- */
-export const defaultServerTestConfig: ServerTestConfig = {
-  baseUrl: 'http://localhost:8080',
-  timeout: 5000
-};

+ 0 - 99
packages/test-utils/src/server/integration-test-db.ts

@@ -1,99 +0,0 @@
-import { DataSource } from 'typeorm';
-import { beforeEach, afterEach } from 'vitest';
-import { UserEntity } from '@d8d/server/modules/users/user.entity';
-import { Role } from '@d8d/server/modules/users/role.entity';
-import { AppDataSource } from '@d8d/server/data-source';
-
-/**
- * 集成测试数据库工具类 - 使用真实PostgreSQL数据库
- */
-export class IntegrationTestDatabase {
-  /**
-   * 清理集成测试数据库
-   */
-  static async cleanup(): Promise<void> {
-    if (AppDataSource.isInitialized) {
-      await AppDataSource.destroy();
-    }
-  }
-
-  /**
-   * 获取当前数据源
-   */
-  static async getDataSource(): Promise<DataSource> {
-    if(!AppDataSource.isInitialized) {
-      await AppDataSource.initialize();
-    }
-    return AppDataSource
-  }
-}
-
-/**
- * 测试数据工厂类
- */
-export class TestDataFactory {
-  /**
-   * 创建测试用户数据
-   */
-  static createUserData(overrides: Partial<UserEntity> = {}): Partial<UserEntity> {
-    const timestamp = Date.now();
-    return {
-      username: `testuser_${timestamp}`,
-      password: 'TestPassword123!',
-      email: `test_${timestamp}@example.com`,
-      phone: `138${timestamp.toString().slice(-8)}`,
-      nickname: `Test User ${timestamp}`,
-      name: `Test Name ${timestamp}`,
-      isDisabled: 0,
-      isDeleted: 0,
-      ...overrides
-    };
-  }
-
-  /**
-   * 创建测试角色数据
-   */
-  static createRoleData(overrides: Partial<Role> = {}): Partial<Role> {
-    const timestamp = Date.now();
-    return {
-      name: `test_role_${timestamp}`,
-      description: `Test role description ${timestamp}`,
-      ...overrides
-    };
-  }
-
-  /**
-   * 在数据库中创建测试用户
-   */
-  static async createTestUser(dataSource: DataSource, overrides: Partial<UserEntity> = {}): Promise<UserEntity> {
-    const userData = this.createUserData(overrides);
-    const userRepository = dataSource.getRepository(UserEntity);
-
-    const user = userRepository.create(userData);
-    return await userRepository.save(user);
-  }
-
-  /**
-   * 在数据库中创建测试角色
-   */
-  static async createTestRole(dataSource: DataSource, overrides: Partial<Role> = {}): Promise<Role> {
-    const roleData = this.createRoleData(overrides);
-    const roleRepository = dataSource.getRepository(Role);
-
-    const role = roleRepository.create(roleData);
-    return await roleRepository.save(role);
-  }
-}
-
-/**
- * 集成测试数据库生命周期钩子
- */
-export function setupIntegrationDatabaseHooks() {
-  beforeEach(async () => {
-    await IntegrationTestDatabase.getDataSource();
-  });
-
-  afterEach(async () => {
-    await IntegrationTestDatabase.cleanup();
-  });
-}

+ 0 - 164
packages/test-utils/src/server/service-mocks.ts

@@ -1,164 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建HTTP服务mock
- */
-export function createHttpServiceMock() {
-  return {
-    get: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-    post: vi.fn().mockResolvedValue({ data: {}, status: 201 }),
-    put: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-    delete: vi.fn().mockResolvedValue({ data: {}, status: 204 }),
-    patch: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-  };
-}
-
-/**
- * 创建认证服务mock
- */
-export function createAuthServiceMock() {
-  return {
-    verifyToken: vi.fn().mockResolvedValue({ userId: 1, username: 'testuser' }),
-    generateToken: vi.fn().mockReturnValue('mock-jwt-token'),
-    refreshToken: vi.fn().mockResolvedValue({ accessToken: 'new-token', refreshToken: 'new-refresh-token' }),
-    invalidateToken: vi.fn().mockResolvedValue(undefined),
-  };
-}
-
-/**
- * 创建邮件服务mock
- */
-export function createEmailServiceMock() {
-  return {
-    sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendPasswordResetEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true }),
-  };
-}
-
-/**
- * 创建文件存储服务mock
- */
-export function createStorageServiceMock() {
-  return {
-    uploadFile: vi.fn().mockResolvedValue({ url: 'https://example.com/file.jpg', key: 'file-key' }),
-    deleteFile: vi.fn().mockResolvedValue({ success: true }),
-    getFileUrl: vi.fn().mockReturnValue('https://example.com/file.jpg'),
-    listFiles: vi.fn().mockResolvedValue([]),
-  };
-}
-
-/**
- * 创建支付服务mock
- */
-export function createPaymentServiceMock() {
-  return {
-    createPaymentIntent: vi.fn().mockResolvedValue({ clientSecret: 'pi_mock_secret', id: 'pi_mock_id' }),
-    confirmPayment: vi.fn().mockResolvedValue({ success: true, transactionId: 'txn_mock_id' }),
-    refundPayment: vi.fn().mockResolvedValue({ success: true, refundId: 'ref_mock_id' }),
-  };
-}
-
-/**
- * 创建短信服务mock
- */
-export function createSmsServiceMock() {
-  return {
-    sendVerificationCode: vi.fn().mockResolvedValue({ success: true, messageId: 'sms_mock_id' }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true, messageId: 'sms_mock_id' }),
-  };
-}
-
-/**
- * 创建第三方API服务mock
- */
-export function createThirdPartyApiMock() {
-  return {
-    call: vi.fn().mockResolvedValue({ success: true, data: {} }),
-    validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }),
-    webhook: vi.fn().mockResolvedValue({ received: true }),
-  };
-}
-
-/**
- * 模拟网络延迟
- */
-export function mockNetworkDelay(delayMs: number) {
-  return new Promise(resolve => setTimeout(resolve, delayMs));
-}
-
-/**
- * 模拟HTTP错误响应
- */
-export function mockHttpError(status: number, message: string) {
-  return {
-    response: {
-      status,
-      data: { error: message }
-    }
-  };
-}
-
-/**
- * 模拟超时错误
- */
-export function mockTimeoutError() {
-  return new Error('Request timeout');
-}
-
-/**
- * 模拟网络断开错误
- */
-export function mockNetworkError() {
-  return new Error('Network error');
-}
-
-/**
- * 服务mock工具类
- */
-export class ServiceMocks {
-  static http = createHttpServiceMock();
-  static auth = createAuthServiceMock();
-  static email = createEmailServiceMock();
-  static storage = createStorageServiceMock();
-  static payment = createPaymentServiceMock();
-  static sms = createSmsServiceMock();
-  static thirdParty = createThirdPartyApiMock();
-
-  /**
-   * 重置所有mock
-   */
-  static resetAll() {
-    Object.values(this).forEach(service => {
-      if (service && typeof service === 'object') {
-        Object.values(service).forEach(mock => {
-          if (mock && typeof mock === 'function' && 'mockClear' in mock) {
-            (mock as any).mockClear();
-          }
-        });
-      }
-    });
-  }
-
-  /**
-   * 设置所有mock为成功状态
-   */
-  static setupForSuccess() {
-    this.resetAll();
-    // 所有mock默认已经是成功状态
-  }
-
-  /**
-   * 设置所有mock为失败状态
-   */
-  static setupForFailure() {
-    this.resetAll();
-    Object.values(this.http).forEach(mock => mock.mockRejectedValue(mockNetworkError()));
-    Object.values(this.auth).forEach(mock => mock.mockRejectedValue(new Error('Auth failed')));
-    Object.values(this.email).forEach(mock => mock.mockRejectedValue(new Error('Email failed')));
-    Object.values(this.storage).forEach(mock => mock.mockRejectedValue(new Error('Storage failed')));
-    Object.values(this.payment).forEach(mock => mock.mockRejectedValue(new Error('Payment failed')));
-    Object.values(this.sms).forEach(mock => mock.mockRejectedValue(new Error('SMS failed')));
-    Object.values(this.thirdParty).forEach(mock => mock.mockRejectedValue(new Error('Third party failed')));
-  }
-}

+ 0 - 158
packages/test-utils/src/server/service-stubs.ts

@@ -1,158 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建模拟的用户服务
- */
-export function createMockUserService() {
-  return {
-    getUserById: vi.fn().mockResolvedValue(null),
-    getUserByUsername: vi.fn().mockResolvedValue(null),
-    getUserByEmail: vi.fn().mockResolvedValue(null),
-    createUser: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }),
-    updateUser: vi.fn().mockResolvedValue({ affected: 1 }),
-    deleteUser: vi.fn().mockResolvedValue({ affected: 1 }),
-    changePassword: vi.fn().mockResolvedValue({ affected: 1 }),
-    verifyPassword: vi.fn().mockResolvedValue(true),
-    assignRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    removeRole: vi.fn().mockResolvedValue({ affected: 1 })
-  };
-}
-
-/**
- * 创建模拟的认证服务
- */
-export function createMockAuthService() {
-  return {
-    login: vi.fn().mockResolvedValue({
-      token: 'mock-jwt-token',
-      user: { id: 1, username: 'testuser' }
-    }),
-    logout: vi.fn().mockResolvedValue(undefined),
-    register: vi.fn().mockResolvedValue({ id: 1, username: 'newuser' }),
-    verifyToken: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }),
-    refreshToken: vi.fn().mockResolvedValue({ token: 'new-mock-jwt-token' }),
-    forgotPassword: vi.fn().mockResolvedValue({ success: true }),
-    resetPassword: vi.fn().mockResolvedValue({ success: true })
-  };
-}
-
-/**
- * 创建模拟的角色服务
- */
-export function createMockRoleService() {
-  return {
-    getRoles: vi.fn().mockResolvedValue([]),
-    getRoleById: vi.fn().mockResolvedValue(null),
-    createRole: vi.fn().mockResolvedValue({ id: 1, name: 'admin' }),
-    updateRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    deleteRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    assignPermission: vi.fn().mockResolvedValue({ affected: 1 }),
-    removePermission: vi.fn().mockResolvedValue({ affected: 1 })
-  };
-}
-
-/**
- * 创建模拟的通用CRUD服务
- */
-export function createMockCrudService() {
-  return {
-    findAll: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    create: vi.fn().mockResolvedValue({ id: 1 }),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    count: vi.fn().mockResolvedValue(0),
-    exists: vi.fn().mockResolvedValue(false)
-  };
-}
-
-/**
- * 创建模拟的邮件服务
- */
-export function createMockEmailService() {
-  return {
-    sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendPasswordResetEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true }),
-    verifyEmail: vi.fn().mockResolvedValue({ success: true })
-  };
-}
-
-/**
- * 创建模拟的文件服务
- */
-export function createMockFileService() {
-  return {
-    upload: vi.fn().mockResolvedValue({ url: 'https://example.com/file.jpg' }),
-    download: vi.fn().mockResolvedValue(Buffer.from('test content')),
-    delete: vi.fn().mockResolvedValue({ success: true }),
-    getSignedUrl: vi.fn().mockResolvedValue('https://example.com/signed-url')
-  };
-}
-
-/**
- * 服务stub工具类
- */
-export class ServiceStubManager {
-  private stubs = new Map<string, any>();
-
-  /**
-   * 创建服务stub
-   */
-  createStub<T>(serviceName: string, stubImplementation: Partial<T>): T {
-    const stub = { ...stubImplementation } as T;
-    this.stubs.set(serviceName, stub);
-    return stub;
-  }
-
-  /**
-   * 获取服务stub
-   */
-  getStub<T>(serviceName: string): T | undefined {
-    return this.stubs.get(serviceName);
-  }
-
-  /**
-   * 重置所有stub
-   */
-  resetAll(): void {
-    this.stubs.clear();
-  }
-
-  /**
-   * 重置特定stub
-   */
-  resetStub(serviceName: string): void {
-    this.stubs.delete(serviceName);
-  }
-}
-
-/**
- * 全局服务stub管理器
- */
-export const serviceStubs = new ServiceStubManager();
-
-/**
- * 设置服务mock
- */
-export function setupServiceMocks() {
-  // 用户服务mock
-  vi.mock('@d8d/server/modules/users/user.service', () => ({
-    UserService: vi.fn().mockImplementation(() => serviceStubs.createStub('UserService', createMockUserService()))
-  }));
-
-  // 认证服务mock
-  vi.mock('@d8d/server/modules/auth/auth.service', () => ({
-    AuthService: vi.fn().mockImplementation(() => serviceStubs.createStub('AuthService', createMockAuthService()))
-  }));
-
-  // 角色服务mock
-  vi.mock('@d8d/server/modules/roles/role.service', () => ({
-    RoleService: vi.fn().mockImplementation(() => serviceStubs.createStub('RoleService', createMockRoleService()))
-  }));
-
-  // 通用CRUD服务mock
-  vi.mock('@d8d/server/utils/generic-crud.service', () => ({
-    GenericCRUDService: vi.fn().mockImplementation(() => serviceStubs.createStub('GenericCRUDService', createMockCrudService()))
-  }));
-}

+ 0 - 88
packages/test-utils/src/server/test-auth.ts

@@ -1,88 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建模拟的认证上下文
- */
-export function createMockAuthContext(overrides: Partial<any> = {}) {
-  const baseContext = {
-    req: {
-      header: (name: string) => {
-        const headers: Record<string, string> = {
-          'authorization': 'Bearer test-token-123',
-          'content-type': 'application/json',
-          'user-agent': 'vitest/integration-test',
-          'x-request-id': `test_${Math.random().toString(36).substr(2, 9)}`
-        };
-        return headers[name.toLowerCase()] || null;
-      }
-    },
-    set: vi.fn(),
-    json: vi.fn().mockImplementation((data, status = 200) => ({
-      status,
-      body: data
-    })),
-    status: vi.fn().mockReturnThis(),
-    body: vi.fn().mockReturnThis(),
-    env: {
-      NODE_ENV: 'test',
-      DATABASE_URL: process.env.TEST_DATABASE_URL || 'mysql://root:test@localhost:3306/test_d8dai'
-    },
-    var: {},
-    get: vi.fn()
-  };
-
-  return { ...baseContext, ...overrides };
-}
-
-/**
- * 创建模拟的JWT用户信息
- */
-export function createMockJwtPayload(overrides: Partial<any> = {}) {
-  return {
-    sub: '1',
-    username: 'testuser',
-    email: 'test@example.com',
-    roles: ['user'],
-    iat: Math.floor(Date.now() / 1000),
-    exp: Math.floor(Date.now() / 1000) + 3600, // 1小时后过期
-    ...overrides
-  };
-}
-
-/**
- * 创建模拟的认证中间件
- */
-export function createMockAuthMiddleware() {
-  return vi.fn().mockImplementation(async (c: any, next: () => Promise<void>) => {
-    // 模拟认证用户信息
-    c.set('user', {
-      id: 1,
-      username: 'testuser',
-      email: 'test@example.com',
-      roles: ['user']
-    });
-    await next();
-  });
-}
-
-/**
- * 创建模拟的权限中间件
- */
-export function createMockPermissionMiddleware(requiredRoles: string[] = []) {
-  return vi.fn().mockImplementation(async (c: any, next: () => Promise<void>) => {
-    const user = c.get('user');
-
-    if (!user) {
-      return c.json({ error: 'Unauthorized' }, 401);
-    }
-
-    if (requiredRoles.length > 0) {
-      const hasRole = requiredRoles.some(role => user.roles?.includes(role));
-      if (!hasRole) {
-        return c.json({ error: 'Forbidden' }, 403);
-      }
-    }
-
-    await next();
-  });
-}

+ 0 - 157
packages/test-utils/src/server/test-db.ts

@@ -1,157 +0,0 @@
-import { DataSource, EntityManager, Repository } from 'typeorm';
-import { vi, beforeEach, afterEach } from 'vitest';
-
-/**
- * 创建模拟的数据源
- */
-export function createMockDataSource() {
-  const manager = createMockEntityManager();
-  const mockDataSource = {
-    initialize: vi.fn().mockResolvedValue(undefined),
-    destroy: vi.fn().mockResolvedValue(undefined),
-    isInitialized: true,
-    manager,
-    getRepository: vi.fn().mockImplementation(() => createMockRepository()),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    transaction: vi.fn().mockImplementation(async (callback) => {
-      return callback(manager);
-    }),
-    synchronize: vi.fn().mockResolvedValue(undefined),
-    dropDatabase: vi.fn().mockResolvedValue(undefined)
-  };
-
-  return mockDataSource;
-}
-
-/**
- * 创建模拟的实体管理器
- */
-export function createMockEntityManager(): EntityManager {
-  return {
-    find: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    transaction: vi.fn().mockImplementation(async (callback) => {
-      return callback(createMockEntityManager());
-    }),
-    getRepository: vi.fn().mockImplementation(() => createMockRepository())
-  } as any;
-}
-
-/**
- * 创建模拟的Repository
- */
-export function createMockRepository<T extends object = any>(): Repository<T> {
-  return {
-    find: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    findOneBy: vi.fn().mockResolvedValue(null),
-    findOneByOrFail: vi.fn().mockResolvedValue(null),
-    findBy: vi.fn().mockResolvedValue([]),
-    findAndCount: vi.fn().mockResolvedValue([[], 0]),
-    findAndCountBy: vi.fn().mockResolvedValue([[], 0]),
-    save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    create: vi.fn().mockImplementation((entity) => ({ ...entity, id: Date.now() })),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    count: vi.fn().mockResolvedValue(0),
-    countBy: vi.fn().mockResolvedValue(0),
-    exist: vi.fn().mockResolvedValue(false)
-  } as any;
-}
-
-/**
- * 创建模拟的QueryBuilder
- */
-export function createMockQueryBuilder() {
-  const mockQueryBuilder = {
-    select: vi.fn().mockReturnThis(),
-    from: vi.fn().mockReturnThis(),
-    where: vi.fn().mockReturnThis(),
-    andWhere: vi.fn().mockReturnThis(),
-    orWhere: vi.fn().mockReturnThis(),
-    leftJoin: vi.fn().mockReturnThis(),
-    innerJoin: vi.fn().mockReturnThis(),
-    orderBy: vi.fn().mockReturnThis(),
-    groupBy: vi.fn().mockReturnThis(),
-    having: vi.fn().mockReturnThis(),
-    skip: vi.fn().mockReturnThis(),
-    take: vi.fn().mockReturnThis(),
-    getMany: vi.fn().mockResolvedValue([]),
-    getOne: vi.fn().mockResolvedValue(null),
-    getCount: vi.fn().mockResolvedValue(0),
-    getRawMany: vi.fn().mockResolvedValue([]),
-    getRawOne: vi.fn().mockResolvedValue(null),
-    execute: vi.fn().mockResolvedValue(undefined),
-    setParameter: vi.fn().mockReturnThis(),
-    setParameters: vi.fn().mockReturnThis()
-  };
-
-  return mockQueryBuilder;
-}
-
-/**
- * 数据库测试工具类
- */
-export class TestDatabase {
-  private static dataSource: DataSource | null = null;
-
-  /**
-   * 初始化测试数据库
-   */
-  static async initialize(): Promise<DataSource> {
-    if (this.dataSource?.isInitialized) {
-      return this.dataSource;
-    }
-
-    // 使用SQLite内存数据库进行测试
-    this.dataSource = new DataSource({
-      type: 'better-sqlite3',
-      database: ':memory:',
-      synchronize: true,
-      logging: false,
-      entities: [
-        // 导入实际实体
-        (await import('@d8d/server/modules/users/user.entity')).UserEntity,
-        (await import('@d8d/server/modules/users/role.entity')).Role
-      ]
-    });
-
-    await this.dataSource.initialize();
-    return this.dataSource;
-  }
-
-  /**
-   * 清理测试数据库
-   */
-  static async cleanup(): Promise<void> {
-    if (this.dataSource?.isInitialized) {
-      await this.dataSource.destroy();
-      this.dataSource = null;
-    }
-  }
-
-  /**
-   * 获取当前数据源
-   */
-  static getDataSource(): DataSource | null {
-    return this.dataSource;
-  }
-}
-
-/**
- * 测试数据库生命周期钩子
- */
-export function setupDatabaseHooks() {
-  beforeEach(async () => {
-    await TestDatabase.initialize();
-  });
-
-  afterEach(async () => {
-    await TestDatabase.cleanup();
-  });
-}

+ 0 - 48
packages/test-utils/src/setup.ts

@@ -1,48 +0,0 @@
-// 测试环境全局设置
-import { beforeAll, afterAll, afterEach, vi, expect } from 'vitest';
-import * as matchers from '@testing-library/jest-dom/matchers';
-
-// 全局测试超时设置已在 vitest.config.ts 中配置
-
-// 扩展expect匹配器
-expect.extend(matchers);
-
-// 全局测试前置处理
-beforeAll(() => {
-  // 设置测试环境变量
-  process.env.NODE_ENV = 'test';
-
-  // 抑制控制台输出(测试中)
-  vi.spyOn(console, 'log').mockImplementation(() => {});
-  vi.spyOn(console, 'error').mockImplementation(() => {});
-  vi.spyOn(console, 'warn').mockImplementation(() => {});
-  vi.spyOn(console, 'info').mockImplementation(() => {});
-});
-
-// 全局测试后置清理
-afterAll(() => {
-  // 恢复控制台输出
-  vi.restoreAllMocks();
-});
-
-// 每个测试后的清理
-afterEach(() => {
-  vi.clearAllMocks();
-});
-
-// 全局测试工具函数
-globalThis.createTestContext = () => ({
-  timestamp: new Date().toISOString(),
-  requestId: `test_${Math.random().toString(36).substr(2, 9)}`
-});
-
-// 类型声明
-declare global {
-  // eslint-disable-next-line no-var
-  var createTestContext: () => {
-    timestamp: string;
-    requestId: string;
-  };
-}
-
-export {};

+ 0 - 90
packages/test-utils/src/test-utils.ts

@@ -1,90 +0,0 @@
-import { OpenAPIHono } from '@hono/zod-openapi';
-import { Hono } from 'hono';
-import { vi } from 'vitest';
-
-/**
- * 创建测试服务器实例
- */
-export function createTestServer(app: OpenAPIHono | Hono) {
-  const server = app as any;
-
-  return {
-    get: (path: string) => makeRequest('GET', path),
-    post: (path: string, body?: any) => makeRequest('POST', path, body),
-    put: (path: string, body?: any) => makeRequest('PUT', path, body),
-    delete: (path: string) => makeRequest('DELETE', path),
-    patch: (path: string, body?: any) => makeRequest('PATCH', path, body)
-  };
-
-  async function makeRequest(method: string, path: string, body?: any) {
-    const url = new URL(path, 'http://localhost:3000');
-
-    const request = new Request(url.toString(), {
-      method,
-      headers: {
-        'Content-Type': 'application/json',
-        'Authorization': 'Bearer mock-token',
-      },
-      body: body ? JSON.stringify(body) : undefined,
-    });
-
-    try {
-      const response = await server.fetch(request);
-      return {
-        status: response.status,
-        headers: response.headers,
-        json: async () => response.json(),
-        text: async () => response.text()
-      };
-    } catch (error) {
-      throw new Error(`Request failed: ${error}`);
-    }
-  }
-}
-
-/**
- * 创建模拟的认证上下文
- */
-export function createMockAuthContext() {
-  return {
-    req: {
-      header: (name: string) => {
-        if (name === 'authorization') return 'Bearer mock-token';
-        return null;
-      }
-    },
-    set: vi.fn(),
-    json: vi.fn().mockImplementation((data, status = 200) => ({
-      status,
-      body: data
-    })),
-    env: {},
-    var: {}
-  };
-}
-
-/**
- * 创建模拟的用户实体
- */
-export function createMockUser(overrides: Partial<any> = {}) {
-  return {
-    id: 1,
-    username: 'testuser',
-    email: 'test@example.com',
-    password: 'hashed_password',
-    phone: '13800138000',
-    nickname: 'Test User',
-    status: 1,
-    createdAt: new Date(),
-    updatedAt: new Date(),
-    roles: [],
-    ...overrides
-  };
-}
-
-/**
- * 等待指定时间
- */
-export function wait(ms: number) {
-  return new Promise(resolve => setTimeout(resolve, ms));
-}

+ 0 - 41
packages/test-utils/tsconfig.json

@@ -1,41 +0,0 @@
-{
-  "compilerOptions": {
-    "composite": true,
-    "target": "ES2022",
-    "module": "ESNext",
-    "moduleResolution": "bundler",
-    "allowSyntheticDefaultImports": true,
-    "esModuleInterop": true,
-    "resolveJsonModule": true,
-    "isolatedModules": true,
-    "noEmit": false,
-    "jsx": "react-jsx",
-    "strict": true,
-    "noUncheckedIndexedAccess": true,
-    "noImplicitOverride": true,
-    "outDir": "./dist",
-    "rootDir": "./src",
-    "declaration": true,
-    "skipLibCheck": true,
-
-    "experimentalDecorators": true,
-    "emitDecoratorMetadata": true,
-    "baseUrl": ".",
-    "paths": {
-      "@/*": ["./src/*"],
-      "@d8d/server": ["../server/src/index.ts"],
-      "@d8d/server/*": ["../server/src/*"]
-    }
-  },
-  "include": [
-    "src/**/*",
-    "tests/**/*"
-  ],
-  "exclude": [
-    "node_modules",
-    "dist"
-  ],
-  "references": [
-    { "path": "../server" }
-  ]
-}

文件差异内容过多而无法显示
+ 538 - 56
pnpm-lock.yaml


+ 0 - 2
web/package.json

@@ -19,7 +19,6 @@
     "test:api:coverage": "vitest run --coverage --project=node",
     "test:components:coverage": "vitest run --coverage --project=happy-dom",
     "test:unit": "vitest run tests/unit",
-    "test:integration:server": "vitest run tests/integration/server",
     "test:integration:client": "vitest run tests/integration/client",
     "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
     "test:e2e:ui": "playwright test --config=tests/e2e/playwright.config.ts --ui",
@@ -40,7 +39,6 @@
   },
   "dependencies": {
     "@d8d/server": "workspace:*",
-    "@d8d/test-utils": "workspace:*",
     "@ant-design/icons": "^6.0.0",
     "@heroicons/react": "^2.2.0",
     "@hono/node-server": "^1.17.1",

+ 0 - 75
web/tests/utils/server/integration-test-utils.ts

@@ -1,75 +0,0 @@
-
-import { IntegrationTestDatabase } from './integration-test-db';
-import { UserEntity } from '@d8d/server/modules/users/user.entity';
-
-
-
-/**
- * 集成测试断言工具
- */
-export class IntegrationTestAssertions {
-  /**
-   * 断言响应状态码
-   */
-  static expectStatus(response: { status: number }, expectedStatus: number): void {
-    if (response.status !== expectedStatus) {
-      throw new Error(`Expected status ${expectedStatus}, but got ${response.status}`);
-    }
-  }
-
-  /**
-   * 断言响应包含特定字段
-   */
-  static expectResponseToHave(response: { data: any }, expectedFields: Record<string, any>): void {
-    for (const [key, value] of Object.entries(expectedFields)) {
-      if (response.data[key] !== value) {
-        throw new Error(`Expected field ${key} to be ${value}, but got ${response.data[key]}`);
-      }
-    }
-  }
-
-  /**
-   * 断言响应包含特定结构
-   */
-  static expectResponseStructure(response: { data: any }, structure: Record<string, any>): void {
-    for (const key of Object.keys(structure)) {
-      if (!(key in response.data)) {
-        throw new Error(`Expected response to have key: ${key}`);
-      }
-    }
-  }
-
-  /**
-   * 断言用户存在于数据库中
-   */
-  static async expectUserToExist(username: string): Promise<void> {
-    const dataSource = await IntegrationTestDatabase.getDataSource();
-    if (!dataSource) {
-      throw new Error('Database not initialized');
-    }
-
-    const userRepository = dataSource.getRepository(UserEntity);
-    const user = await userRepository.findOne({ where: { username } });
-
-    if (!user) {
-      throw new Error(`Expected user ${username} to exist in database`);
-    }
-  }
-
-  /**
-   * 断言用户不存在于数据库中
-   */
-  static async expectUserNotToExist(username: string): Promise<void> {
-    const dataSource = await IntegrationTestDatabase.getDataSource();
-    if (!dataSource) {
-      throw new Error('Database not initialized');
-    }
-
-    const userRepository = dataSource.getRepository(UserEntity);
-    const user = await userRepository.findOne({ where: { username } });
-
-    if (user) {
-      throw new Error(`Expected user ${username} not to exist in database`);
-    }
-  }
-}

+ 0 - 164
web/tests/utils/server/service-mocks.ts

@@ -1,164 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建HTTP服务mock
- */
-export function createHttpServiceMock() {
-  return {
-    get: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-    post: vi.fn().mockResolvedValue({ data: {}, status: 201 }),
-    put: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-    delete: vi.fn().mockResolvedValue({ data: {}, status: 204 }),
-    patch: vi.fn().mockResolvedValue({ data: {}, status: 200 }),
-  };
-}
-
-/**
- * 创建认证服务mock
- */
-export function createAuthServiceMock() {
-  return {
-    verifyToken: vi.fn().mockResolvedValue({ userId: 1, username: 'testuser' }),
-    generateToken: vi.fn().mockReturnValue('mock-jwt-token'),
-    refreshToken: vi.fn().mockResolvedValue({ accessToken: 'new-token', refreshToken: 'new-refresh-token' }),
-    invalidateToken: vi.fn().mockResolvedValue(undefined),
-  };
-}
-
-/**
- * 创建邮件服务mock
- */
-export function createEmailServiceMock() {
-  return {
-    sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendPasswordResetEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true }),
-  };
-}
-
-/**
- * 创建文件存储服务mock
- */
-export function createStorageServiceMock() {
-  return {
-    uploadFile: vi.fn().mockResolvedValue({ url: 'https://example.com/file.jpg', key: 'file-key' }),
-    deleteFile: vi.fn().mockResolvedValue({ success: true }),
-    getFileUrl: vi.fn().mockReturnValue('https://example.com/file.jpg'),
-    listFiles: vi.fn().mockResolvedValue([]),
-  };
-}
-
-/**
- * 创建支付服务mock
- */
-export function createPaymentServiceMock() {
-  return {
-    createPaymentIntent: vi.fn().mockResolvedValue({ clientSecret: 'pi_mock_secret', id: 'pi_mock_id' }),
-    confirmPayment: vi.fn().mockResolvedValue({ success: true, transactionId: 'txn_mock_id' }),
-    refundPayment: vi.fn().mockResolvedValue({ success: true, refundId: 'ref_mock_id' }),
-  };
-}
-
-/**
- * 创建短信服务mock
- */
-export function createSmsServiceMock() {
-  return {
-    sendVerificationCode: vi.fn().mockResolvedValue({ success: true, messageId: 'sms_mock_id' }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true, messageId: 'sms_mock_id' }),
-  };
-}
-
-/**
- * 创建第三方API服务mock
- */
-export function createThirdPartyApiMock() {
-  return {
-    call: vi.fn().mockResolvedValue({ success: true, data: {} }),
-    validate: vi.fn().mockResolvedValue({ valid: true, errors: [] }),
-    webhook: vi.fn().mockResolvedValue({ received: true }),
-  };
-}
-
-/**
- * 模拟网络延迟
- */
-export function mockNetworkDelay(delayMs: number) {
-  return new Promise(resolve => setTimeout(resolve, delayMs));
-}
-
-/**
- * 模拟HTTP错误响应
- */
-export function mockHttpError(status: number, message: string) {
-  return {
-    response: {
-      status,
-      data: { error: message }
-    }
-  };
-}
-
-/**
- * 模拟超时错误
- */
-export function mockTimeoutError() {
-  return new Error('Request timeout');
-}
-
-/**
- * 模拟网络断开错误
- */
-export function mockNetworkError() {
-  return new Error('Network error');
-}
-
-/**
- * 服务mock工具类
- */
-export class ServiceMocks {
-  static http = createHttpServiceMock();
-  static auth = createAuthServiceMock();
-  static email = createEmailServiceMock();
-  static storage = createStorageServiceMock();
-  static payment = createPaymentServiceMock();
-  static sms = createSmsServiceMock();
-  static thirdParty = createThirdPartyApiMock();
-
-  /**
-   * 重置所有mock
-   */
-  static resetAll() {
-    Object.values(this).forEach(service => {
-      if (service && typeof service === 'object') {
-        Object.values(service).forEach(mock => {
-          if (mock && typeof mock === 'function' && 'mockClear' in mock) {
-            (mock as any).mockClear();
-          }
-        });
-      }
-    });
-  }
-
-  /**
-   * 设置所有mock为成功状态
-   */
-  static setupForSuccess() {
-    this.resetAll();
-    // 所有mock默认已经是成功状态
-  }
-
-  /**
-   * 设置所有mock为失败状态
-   */
-  static setupForFailure() {
-    this.resetAll();
-    Object.values(this.http).forEach(mock => mock.mockRejectedValue(mockNetworkError()));
-    Object.values(this.auth).forEach(mock => mock.mockRejectedValue(new Error('Auth failed')));
-    Object.values(this.email).forEach(mock => mock.mockRejectedValue(new Error('Email failed')));
-    Object.values(this.storage).forEach(mock => mock.mockRejectedValue(new Error('Storage failed')));
-    Object.values(this.payment).forEach(mock => mock.mockRejectedValue(new Error('Payment failed')));
-    Object.values(this.sms).forEach(mock => mock.mockRejectedValue(new Error('SMS failed')));
-    Object.values(this.thirdParty).forEach(mock => mock.mockRejectedValue(new Error('Third party failed')));
-  }
-}

+ 0 - 158
web/tests/utils/server/service-stubs.ts

@@ -1,158 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建模拟的用户服务
- */
-export function createMockUserService() {
-  return {
-    getUserById: vi.fn().mockResolvedValue(null),
-    getUserByUsername: vi.fn().mockResolvedValue(null),
-    getUserByEmail: vi.fn().mockResolvedValue(null),
-    createUser: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }),
-    updateUser: vi.fn().mockResolvedValue({ affected: 1 }),
-    deleteUser: vi.fn().mockResolvedValue({ affected: 1 }),
-    changePassword: vi.fn().mockResolvedValue({ affected: 1 }),
-    verifyPassword: vi.fn().mockResolvedValue(true),
-    assignRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    removeRole: vi.fn().mockResolvedValue({ affected: 1 })
-  };
-}
-
-/**
- * 创建模拟的认证服务
- */
-export function createMockAuthService() {
-  return {
-    login: vi.fn().mockResolvedValue({
-      token: 'mock-jwt-token',
-      user: { id: 1, username: 'testuser' }
-    }),
-    logout: vi.fn().mockResolvedValue(undefined),
-    register: vi.fn().mockResolvedValue({ id: 1, username: 'newuser' }),
-    verifyToken: vi.fn().mockResolvedValue({ id: 1, username: 'testuser' }),
-    refreshToken: vi.fn().mockResolvedValue({ token: 'new-mock-jwt-token' }),
-    forgotPassword: vi.fn().mockResolvedValue({ success: true }),
-    resetPassword: vi.fn().mockResolvedValue({ success: true })
-  };
-}
-
-/**
- * 创建模拟的角色服务
- */
-export function createMockRoleService() {
-  return {
-    getRoles: vi.fn().mockResolvedValue([]),
-    getRoleById: vi.fn().mockResolvedValue(null),
-    createRole: vi.fn().mockResolvedValue({ id: 1, name: 'admin' }),
-    updateRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    deleteRole: vi.fn().mockResolvedValue({ affected: 1 }),
-    assignPermission: vi.fn().mockResolvedValue({ affected: 1 }),
-    removePermission: vi.fn().mockResolvedValue({ affected: 1 })
-  };
-}
-
-/**
- * 创建模拟的通用CRUD服务
- */
-export function createMockCrudService() {
-  return {
-    findAll: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    create: vi.fn().mockResolvedValue({ id: 1 }),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    count: vi.fn().mockResolvedValue(0),
-    exists: vi.fn().mockResolvedValue(false)
-  };
-}
-
-/**
- * 创建模拟的邮件服务
- */
-export function createMockEmailService() {
-  return {
-    sendWelcomeEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendPasswordResetEmail: vi.fn().mockResolvedValue({ success: true }),
-    sendNotification: vi.fn().mockResolvedValue({ success: true }),
-    verifyEmail: vi.fn().mockResolvedValue({ success: true })
-  };
-}
-
-/**
- * 创建模拟的文件服务
- */
-export function createMockFileService() {
-  return {
-    upload: vi.fn().mockResolvedValue({ url: 'https://example.com/file.jpg' }),
-    download: vi.fn().mockResolvedValue(Buffer.from('test content')),
-    delete: vi.fn().mockResolvedValue({ success: true }),
-    getSignedUrl: vi.fn().mockResolvedValue('https://example.com/signed-url')
-  };
-}
-
-/**
- * 服务stub工具类
- */
-export class ServiceStubManager {
-  private stubs = new Map<string, any>();
-
-  /**
-   * 创建服务stub
-   */
-  createStub<T>(serviceName: string, stubImplementation: Partial<T>): T {
-    const stub = { ...stubImplementation } as T;
-    this.stubs.set(serviceName, stub);
-    return stub;
-  }
-
-  /**
-   * 获取服务stub
-   */
-  getStub<T>(serviceName: string): T | undefined {
-    return this.stubs.get(serviceName);
-  }
-
-  /**
-   * 重置所有stub
-   */
-  resetAll(): void {
-    this.stubs.clear();
-  }
-
-  /**
-   * 重置特定stub
-   */
-  resetStub(serviceName: string): void {
-    this.stubs.delete(serviceName);
-  }
-}
-
-/**
- * 全局服务stub管理器
- */
-export const serviceStubs = new ServiceStubManager();
-
-/**
- * 设置服务mock
- */
-export function setupServiceMocks() {
-  // 用户服务mock
-  vi.mock('@d8d/server/modules/users/user.service', () => ({
-    UserService: vi.fn().mockImplementation(() => serviceStubs.createStub('UserService', createMockUserService()))
-  }));
-
-  // 认证服务mock
-  vi.mock('@d8d/server/modules/auth/auth.service', () => ({
-    AuthService: vi.fn().mockImplementation(() => serviceStubs.createStub('AuthService', createMockAuthService()))
-  }));
-
-  // 角色服务mock
-  vi.mock('@d8d/server/modules/roles/role.service', () => ({
-    RoleService: vi.fn().mockImplementation(() => serviceStubs.createStub('RoleService', createMockRoleService()))
-  }));
-
-  // 通用CRUD服务mock
-  vi.mock('@d8d/server/utils/generic-crud.service', () => ({
-    GenericCRUDService: vi.fn().mockImplementation(() => serviceStubs.createStub('GenericCRUDService', createMockCrudService()))
-  }));
-}

+ 0 - 88
web/tests/utils/server/test-auth.ts

@@ -1,88 +0,0 @@
-import { vi } from 'vitest';
-
-/**
- * 创建模拟的认证上下文
- */
-export function createMockAuthContext(overrides: Partial<any> = {}) {
-  const baseContext = {
-    req: {
-      header: (name: string) => {
-        const headers: Record<string, string> = {
-          'authorization': 'Bearer test-token-123',
-          'content-type': 'application/json',
-          'user-agent': 'vitest/integration-test',
-          'x-request-id': `test_${Math.random().toString(36).substr(2, 9)}`
-        };
-        return headers[name.toLowerCase()] || null;
-      }
-    },
-    set: vi.fn(),
-    json: vi.fn().mockImplementation((data, status = 200) => ({
-      status,
-      body: data
-    })),
-    status: vi.fn().mockReturnThis(),
-    body: vi.fn().mockReturnThis(),
-    env: {
-      NODE_ENV: 'test',
-      DATABASE_URL: process.env.TEST_DATABASE_URL || 'mysql://root:test@localhost:3306/test_d8dai'
-    },
-    var: {},
-    get: vi.fn()
-  };
-
-  return { ...baseContext, ...overrides };
-}
-
-/**
- * 创建模拟的JWT用户信息
- */
-export function createMockJwtPayload(overrides: Partial<any> = {}) {
-  return {
-    sub: '1',
-    username: 'testuser',
-    email: 'test@example.com',
-    roles: ['user'],
-    iat: Math.floor(Date.now() / 1000),
-    exp: Math.floor(Date.now() / 1000) + 3600, // 1小时后过期
-    ...overrides
-  };
-}
-
-/**
- * 创建模拟的认证中间件
- */
-export function createMockAuthMiddleware() {
-  return vi.fn().mockImplementation(async (c: any, next: () => Promise<void>) => {
-    // 模拟认证用户信息
-    c.set('user', {
-      id: 1,
-      username: 'testuser',
-      email: 'test@example.com',
-      roles: ['user']
-    });
-    await next();
-  });
-}
-
-/**
- * 创建模拟的权限中间件
- */
-export function createMockPermissionMiddleware(requiredRoles: string[] = []) {
-  return vi.fn().mockImplementation(async (c: any, next: () => Promise<void>) => {
-    const user = c.get('user');
-
-    if (!user) {
-      return c.json({ error: 'Unauthorized' }, 401);
-    }
-
-    if (requiredRoles.length > 0) {
-      const hasRole = requiredRoles.some(role => user.roles?.includes(role));
-      if (!hasRole) {
-        return c.json({ error: 'Forbidden' }, 403);
-      }
-    }
-
-    await next();
-  });
-}

+ 0 - 157
web/tests/utils/server/test-db.ts

@@ -1,157 +0,0 @@
-import { DataSource, EntityManager, Repository } from 'typeorm';
-import { vi, beforeEach, afterEach } from 'vitest';
-
-/**
- * 创建模拟的数据源
- */
-export function createMockDataSource() {
-  const manager = createMockEntityManager();
-  const mockDataSource = {
-    initialize: vi.fn().mockResolvedValue(undefined),
-    destroy: vi.fn().mockResolvedValue(undefined),
-    isInitialized: true,
-    manager,
-    getRepository: vi.fn().mockImplementation(() => createMockRepository()),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    transaction: vi.fn().mockImplementation(async (callback) => {
-      return callback(manager);
-    }),
-    synchronize: vi.fn().mockResolvedValue(undefined),
-    dropDatabase: vi.fn().mockResolvedValue(undefined)
-  };
-
-  return mockDataSource;
-}
-
-/**
- * 创建模拟的实体管理器
- */
-export function createMockEntityManager(): EntityManager {
-  return {
-    find: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    transaction: vi.fn().mockImplementation(async (callback) => {
-      return callback(createMockEntityManager());
-    }),
-    getRepository: vi.fn().mockImplementation(() => createMockRepository())
-  } as any;
-}
-
-/**
- * 创建模拟的Repository
- */
-export function createMockRepository<T extends object = any>(): Repository<T> {
-  return {
-    find: vi.fn().mockResolvedValue([]),
-    findOne: vi.fn().mockResolvedValue(null),
-    findOneBy: vi.fn().mockResolvedValue(null),
-    findOneByOrFail: vi.fn().mockResolvedValue(null),
-    findBy: vi.fn().mockResolvedValue([]),
-    findAndCount: vi.fn().mockResolvedValue([[], 0]),
-    findAndCountBy: vi.fn().mockResolvedValue([[], 0]),
-    save: vi.fn().mockImplementation((entity) => Promise.resolve(entity)),
-    update: vi.fn().mockResolvedValue({ affected: 1 }),
-    delete: vi.fn().mockResolvedValue({ affected: 1 }),
-    create: vi.fn().mockImplementation((entity) => ({ ...entity, id: Date.now() })),
-    createQueryBuilder: vi.fn().mockReturnValue(createMockQueryBuilder()),
-    count: vi.fn().mockResolvedValue(0),
-    countBy: vi.fn().mockResolvedValue(0),
-    exist: vi.fn().mockResolvedValue(false)
-  } as any;
-}
-
-/**
- * 创建模拟的QueryBuilder
- */
-export function createMockQueryBuilder() {
-  const mockQueryBuilder = {
-    select: vi.fn().mockReturnThis(),
-    from: vi.fn().mockReturnThis(),
-    where: vi.fn().mockReturnThis(),
-    andWhere: vi.fn().mockReturnThis(),
-    orWhere: vi.fn().mockReturnThis(),
-    leftJoin: vi.fn().mockReturnThis(),
-    innerJoin: vi.fn().mockReturnThis(),
-    orderBy: vi.fn().mockReturnThis(),
-    groupBy: vi.fn().mockReturnThis(),
-    having: vi.fn().mockReturnThis(),
-    skip: vi.fn().mockReturnThis(),
-    take: vi.fn().mockReturnThis(),
-    getMany: vi.fn().mockResolvedValue([]),
-    getOne: vi.fn().mockResolvedValue(null),
-    getCount: vi.fn().mockResolvedValue(0),
-    getRawMany: vi.fn().mockResolvedValue([]),
-    getRawOne: vi.fn().mockResolvedValue(null),
-    execute: vi.fn().mockResolvedValue(undefined),
-    setParameter: vi.fn().mockReturnThis(),
-    setParameters: vi.fn().mockReturnThis()
-  };
-
-  return mockQueryBuilder;
-}
-
-/**
- * 数据库测试工具类
- */
-export class TestDatabase {
-  private static dataSource: DataSource | null = null;
-
-  /**
-   * 初始化测试数据库
-   */
-  static async initialize(): Promise<DataSource> {
-    if (this.dataSource?.isInitialized) {
-      return this.dataSource;
-    }
-
-    // 使用SQLite内存数据库进行测试
-    this.dataSource = new DataSource({
-      type: 'better-sqlite3',
-      database: ':memory:',
-      synchronize: true,
-      logging: false,
-      entities: [
-        // 导入实际实体
-        (await import('@d8d/server/modules/users/user.entity')).UserEntity,
-        (await import('@d8d/server/modules/users/role.entity')).Role
-      ]
-    });
-
-    await this.dataSource.initialize();
-    return this.dataSource;
-  }
-
-  /**
-   * 清理测试数据库
-   */
-  static async cleanup(): Promise<void> {
-    if (this.dataSource?.isInitialized) {
-      await this.dataSource.destroy();
-      this.dataSource = null;
-    }
-  }
-
-  /**
-   * 获取当前数据源
-   */
-  static getDataSource(): DataSource | null {
-    return this.dataSource;
-  }
-}
-
-/**
- * 测试数据库生命周期钩子
- */
-export function setupDatabaseHooks() {
-  beforeEach(async () => {
-    await TestDatabase.initialize();
-  });
-
-  afterEach(async () => {
-    await TestDatabase.cleanup();
-  });
-}

部分文件因为文件数量过多而无法显示