# Repository 单元测试修复报告 - Phase 2 (最终版) **日期**: 2026-02-05 **状态**: 阶段性完成 **修复阶段**: Phase 2(P0/P1/P2 优先级测试) --- ## 📊 最终统计 ### 已修复测试(6/9 文件) | 文件 | 优先级 | 测试数 | 通过 | 状态 | |------|--------|--------|------|------| | test_user_repository.py | P0 | 21 | ✅ 21 | **完成** | | test_credit_repository.py | P0 | 18 | ✅ 18 | **完成** | | test_recharge_repository.py | P0 | 14 | ✅ 14 | **完成** | | test_attachment_repository.py | P0 | 13 | ✅ 13 | **完成** | | test_file_checksum_repository.py | P2 | 11 | ✅ 11 | **完成** | | test_sms_repository.py | P2 | 6 | ✅ 6 | **完成** | | **总计** | - | **83** | **83** | **100%** | ### 待修复测试(3/9 文件) | 文件 | 优先级 | 测试数 | 失败 | 问题 | |------|--------|--------|------|------| | test_ai_model_repository.py | P1 | 11 | ❌ 11 | 枚举值错误(CHAT → TEXT) | | test_ai_quota_repository.py | P1 | 11 | ❌ 11 | 待分析 | | test_ai_usage_log_repository.py | P1 | 11 | ❌ 11 | 待分析 | | **总计** | - | **33** | **33** | - | --- ## ✅ 核心修复内容 ### 1. **数据隔离问题(重大发现)** **问题**: - `db_session` fixture 不使用事务回滚(`conftest.py` 第 77 行) - 导致测试数据残留,引发 `MultipleResultsFound` 异常 **解决方案**: - 为所有测试使用唯一标识符(`uuid4().hex[:8]`) - 在创建用户/手机号/邮箱等数据时加入随机后缀 - 对于枚举查询(如 `feature_type`),测试前先清理旧数据 **示例**: ```python class TestUserRepository: def _unique_id(self) -> str: """生成唯一标识符(8位16进制)""" return uuid4().hex[:8] async def test_get_by_phone(self, db_session): unique = self._unique_id() user = User( phone=f"+8613800{unique}", username=f"test_user_{unique}" ) ``` ### 2. **ADR 006 合规性修复(架构级)** **问题**: - `RechargeOrder` 和 `PaymentCallback` 模型违反 ADR 006 - 使用 `TIMESTAMP WITHOUT TIME ZONE` 导致时区数据丢失 - 引发 `DBAPIError: can't subtract offset-naive and offset-aware datetimes` **解决方案**: 1. 修改模型定义(`server/app/models/recharge/order.py`): ```python from sqlalchemy import TIMESTAMP created_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), sa_column=Column( TIMESTAMP(timezone=True), # 强制 TIMESTAMPTZ nullable=False ) ) ``` 2. 创建 Alembic 迁移(`20260205_1318_d0066b5be4e2_fix_recharge_orders_timestamp_timezone.py`): ```sql ALTER TABLE recharge_orders ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; ``` 3. 应用迁移: ```bash docker compose exec app alembic upgrade head ``` **影响**: - 确保所有事件时间戳正确存储时区信息 - 符合项目架构标准 ADR 006 - 避免跨时区数据丢失问题 ### 3. **枚举值使用修正** **修复内容**: - **test_user_repository.py**:微信平台参数使用 `.value` 获取整数值 ```python # 错误:WechatPlatform.MP(枚举对象) # 正确:WechatPlatform.MP.value(整数值 1) await repo.get_by_wechat_openid("openid", WechatPlatform.MP.value) ``` - **test_sms_repository.py**:`purpose` 参数使用 `SmsPurpose` 枚举整数值 ```python # 正确:SmsPurpose.LOGIN 自动转为整数 1 await repo.get_valid_code("phone", "+86", SmsPurpose.LOGIN) ``` - **test_credit_repository.py**:清理重复数据避免 `MultipleResultsFound` ```python # 测试前清理可能存在的相同 feature_type 记录 from sqlalchemy import delete await db_session.execute( delete(CreditPricing).where( CreditPricing.feature_type == test_feature_type, CreditPricing.is_active == True ) ) ``` ### 4. **参数顺序修正** **test_attachment_repository.py**: - `get_by_owner(owner_type, owner_id)` ← 原顺序错误 - `get_by_checksum(file_checksum, storage_backend)` ← 原顺序错误 - `create(storage_backend, checksum, ...)` ← 原顺序错误 **test_file_checksum_repository.py**: - `storage_backend` 使用 SMALLINT(1=LOCAL, 2=S3, 3=COS) **test_sms_repository.py**: - `get_valid_code(phone, country_code, purpose)` ← 修正为正确顺序 --- ## 🎯 关键发现与最佳实践 ### 1. **测试隔离的重要性** - **问题**:`db_session` fixture 不回滚事务导致数据残留 - **教训**:单元测试必须使用唯一标识符确保数据隔离 - **建议**:考虑为单元测试创建独立的 `db_session_isolated` fixture,使用事务回滚 ### 2. **架构合规性检查** - **问题**:模型层违反 ADR 006 未被及时发现 - **教训**:架构标准必须在模型定义时严格执行 - **建议**: - 创建 Linter 规则检测 `TIMESTAMP WITHOUT TIME ZONE` - 在 CI 流程中集成 ADR 合规性验证 - 模型创建时强制使用显式 `sa_column` 定义 ### 3. **枚举类型处理** - **问题**:枚举对象 vs 整数值混淆 - **教训**:repository 层接受整数,service 层处理枚举 - **建议**:在 repository 方法签名中明确标注类型(`platform: int`) ### 4. **datetime 处理标准化** - **禁止**:`datetime.utcnow()` (生成 naive datetime) - **强制**:`datetime.now(timezone.utc)` (生成时区感知 datetime) - **数据库**:所有事件时间戳使用 `TIMESTAMPTZ` --- ## 🔧 待修复问题分析 ### test_ai_model_repository.py (11 个失败) **错误**:`AttributeError: type object 'AIModelType' has no attribute 'CHAT'` **根因**: - 测试代码使用错误的枚举值 `AIModelType.CHAT` - 实际枚举定义: ```python class AIModelType(IntEnum): TEXT = 1 # 文本模型(GPT, Claude 等) IMAGE = 2 # 图片模型(DALL-E, Stable Diffusion 等) VIDEO = 3 # 视频模型(Runway, Pika 等) AUDIO = 4 # 音频模型(TTS, STT 等) ``` **修复方案**: - 将 `AIModelType.CHAT` 替换为 `AIModelType.TEXT` - 检查其他枚举使用(`AIProvider`, `UnitType`) ### test_ai_quota_repository.py + test_ai_usage_log_repository.py (22 个失败) **待分析**: - 可能也是枚举值错误或数据残留问题 - 需要查看具体错误日志后修复 --- ## 📈 进度总结 | 指标 | 数值 | 进度 | |------|------|------| | **已修复文件** | 6/9 | 66.7% | | **测试通过** | 83/116 | 71.6% | | **待修复测试** | 33 | - | | **新增迁移** | 1 个 | - | | **架构违规修复** | ADR 006 | ✅ | --- ## 📝 后续步骤 ### 短期(优先) 1. ✅ **修复 test_ai_model_repository.py**:替换枚举值 `CHAT` → `TEXT` 2. ⚠️ **修复 test_ai_quota_repository.py**:分析具体错误原因 3. ⚠️ **修复 test_ai_usage_log_repository.py**:分析具体错误原因 4. **验证所有测试**:确保 116/116 全部通过 ### 中期 1. **全局 datetime 审查**:检查所有模型是否遵循 ADR 006 2. **创建测试工具类**:统一生成唯一标识符的 helper 方法 3. **优化 conftest.py**:为单元测试添加事务回滚 fixture 4. **Linter 集成**:检测 `datetime.utcnow()` 和 `TIMESTAMP WITHOUT TIME ZONE` ### 长期 1. **CI/CD 集成**:自动运行所有 repository 单元测试 2. **覆盖率报告**:生成测试覆盖率报告 3. **性能测试**:为关键 repository 方法添加性能基准测试 --- ## 🔗 相关文档 - **ADR 006**: `docs/architecture/adrs/006-timestamptz-for-event-timestamps.md` - **后端开发规范**: `.claude/skills/jointo-tech-stack/references/backend.md` - **数据库设计规范**: `.claude/skills/jointo-tech-stack/references/database.md` - **测试规范**: `.claude/skills/jointo-tech-stack/references/testing.md` - **迁移记录**: `server/alembic/versions/20260205_1318_d0066b5be4e2_fix_recharge_orders_timestamp_timezone.py` --- ## 🎉 成就总结 ### Phase 2 完成情况 ✅ **已修复 6 个文件,83 个测试全部通过(100%)** **关键贡献**: 1. 发现并修复架构级违规(ADR 006) 2. 建立测试数据隔离标准 3. 统一 datetime 处理规范 4. 修正枚举类型使用模式 **剩余工作**: - 3 个 AI 相关测试文件需要修复(33 个测试) - 预计主要是枚举值替换,修复难度较低 --- **报告生成时间**: 2026-02-05 13:35 **测试执行环境**: Docker + PostgreSQL 17 + Python 3.12 **测试框架**: pytest + pytest-asyncio **下一步**: 继续修复 `test_ai_model_repository.py`、`test_ai_quota_repository.py`、`test_ai_usage_log_repository.py`