管理端所有成长路径API现在需要管理员或经理权限才能访问:
- GET/POST /manager/growth-paths
- GET/PUT/DELETE /manager/growth-paths/{path_id}
This commit is contained in:
155
TEST_REPORT_2026-01-31.md
Normal file
155
TEST_REPORT_2026-01-31.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# KPL 考培练系统测试报告
|
||||||
|
|
||||||
|
**测试环境**: dev (https://kpl.ireborn.com.cn)
|
||||||
|
**测试时间**: 2026-01-31
|
||||||
|
**测试人员**: AI 自动化测试系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、测试概要
|
||||||
|
|
||||||
|
| 模块 | 测试用例数 | 通过 | 失败 | 警告 |
|
||||||
|
|------|-----------|------|------|------|
|
||||||
|
| 认证模块 | 7 | 5 | 2 | 0 |
|
||||||
|
| 课程管理 | 7 | 7 | 0 | 0 |
|
||||||
|
| 成长路径 | 4 | 4 | 0 | 0 |
|
||||||
|
| 岗位管理 | 2 | 2 | 0 | 0 |
|
||||||
|
| 考试模块 | 3 | 2 | 1 | 0 |
|
||||||
|
| AI练习 | 3 | 2 | 0 | 1 |
|
||||||
|
| 通知系统 | 2 | 2 | 0 | 0 |
|
||||||
|
| 极端边界 | 8 | 7 | 0 | 1 |
|
||||||
|
| 安全测试 | 7 | 5 | 0 | 2 |
|
||||||
|
| **合计** | **43** | **36** | **3** | **4** |
|
||||||
|
|
||||||
|
**通过率**: 83.7%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、发现的问题
|
||||||
|
|
||||||
|
### 严重 (High)
|
||||||
|
|
||||||
|
#### 1. 错误密码登录返回200
|
||||||
|
- **位置**: `POST /api/v1/auth/login`
|
||||||
|
- **描述**: 使用错误密码登录时返回 HTTP 200,应返回 401
|
||||||
|
- **影响**: 可能导致暴力破解攻击难以被检测
|
||||||
|
- **建议**: 检查登录逻辑,确保密码错误时返回 401
|
||||||
|
|
||||||
|
#### 2. XSS 内容被原样存储
|
||||||
|
- **位置**: `POST /api/v1/courses` (name, description 字段)
|
||||||
|
- **描述**: `<script>alert(1)</script>` 等 XSS 代码被原样存入数据库
|
||||||
|
- **影响**: 潜在的存储型 XSS 攻击风险
|
||||||
|
- **建议**:
|
||||||
|
- 输入时转义或过滤 HTML 标签
|
||||||
|
- 输出时使用 HTML 实体编码
|
||||||
|
|
||||||
|
### 中等 (Medium)
|
||||||
|
|
||||||
|
#### 3. 不存在用户登录返回422
|
||||||
|
- **位置**: `POST /api/v1/auth/login`
|
||||||
|
- **描述**: 登录不存在的用户返回 422,应返回 401
|
||||||
|
- **影响**: 用户枚举风险(可判断用户是否存在)
|
||||||
|
- **建议**: 统一返回 401 "用户名或密码错误"
|
||||||
|
|
||||||
|
#### 4. API 限流未配置
|
||||||
|
- **位置**: 全局
|
||||||
|
- **描述**: 10次快速请求未触发限流
|
||||||
|
- **影响**: 可能被恶意请求攻击
|
||||||
|
- **建议**: 配置 API 限流中间件
|
||||||
|
|
||||||
|
### 低等 (Low)
|
||||||
|
|
||||||
|
#### 5. 越权访问返回404而非403
|
||||||
|
- **位置**: `GET /api/v1/admin/users`
|
||||||
|
- **描述**: 普通用户访问管理接口返回 404 而非 403
|
||||||
|
- **影响**: 信息泄露(可探测接口是否存在)
|
||||||
|
- **建议**: 统一返回 403 Forbidden
|
||||||
|
|
||||||
|
#### 6. 部分API端点404
|
||||||
|
- **位置**:
|
||||||
|
- `GET /api/v1/exams` (考试列表)
|
||||||
|
- `GET /api/v1/practice/sessions` (练习记录)
|
||||||
|
- **描述**: 这些端点返回 404,可能是路径变更或未实现
|
||||||
|
- **建议**: 确认 API 路径或补充实现
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、测试详情
|
||||||
|
|
||||||
|
### 3.1 认证模块测试
|
||||||
|
|
||||||
|
| 测试项 | 结果 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 正常登录 | ✓ PASS | HTTP 200, Token 获取成功 |
|
||||||
|
| 错误密码登录 | ✗ FAIL | HTTP 200 (应返回401) |
|
||||||
|
| 不存在用户登录 | ✗ FAIL | HTTP 422 (应返回401) |
|
||||||
|
| Token验证 | ✓ PASS | HTTP 200 |
|
||||||
|
| 无效Token访问 | ✓ PASS | HTTP 401 |
|
||||||
|
| 无Token访问 | ✓ PASS | HTTP 403 |
|
||||||
|
| 获取用户信息 | ✓ PASS | HTTP 200 |
|
||||||
|
|
||||||
|
### 3.2 课程管理测试
|
||||||
|
|
||||||
|
| 测试项 | 结果 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 获取课程列表 | ✓ PASS | 总课程数: 16 |
|
||||||
|
| 创建课程 | ✓ PASS | HTTP 201 |
|
||||||
|
| 获取课程详情 | ✓ PASS | HTTP 200 |
|
||||||
|
| 更新课程 | ✓ PASS | HTTP 200 |
|
||||||
|
| 获取考试设置 | ✓ PASS | HTTP 200 |
|
||||||
|
| 更新考试设置 | ✓ PASS | HTTP 200 |
|
||||||
|
| 获取不存在课程 | ✓ PASS | HTTP 404 |
|
||||||
|
|
||||||
|
### 3.3 极端边界测试
|
||||||
|
|
||||||
|
| 测试项 | 结果 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 空名称创建课程 | ✓ PASS | 正确返回 422 |
|
||||||
|
| 超长名称(1000字符) | ✓ PASS | 正确返回 422 |
|
||||||
|
| XSS注入 | ⚠ WARN | 内容被原样存储 |
|
||||||
|
| SQL注入 | ✓ PASS | 注入被防护 |
|
||||||
|
| 负数分页参数 | ✓ PASS | 正确返回 422 |
|
||||||
|
| 超大分页(10000) | ✓ PASS | 正确返回 422 |
|
||||||
|
| Unicode/Emoji | ✓ PASS | 正确处理 |
|
||||||
|
| 特殊字符 | ✓ PASS | 正确处理 |
|
||||||
|
|
||||||
|
### 3.4 安全测试
|
||||||
|
|
||||||
|
| 测试项 | 结果 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 越权访问 | ⚠ WARN | 返回404而非403 |
|
||||||
|
| 伪造Token | ✓ PASS | 正确拒绝 |
|
||||||
|
| 过期Token | ✓ PASS | 正确拒绝 |
|
||||||
|
| 访问他人数据 | ✓ PASS | 访问被限制 |
|
||||||
|
| 敏感信息泄露 | ✓ PASS | 未泄露密码/Token |
|
||||||
|
| API限流 | ⚠ INFO | 未触发限流 |
|
||||||
|
| 目录遍历 | ✓ PASS | 攻击被阻止 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、修复建议优先级
|
||||||
|
|
||||||
|
### P0 - 立即修复
|
||||||
|
1. 修复错误密码登录返回200的问题
|
||||||
|
2. 添加 XSS 输入过滤/输出编码
|
||||||
|
|
||||||
|
### P1 - 尽快修复
|
||||||
|
3. 统一登录错误响应码为401
|
||||||
|
4. 配置 API 限流保护
|
||||||
|
|
||||||
|
### P2 - 计划修复
|
||||||
|
5. 越权访问统一返回403
|
||||||
|
6. 确认并修复404的API端点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、测试环境信息
|
||||||
|
|
||||||
|
- **后端容器**: kpl-backend-dev
|
||||||
|
- **数据库**: MySQL 8.0
|
||||||
|
- **测试账号**: admin / admin123
|
||||||
|
- **测试时间**: 2026-01-31 10:30 UTC+8
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本报告由自动化测试系统生成*
|
||||||
@@ -7,7 +7,7 @@ from typing import Optional
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.core.deps import get_db, get_current_user
|
from app.core.deps import get_db, get_current_user, require_admin_or_manager
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.services.growth_path_service import growth_path_service
|
from app.services.growth_path_service import growth_path_service
|
||||||
from app.schemas.growth_path import (
|
from app.schemas.growth_path import (
|
||||||
@@ -118,7 +118,7 @@ async def list_growth_paths(
|
|||||||
page: int = Query(1, ge=1, description="页码"),
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(require_admin_or_manager),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取成长路径列表(管理端)
|
获取成长路径列表(管理端)
|
||||||
@@ -141,7 +141,7 @@ async def list_growth_paths(
|
|||||||
async def create_growth_path(
|
async def create_growth_path(
|
||||||
data: GrowthPathCreate,
|
data: GrowthPathCreate,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(require_admin_or_manager),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
创建成长路径(管理端)
|
创建成长路径(管理端)
|
||||||
@@ -168,7 +168,7 @@ async def create_growth_path(
|
|||||||
async def get_growth_path(
|
async def get_growth_path(
|
||||||
path_id: int,
|
path_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(require_admin_or_manager),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取成长路径详情(管理端)
|
获取成长路径详情(管理端)
|
||||||
@@ -190,7 +190,7 @@ async def update_growth_path(
|
|||||||
path_id: int,
|
path_id: int,
|
||||||
data: GrowthPathUpdate,
|
data: GrowthPathUpdate,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(require_admin_or_manager),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
更新成长路径(管理端)
|
更新成长路径(管理端)
|
||||||
@@ -216,7 +216,7 @@ async def update_growth_path(
|
|||||||
async def delete_growth_path(
|
async def delete_growth_path(
|
||||||
path_id: int,
|
path_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(require_admin_or_manager),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
删除成长路径(管理端)
|
删除成长路径(管理端)
|
||||||
|
|||||||
Reference in New Issue
Block a user