fix: 修复临时邮箱生成失败的BUG (BUG-00001)
【问题描述】 - 用户生成临时邮箱时返回笼统错误:生成临时邮箱失败,请稍后重试 - 错误码:LEGACY_ERROR,无法定位具体失败原因 - 可能原因:API Key未配置、网络问题、API服务不可用等 【修复内容】 1. 改进 gptmail_request 函数 - 添加 API Key 配置检查 - 区分 10+ 种错误类型(CONFIG_ERROR, AUTH_ERROR, TIMEOUT_ERROR等) - 返回结构化错误信息 2. 改进 generate_temp_email 函数 - 返回值改为 (email_addr, error_msg) 元组 - 保留详细错误信息 3. 改进 api_generate_temp_email 函数 - 使用详细错误信息返回给前端 - 添加日志记录 4. 更新前端错误提示 - 显示详细的错误信息 5. 编写单元测试 - 新增 11 个单元测试 - 更新 2 个现有测试的 mock 【测试结果】 - 所有测试通过:51/51 ✅ 【相关文档】 - BUG文档:docs/BUGS/BUG-00001-临时邮箱生成失败.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
14b1a7fa09
commit
a9b3e4254f
16 files changed
+592
-18
No files matched your search
@@ -0,0 +1,287 @@
|
||||
# BUG-00001:临时邮箱生成失败
|
||||
|
||||
## 基本信息
|
||||
|
||||
- **BUG编号**:BUG-00001
|
||||
- **发现日期**:2026-02-23
|
||||
- **报告人**:用户
|
||||
- **严重程度**:P1(高优先级)
|
||||
- **状态**:✅ 已修复
|
||||
- **影响范围**:临时邮箱功能
|
||||
- **修复日期**:2026-02-23
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 用户错误信息
|
||||
```
|
||||
生成临时邮箱失败,请稍后重试
|
||||
```
|
||||
|
||||
### 错误详情
|
||||
- **Code**: LEGACY_ERROR
|
||||
- **Type**: LegacyError
|
||||
- **Status**: 400
|
||||
- **Trace ID**: 557910def72a4ea7a59c8b6fb7880a98
|
||||
|
||||
## 复现步骤
|
||||
|
||||
1. 登录系统
|
||||
2. 进入临时邮箱管理页面
|
||||
3. 点击"生成临时邮箱"按钮
|
||||
4. 系统返回错误提示
|
||||
|
||||
## 预期行为
|
||||
|
||||
- 成功生成临时邮箱地址
|
||||
- 返回邮箱地址给用户
|
||||
- 在数据库中记录该临时邮箱
|
||||
|
||||
## 实际行为
|
||||
|
||||
- 生成失败
|
||||
- 返回笼统的错误信息
|
||||
- 用户无法知道具体失败原因
|
||||
|
||||
## 技术分析
|
||||
|
||||
### 相关代码位置
|
||||
|
||||
1. **路由层**:`outlook_web/routes/temp_emails.py:9`
|
||||
- 路由:`/api/temp-emails/generate`
|
||||
- 方法:POST
|
||||
|
||||
2. **业务逻辑层**:`outlook_web/legacy.py:4171-4186`
|
||||
```python
|
||||
def api_generate_temp_email():
|
||||
"""生成新的临时邮箱"""
|
||||
data = request.json or {}
|
||||
prefix = data.get('prefix')
|
||||
domain = data.get('domain')
|
||||
|
||||
email_addr = generate_temp_email(prefix, domain)
|
||||
|
||||
if email_addr:
|
||||
if add_temp_email(email_addr):
|
||||
log_audit('create', 'temp_email', email_addr, "生成临时邮箱")
|
||||
return jsonify({'success': True, 'email': email_addr, 'message': '临时邮箱创建成功'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '邮箱已存在'})
|
||||
else:
|
||||
retursonify({'success': False, 'error': '生成临时邮箱失败,请稍后重试'})
|
||||
```
|
||||
|
||||
3. **服务层**:`outlook_web/services/gptmail.py:42-57`
|
||||
```python
|
||||
def generate_temp_email(prefix: str = None, domain: str = None) -> Optional[str]:
|
||||
"""生成临时邮箱地址"""
|
||||
json_data = {}
|
||||
if prefix:
|
||||
json_data["prefix"] = prefix
|
||||
if domain:
|
||||
json_data["domain"] = domain
|
||||
|
||||
if json_data:
|
||||
result = gptmail_request("POST", "/api/generate-email", json_data=json_data)
|
||||
else:
|
||||
result = gptmail_request("GET", "/api/generate-email")
|
||||
|
||||
if result and result.get("success"):
|
||||
return result.get("data", {}).get("email")
|
||||
return None
|
||||
```
|
||||
|
||||
4. **错误处理**:`outlook_web/legacy.py:253-267`
|
||||
- 将字符串错误转换为 LEGACY_ERROR
|
||||
|
||||
### 根本原因分析
|
||||
|
||||
1. **直接原因**:`generate_temp_email()` 函数返回 `None`
|
||||
|
||||
2. **可能的根本原因**:
|
||||
- ❌ GPTMail API Key 未配置或无效
|
||||
- ❌ GPTMail API 服务不可用
|
||||
- ❌ 网络连接问题
|
||||
- ❌ API 请求超时(30秒)
|
||||
- ❌ API 返回数据格式不符合预期
|
||||
|
||||
3. **错误处理问题**:
|
||||
- ❌ `gptmail_request` 返回的详细错误信息被丢弃
|
||||
- ❌ `generate_temp_email` 只返回 None,不返回错误原因
|
||||
- ❌ `api_generate_temp_email` 使用笼统的错误信息
|
||||
- ❌ 没有记录详细的错误日志
|
||||
|
||||
### 错误传播链
|
||||
|
||||
```
|
||||
GPTMail API 失败
|
||||
↓
|
||||
gptmail_request() 返回 {"success": False, "error": "具体错误"}
|
||||
↓
|
||||
generate_temp_email() 返回 None(错误信息丢失)
|
||||
↓
|
||||
api_generate_temp_email() 返回 "生成临时邮箱失败,请稍后重试"
|
||||
↓
|
||||
错误处理中间件转换为 LEGACY_ERROR
|
||||
↓
|
||||
用户看到笼统的错误信息
|
||||
```
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 方案1:改进错误传播(推荐)
|
||||
|
||||
1. **修改 `generate_temp_email` 函数**:
|
||||
- 返回 `(email_addr, error_msg)` 元组
|
||||
- 保留详细的错误信息
|
||||
|
||||
2. **修改 `api_generate_temp_email` 函数**:
|
||||
- 使用详细的错误信息
|
||||
- 添加错误日志记录
|
||||
|
||||
3. **改进 `gptmail_request` 函数**:
|
||||
- 返回更详细的错误信息
|
||||
- 区分不同类型的错误(网络错误、API错误、超时等)
|
||||
|
||||
### 方案2:使用异常处理
|
||||
|
||||
1. 在 `gptmail_request` 中抛出自定义异常
|
||||
2. 在 `api_generate_temp_email` 中捕获并处理异常
|
||||
3. 返回详细的错误信息给用户
|
||||
|
||||
### 推荐实施步骤
|
||||
|
||||
1. ✅ 创建 BUG 文档
|
||||
2. ✅ 改进 `gptmail_request` 函数的错误返回
|
||||
3. ✅ 改进 `generate_temp_email` 函数的返回值
|
||||
4. ✅ 改进 `api_generate_temp_email` 函数的错误处理
|
||||
5. ✅ 添加详细的错误日志
|
||||
6. ✅ 更新前端错误提示
|
||||
7. ✅ 编写单元测试(11个新测试)
|
||||
8. ✅ 测试验证(51/51 测试通过)
|
||||
|
||||
## 影响评估
|
||||
|
||||
- **用户体验**:严重影响,用户无法使用临时邮箱功能
|
||||
- **数据完整性**:无影响
|
||||
- **系统稳定性**:无影响
|
||||
- **安全性**:无影响
|
||||
|
||||
## 相关文档
|
||||
|
||||
- PRD:`docs/PRD/Outlook邮件管理工具-重构升级PRD.md`
|
||||
- TDD:`docs/TDD/Outlook邮件管理工具-重构升级TDD.md`
|
||||
- 开发者指南:`docs/DEV/00002-前后端拆分-开发者指南.md`
|
||||
|
||||
## 测试计划
|
||||
|
||||
### 单元测试
|
||||
- [x] 测试 `gptmail_request` 的各种错误场景
|
||||
- API Key 未配置
|
||||
- API Key 无效(401)
|
||||
- 请求超时
|
||||
- 网络连接失败
|
||||
- 服务器错误(500)
|
||||
- 请求频率超限(429)
|
||||
- [x] 测试 `generate_temp_email` 的错误处理
|
||||
- 成功生成
|
||||
- API Key 未配置
|
||||
- 请求超时
|
||||
- 数据格式错误
|
||||
- 使用 prefix 和 domain
|
||||
- [x] 测试 `api_generate_temp_email` 的错误响应(已包含在原有测试中)
|
||||
|
||||
### 集成测试
|
||||
- [x] 测试 API Key 无效的场景(通过 mock)
|
||||
- [x] 测试网络超时的场景(通过 mock)
|
||||
- [x] 测试 API 服务不可用的场景(通过 mock)
|
||||
- [x] 测试正常生成临时邮箱的场景
|
||||
|
||||
### 回归测试
|
||||
- [x] 验证其他临时邮箱功能不受影响
|
||||
- [x] 验证错误处理中间件正常工作
|
||||
- [x] 所有测试通过(51/51)
|
||||
|
||||
## 修复记录
|
||||
|
||||
- **2026-02-23 20:30**:创建 BUG 文档,完成根本原因分析
|
||||
- **2026-02-23 20:45**:实施修复方案
|
||||
- 改进 `gptmail_request` 函数,返回详细的错误信息(区分 10+ 种错误类型)
|
||||
- 改进 `generate_temp_email` 函数,返回 `(email_addr, error_msg)` 元组
|
||||
- 改进 `api_generate_temp_email` 函数,使用详细错误信息并添加日志
|
||||
- 更新前端错误提示,显示详细错误信息
|
||||
- 编写 11 个新的单元测试
|
||||
- 更新 2 个现有测试的 mock
|
||||
- **2026-02-23 21:00**:测试验证通过(51/51 测试全部通过)
|
||||
- **2026-02-23 21:05**:准备提交修复
|
||||
|
||||
## 修复详情
|
||||
|
||||
### 1. 改进 `gptmail_request` 函数(outlook_web/services/gptmail.py)
|
||||
|
||||
**改进内容**:
|
||||
- 添加 API Key 配置检查
|
||||
- 区分 10+ 种错误类型:
|
||||
- `CONFIG_ERROR`:API Key 未配置
|
||||
- `AUTH_ERROR`:API Key 无效(401)
|
||||
- `PERMISSION_ERROR`:访问被拒绝(403)
|
||||
- `RATE_LIMIT_ERROR`:请求频率超限(429)
|
||||
- `SERVER_ERROR`:服务器错误(500+)
|
||||
- `HTTP_ERROR`:其他 HTTP 错误
|
||||
- `TIMEOUT_ERROR`:请求超时
|
||||
- `CONNECTION_ERROR`:网络连接失败
|
||||
- `REQUEST_ERROR`:其他请求异常
|
||||
- `METHOD_ERROR`:不支持的请求方法
|
||||
- `UNKNOWN_ERROR`:未知错误
|
||||
- 返回结构化错误信息:`{success, error, error_type, details}`
|
||||
|
||||
### 2. 改进 `generate_temp_email` 函数(outlook_web/services/gptmail.py)
|
||||
|
||||
**改进内容**:
|
||||
- 返回值从 `Optional[str]` 改为 `Tuple[Optional[str], Optional[str]]`
|
||||
- 成功时返回 `(email_addr, None)`
|
||||
- 失败时返回 `(None, error_message)`,保留详细错误信息
|
||||
- 添加数据格式验证
|
||||
|
||||
### 3. 改进 `api_generate_temp_email` 函数(outlook_web/legacy.py)
|
||||
|
||||
**改进内容**:
|
||||
- 解包 `generate_temp_email` 的返回值:`email_addr, error_msg = generate_temp_email(...)`
|
||||
- 使用详细的错误信息返回给前端
|
||||
- 添加日志记录(成功、警告、错误三个级别)
|
||||
|
||||
### 4. 更新前端错误提示(static/js/features/temp_emails.js)
|
||||
|
||||
**改进内容**:
|
||||
- 检查错误对象的结构
|
||||
- 优先显示 `error.message` 或 `error.error`
|
||||
- 支持结构化错误对象和字符串错误
|
||||
- 保留错误详情用于调试
|
||||
|
||||
### 5. 编写单元测试(tests/test_gptmail_error_handling.py)
|
||||
|
||||
**新增测试**:
|
||||
- 11 个新的单元测试,覆盖所有错误场景
|
||||
- 使用 mock 模拟各种失败情况
|
||||
- 验证错误类型和错误信息的正确性
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 修复前
|
||||
- 用户看到:`生成临时邮箱失败,请稍后重试`
|
||||
- 错误码:`LEGACY_ERROR`
|
||||
- 无法知道具体失败原因
|
||||
|
||||
### 修复后
|
||||
- 用户看到详细错误信息,例如:
|
||||
- `GPTMail API Key 未配置(请在系统设置中配置 GPTMail API Key)`
|
||||
- `API 请求超时(请求超过 30 秒未响应,请检查网络连接或稍后重试)`
|
||||
- `无法连接到 GPTMail 服务(网络连接失败: ...)`
|
||||
- `API Key 无效或已过期(HTTP 401: 请检查 API Key 是否正确)`
|
||||
- 后端日志记录详细的错误信息
|
||||
- 开发者可以快速定位问题
|
||||
|
||||
## 备注
|
||||
|
||||
- 建议同时检查其他使用 `gptmail_request` 的函数,确保错误处理一致
|
||||
- 考虑添加 GPTMail API 健康检查功能
|
||||
- 考虑在设置页面添加 API Key 验证功能
|
||||
+13
-4
@@ -4170,20 +4170,29 @@ def api_get_temp_emails():
|
||||
@login_required
|
||||
def api_generate_temp_email():
|
||||
"""生成新的临时邮箱"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
data = request.json or {}
|
||||
prefix = data.get('prefix')
|
||||
domain = data.get('domain')
|
||||
|
||||
email_addr = generate_temp_email(prefix, domain)
|
||||
|
||||
|
||||
# 调用改进后的 generate_temp_email,返回 (email_addr, error_msg)
|
||||
email_addr, error_msg = generate_temp_email(prefix, domain)
|
||||
|
||||
if email_addr:
|
||||
# 成功生成邮箱地址
|
||||
if add_temp_email(email_addr):
|
||||
log_audit('create', 'temp_email', email_addr, "生成临时邮箱")
|
||||
logger.info(f"临时邮箱生成成功: {email_addr}")
|
||||
return jsonify({'success': True, 'email': email_addr, 'message': '临时邮箱创建成功'})
|
||||
else:
|
||||
logger.warning(f"临时邮箱已存在: {email_addr}")
|
||||
return jsonify({'success': False, 'error': '邮箱已存在'})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '生成临时邮箱失败,请稍后重试'})
|
||||
# 生成失败,返回详细错误信息
|
||||
logger.error(f"临时邮箱生成失败: {error_msg}, prefix={prefix}, domain={domain}")
|
||||
return jsonify({'success': False, 'error': error_msg or '生成临时邮箱失败,请稍后重试'})
|
||||
|
||||
|
||||
@app.route('/api/temp-emails/<path:email_addr>', methods=['DELETE'])
|
||||
|
||||
+119
-11
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
@@ -14,15 +14,32 @@ def gptmail_request(
|
||||
params: dict = None,
|
||||
json_data: dict = None,
|
||||
) -> Optional[Dict]:
|
||||
"""发送 GPTMail API 请求"""
|
||||
"""
|
||||
发送 GPTMail API 请求
|
||||
|
||||
返回格式:
|
||||
- 成功:{"success": True, "data": {...}}
|
||||
- 失败:{"success": False, "error": "错误信息", "error_type": "错误类型", "details": "详细信息"}
|
||||
"""
|
||||
try:
|
||||
url = f"{config.get_gptmail_base_url()}{endpoint}"
|
||||
api_key = get_gptmail_api_key()
|
||||
|
||||
# 检查 API Key 是否配置
|
||||
if not api_key:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "GPTMail API Key 未配置",
|
||||
"error_type": "CONFIG_ERROR",
|
||||
"details": "请在系统设置中配置 GPTMail API Key"
|
||||
}
|
||||
|
||||
headers = {
|
||||
"X-API-Key": api_key,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# 发送请求
|
||||
if method.upper() == "GET":
|
||||
response = requests.get(url, headers=headers, params=params, timeout=30)
|
||||
elif method.upper() == "POST":
|
||||
@@ -30,17 +47,90 @@ def gptmail_request(
|
||||
elif method.upper() == "DELETE":
|
||||
response = requests.delete(url, headers=headers, params=params, timeout=30)
|
||||
else:
|
||||
return None
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"不支持的请求方法: {method}",
|
||||
"error_type": "METHOD_ERROR",
|
||||
"details": "仅支持 GET、POST、DELETE 方法"
|
||||
}
|
||||
|
||||
# 处理响应
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
return {"success": False, "error": f"API 请求失败: {response.status_code}"}
|
||||
elif response.status_code == 401:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "API Key 无效或已过期",
|
||||
"error_type": "AUTH_ERROR",
|
||||
"details": f"HTTP {response.status_code}: 请检查 API Key 是否正确"
|
||||
}
|
||||
elif response.status_code == 403:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "API 访问被拒绝",
|
||||
"error_type": "PERMISSION_ERROR",
|
||||
"details": f"HTTP {response.status_code}: 请检查 API Key 权限"
|
||||
}
|
||||
elif response.status_code == 429:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "API 请求频率超限",
|
||||
"error_type": "RATE_LIMIT_ERROR",
|
||||
"details": f"HTTP {response.status_code}: 请稍后重试"
|
||||
}
|
||||
elif response.status_code >= 500:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "GPTMail 服务暂时不可用",
|
||||
"error_type": "SERVER_ERROR",
|
||||
"details": f"HTTP {response.status_code}: 服务器错误,请稍后重试"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"API 请求失败",
|
||||
"error_type": "HTTP_ERROR",
|
||||
"details": f"HTTP {response.status_code}: {response.text[:200]}"
|
||||
}
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "API 请求超时",
|
||||
"error_type": "TIMEOUT_ERROR",
|
||||
"details": "请求超过 30 秒未响应,请检查网络连接或稍后重试"
|
||||
}
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "无法连接到 GPTMail 服务",
|
||||
"error_type": "CONNECTION_ERROR",
|
||||
"details": f"网络连接失败: {str(e)[:200]}"
|
||||
}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "网络请求异常",
|
||||
"error_type": "REQUEST_ERROR",
|
||||
"details": f"请求失败: {str(e)[:200]}"
|
||||
}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"请求异常: {str(e)}"}
|
||||
return {
|
||||
"success": False,
|
||||
"error": "未知错误",
|
||||
"error_type": "UNKNOWN_ERROR",
|
||||
"details": f"异常: {str(e)[:200]}"
|
||||
}
|
||||
|
||||
|
||||
def generate_temp_email(prefix: str = None, domain: str = None) -> Optional[str]:
|
||||
"""生成临时邮箱地址"""
|
||||
def generate_temp_email(prefix: str = None, domain: str = None) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
生成临时邮箱地址
|
||||
|
||||
返回:
|
||||
- (email_addr, None) - 成功
|
||||
- (None, error_message) - 失败,包含详细错误信息
|
||||
"""
|
||||
json_data = {}
|
||||
if prefix:
|
||||
json_data["prefix"] = prefix
|
||||
@@ -52,9 +142,28 @@ def generate_temp_email(prefix: str = None, domain: str = None) -> Optional[str]
|
||||
else:
|
||||
result = gptmail_request("GET", "/api/generate-email")
|
||||
|
||||
if result and result.get("success"):
|
||||
return result.get("data", {}).get("email")
|
||||
return None
|
||||
if not result:
|
||||
return None, "GPTMail API 请求失败"
|
||||
|
||||
if result.get("success"):
|
||||
email = result.get("data", {}).get("email")
|
||||
if email:
|
||||
return email, None
|
||||
else:
|
||||
return None, "API 返回数据格式错误:缺少 email 字段"
|
||||
else:
|
||||
# 返回详细的错误信息
|
||||
error = result.get("error", "未知错误")
|
||||
error_type = result.get("error_type", "UNKNOWN")
|
||||
details = result.get("details", "")
|
||||
|
||||
# 组合错误信息
|
||||
if details:
|
||||
error_message = f"{error}({details})"
|
||||
else:
|
||||
error_message = error
|
||||
|
||||
return None, error_message
|
||||
|
||||
|
||||
def get_temp_emails_from_api(email_addr: str) -> Optional[List[Dict]]:
|
||||
@@ -83,4 +192,3 @@ def clear_temp_emails_from_api(email_addr: str) -> bool:
|
||||
"""清空 GPTMail 邮箱的所有邮件"""
|
||||
result = gptmail_request("DELETE", "/api/emails/clear", params={"email": email_addr})
|
||||
return bool(result and result.get("success", False))
|
||||
|
||||
@@ -84,7 +84,16 @@
|
||||
loadTempEmails(true);
|
||||
loadGroups();
|
||||
} else {
|
||||
handleApiError(data, '生成临时邮箱失败');
|
||||
// 显示详细的错误信息
|
||||
const errorMsg = data.error || '生成临时邮箱失败';
|
||||
if (data.error && typeof data.error === 'object') {
|
||||
// 结构化错误对象
|
||||
const detailedError = data.error.message || data.error.error || errorMsg;
|
||||
showToast(detailedError, 'error', data.error);
|
||||
} else {
|
||||
// 字符串错误
|
||||
showToast(errorMsg, 'error');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('生成临时邮箱失败', 'error');
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -172,7 +172,8 @@ class CoreFeatureTests(unittest.TestCase):
|
||||
self._login(client)
|
||||
|
||||
email_addr = f"temp_{uuid.uuid4().hex}@example.com"
|
||||
with patch.object(self.module.impl, "generate_temp_email", return_value=email_addr):
|
||||
# Mock generate_temp_email 返回元组 (email_addr, None)
|
||||
with patch.object(self.module.impl, "generate_temp_email", return_value=(email_addr, None)):
|
||||
created = client.post("/api/temp-emails/generate", json={"prefix": "x", "domain": "y"})
|
||||
self.assertEqual(created.status_code, 200)
|
||||
created_data = created.get_json()
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import requests
|
||||
|
||||
from outlook_web.services import gptmail
|
||||
|
||||
|
||||
class TestGPTMailErrorHandling(unittest.TestCase):
|
||||
"""测试 GPTMail 服务的错误处理"""
|
||||
|
||||
def test_gptmail_request_no_api_key(self):
|
||||
"""测试 API Key 未配置的情况"""
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value=''):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'CONFIG_ERROR')
|
||||
self.assertIn('未配置', result['error'])
|
||||
|
||||
def test_gptmail_request_401_unauthorized(self):
|
||||
"""测试 API Key 无效的情况"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 401
|
||||
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value='invalid_key'):
|
||||
with patch('requests.get', return_value=mock_response):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'AUTH_ERROR')
|
||||
self.assertIn('无效', result['error'])
|
||||
|
||||
def test_gptmail_request_timeout(self):
|
||||
"""测试请求超时的情况"""
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value='test_key'):
|
||||
with patch('requests.get', side_effect=requests.exceptions.Timeout()):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'TIMEOUT_ERROR')
|
||||
self.assertIn('超时', result['error'])
|
||||
|
||||
def test_gptmail_request_connection_error(self):
|
||||
"""测试网络连接失败的情况"""
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value='test_key'):
|
||||
with patch('requests.get', side_effect=requests.exceptions.ConnectionError('Connection refused')):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'CONNECTION_ERROR')
|
||||
self.assertIn('连接', result['error'])
|
||||
|
||||
def test_gptmail_request_500_server_error(self):
|
||||
"""测试服务器错误的情况"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value='test_key'):
|
||||
with patch('requests.get', return_value=mock_response):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'SERVER_ERROR')
|
||||
self.assertIn('不可用', result['error'])
|
||||
|
||||
def test_gptmail_request_429_rate_limit(self):
|
||||
"""测试请求频率超限的情况"""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 429
|
||||
|
||||
with patch('outlook_web.services.gptmail.get_gptmail_api_key', return_value='test_key'):
|
||||
with patch('requests.get', return_value=mock_response):
|
||||
result = gptmail.gptmail_request('GET', '/api/test')
|
||||
|
||||
self.assertFalse(result['success'])
|
||||
self.assertEqual(result['error_type'], 'RATE_LIMIT_ERROR')
|
||||
self.assertIn('频率超限', result['error'])
|
||||
|
||||
def test_generate_temp_email_success(self):
|
||||
"""测试成功生成临时邮箱"""
|
||||
mock_result = {
|
||||
'success': True,
|
||||
'data': {'email': 'test@example.com'}
|
||||
}
|
||||
|
||||
with patch('outlook_web.services.gptmail.gptmail_request', return_value=mock_result):
|
||||
email, error = gptmail.generate_temp_email()
|
||||
|
||||
self.assertEqual(email, 'test@example.com')
|
||||
self.assertIsNone(error)
|
||||
|
||||
def test_generate_temp_email_api_key_not_configured(self):
|
||||
"""测试 API Key 未配置时生成临时邮箱"""
|
||||
mock_result = {
|
||||
'success': False,
|
||||
'error': 'GPTMail API Key 未配置',
|
||||
'error_type': 'CONFIG_ERROR',
|
||||
'details': '请在系统设置中配置 GPTMail API Key'
|
||||
}
|
||||
|
||||
with patch('outlook_web.services.gptmail.gptmail_request', return_value=mock_result):
|
||||
email, error = gptmail.generate_temp_email()
|
||||
|
||||
self.assertIsNone(email)
|
||||
self.assertIn('未配置', error)
|
||||
self.assertIn('系统设置', error)
|
||||
|
||||
def test_generate_temp_email_timeout(self):
|
||||
"""测试请求超时时生成临时邮箱"""
|
||||
mock_result = {
|
||||
'success': False,
|
||||
'error': 'API 请求超时',
|
||||
'error_type': 'TIMEOUT_ERROR',
|
||||
'details': '请求超过 30 秒未响应,请检查网络连接或稍后重试'
|
||||
}
|
||||
|
||||
with patch('outlook_web.services.gptmail.gptmail_request', return_value=mock_result):
|
||||
email, error = gptmail.generate_temp_email()
|
||||
|
||||
self.assertIsNone(email)
|
||||
self.assertIn('超时', error)
|
||||
self.assertIn('30 秒', error)
|
||||
|
||||
def test_generate_temp_email_missing_email_field(self):
|
||||
"""测试 API 返回数据缺少 email 字段"""
|
||||
mock_result = {
|
||||
'success': True,
|
||||
'data': {} # 缺少 email 字段
|
||||
}
|
||||
|
||||
with patch('outlook_web.services.gptmail.gptmail_request', return_value=mock_result):
|
||||
email, error = gptmail.generate_temp_email()
|
||||
|
||||
self.assertIsNone(email)
|
||||
self.assertIn('格式错误', error)
|
||||
self.assertIn('email', error)
|
||||
|
||||
def test_generate_temp_email_with_prefix_and_domain(self):
|
||||
"""测试使用 prefix 和 domain 生成临时邮箱"""
|
||||
mock_result = {
|
||||
'success': True,
|
||||
'data': {'email': 'custom@example.com'}
|
||||
}
|
||||
|
||||
with patch('outlook_web.services.gptmail.gptmail_request', return_value=mock_result) as mock_request:
|
||||
email, error = gptmail.generate_temp_email(prefix='custom', domain='example.com')
|
||||
|
||||
self.assertEqual(email, 'custom@example.com')
|
||||
self.assertIsNone(error)
|
||||
|
||||
# 验证调用参数
|
||||
mock_request.assert_called_once()
|
||||
call_args = mock_request.call_args
|
||||
self.assertEqual(call_args[0][0], 'POST')
|
||||
self.assertEqual(call_args[1]['json_data'], {'prefix': 'custom', 'domain': 'example.com'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -187,7 +187,8 @@ class MaskingAuditAndImportTests(unittest.TestCase):
|
||||
unique = uuid.uuid4().hex
|
||||
email_addr = f"tmp_{unique}@example.com"
|
||||
|
||||
with patch.object(self.module.impl, "generate_temp_email", return_value=email_addr):
|
||||
# Mock generate_temp_email 返回元组 (email_addr, None)
|
||||
with patch.object(self.module.impl, "generate_temp_email", return_value=(email_addr, None)):
|
||||
resp = client.post("/api/temp-emails/generate", json={})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.get_json().get("success"), True)
|
||||
|
||||
Reference in new issue
Block a user