You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9.4 KiB
9.4 KiB
Repository 单元测试修复报告 - Phase 2
日期: 2026-02-05
状态: 部分完成
修复阶段: Phase 2(P0/P1/P2 优先级测试)
📋 任务概述
继续修复在 Phase 1 创建的 9 个 repository 单元测试文件,确保它们符合 jointo-tech-stack 规范。
✅ 完成情况
完全修复的测试文件(4/9)
1. test_recharge_repository.py - 14/14 通过 ✅
- 优先级: P0(支付系统)
- 修复内容:
- 发现并修复底层架构违规:
RechargeOrder和PaymentCallback模型未遵循 ADR 006(TIMESTAMPTZ 规范) - 修改
server/app/models/recharge/order.py:- 所有 datetime 字段(
created_at,paid_at,expired_at,processed_at)显式指定sa_column=Column(TIMESTAMP(timezone=True), ...)
- 所有 datetime 字段(
- 创建 Alembic 迁移
20260205_1318_d0066b5be4e2_fix_recharge_orders_timestamp_timezone.py:- 将
recharge_orders和payment_callbacks表的 datetime 列从TIMESTAMP WITHOUT TIME ZONE改为TIMESTAMPTZ
- 将
- 测试代码全部使用
datetime.now(timezone.utc)生成时区感知 datetime 对象
- 发现并修复底层架构违规:
- 覆盖场景: 订单创建、状态更新、过期订单查询、回调记录、多状态筛选、已支付订单查询
2. test_attachment_repository.py - 13/13 通过 ✅
- 优先级: P0(附件系统)
- 修复内容:
- 修正
get_by_owner方法参数顺序:(owner_type, owner_id) - 修正
get_by_checksum方法参数顺序:(file_checksum, storage_backend) - 所有 datetime 字段使用
datetime.now(timezone.utc) - 统一
storage_backend枚举值为 SMALLINT(1=LOCAL, 2=S3, 3=COS)
- 修正
- 覆盖场景: 附件创建、按所有者查询、按 checksum 去重、按存储后端查询、删除附件
3. test_file_checksum_repository.py - 11/11 通过 ✅
- 优先级: P2(文件去重)
- 修复内容:
- 修正参数顺序:
create(storage_backend, checksum, ...) - 修正枚举值:
storage_backend使用 SMALLINT(1=LOCAL, 2=S3, 3=COS) - 所有 datetime 字段使用
datetime.now(timezone.utc) - 修正
count_by_backend方法为实际存在的get_by_backend
- 修正参数顺序:
- 覆盖场景: 记录创建、按 backend+checksum 查询、按 backend 批量查询、引用计数增减
4. test_sms_repository.py - 6/6 通过 ✅
- 优先级: P2(短信验证)
- 修复内容:
- 修正
get_valid_code方法参数顺序:(phone, country_code, purpose) purpose字段使用SmsPurpose枚举(SMALLINT:1=LOGIN, 2=BIND_PHONE, 3=RESET_PASSWORD)- 所有 datetime 字段使用
datetime.now(timezone.utc) - 测试覆盖不同国家代码、不同用途的验证码
- 修正
- 覆盖场景: 验证码创建、有效验证码查询、标记已验证、删除过期验证码、多用途验证码
未修复的测试文件(5/9)
以下测试文件已创建但未完成修复,需要后续处理:
5. test_user_repository.py - 0/8 失败 ❌
- 优先级: P0(用户系统)
- 失败原因: 方法签名或参数类型不匹配
- 待修复方法:
get_by_phone,get_by_username,get_by_email,get_by_wechat_openid,get_by_wechat_unionid,create_session,get_session_by_token,get_sessions_by_user_id
6. test_credit_repository.py - 0/1 失败 ❌
- 优先级: P0(积分系统)
- 失败原因: 方法签名或参数类型不匹配
- 待修复方法:
get_pricing
7. test_ai_model_repository.py - 0/11 失败 ❌
- 优先级: P1(AI 配置)
- 失败原因: 方法签名或参数类型不匹配
- 待修复方法:
create_model,get_by_id,get_by_name,update_model,get_default_model,get_active_models, 等
8. test_ai_quota_repository.py - 0/11 失败 ❌
- 优先级: P1(AI 配额)
- 失败原因: 方法签名或参数类型不匹配
- 待修复方法:
create_quota,get_by_id,get_user_quota,increment_usage,check_quota_available, 等
9. test_ai_usage_log_repository.py - 0/11 失败 ❌
- 优先级: P1(AI 日志)
- 失败原因: 方法签名或参数类型不匹配
- 待修复方法:
create_log,get_by_id,get_by_job_id,get_user_logs,get_user_stats, 等
🔧 重大修复:ADR 006 合规性
问题描述
测试运行中发现 test_recharge_repository.py 抛出 DBAPIError:
invalid input for query argument: can't subtract offset-naive and offset-aware datetimes
根因分析
RechargeOrder和PaymentCallback模型中的 datetime 字段(created_at,paid_at,expired_at,processed_at)未显式指定TIMESTAMP(timezone=True)- SQLModel 默认将 Python
datetime映射为 PostgreSQLTIMESTAMP WITHOUT TIME ZONE - 测试代码使用
datetime.now(timezone.utc)生成时区感知对象,导致与数据库 naive timestamp 不兼容 - 违反 ADR 006:项目强制要求所有事件时间戳使用
TIMESTAMPTZ
解决方案
1. 修改模型定义 (server/app/models/recharge/order.py)
from sqlalchemy import TIMESTAMP
# RechargeOrder
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
sa_column=Column(
TIMESTAMP(timezone=True), # 强制使用 TIMESTAMPTZ
nullable=False
),
description="创建时间(UTC)"
)
2. 创建数据库迁移
alembic revision --autogenerate -m "fix_recharge_orders_timestamp_timezone"
修改迁移脚本,添加显式类型转换:
def upgrade() -> None:
op.execute("""
ALTER TABLE recharge_orders
ALTER COLUMN created_at TYPE TIMESTAMPTZ
USING created_at AT TIME ZONE 'UTC';
-- ... 其他字段
""")
3. 应用迁移
docker compose exec app alembic upgrade head
影响范围
- ✅ 已修复:
RechargeOrder,PaymentCallback - ⚠️ 待审查: 项目中其他可能违反 ADR 006 的模型(建议全局检查所有 datetime 字段)
📊 统计数据
测试通过率
- 已修复: 4/9 文件(44.4%)
- 测试通过: 44/116 测试(37.9%)
- 待修复: 5/9 文件,72 个测试
文件列表
| 文件 | 优先级 | 测试数 | 通过 | 失败 | 状态 |
|---|---|---|---|---|---|
| test_recharge_repository.py | P0 | 14 | ✅ 14 | 0 | 完成 |
| test_attachment_repository.py | P0 | 13 | ✅ 13 | 0 | 完成 |
| test_file_checksum_repository.py | P2 | 11 | ✅ 11 | 0 | 完成 |
| test_sms_repository.py | P2 | 6 | ✅ 6 | 0 | 完成 |
| test_user_repository.py | P0 | 8 | 0 | ❌ 8 | 待修复 |
| test_credit_repository.py | P0 | 1 | 0 | ❌ 1 | 待修复 |
| test_ai_model_repository.py | P1 | 11 | 0 | ❌ 11 | 待修复 |
| test_ai_quota_repository.py | P1 | 11 | 0 | ❌ 11 | 待修复 |
| test_ai_usage_log_repository.py | P1 | 11 | 0 | ❌ 11 | 待修复 |
| 总计 | - | 86 | 44 | 42 | - |
🎯 关键发现
1. 架构合规性至关重要
- ADR 006(TIMESTAMPTZ 强制规范)必须在模型层严格执行
- SQLModel 隐式映射可能导致合规性违规
- 建议所有 datetime 字段显式指定
sa_column=Column(TIMESTAMP(timezone=True), ...)
2. 枚举类型统一
- 所有枚举字段在数据库层使用 SMALLINT 存储
- Python 层使用
IntEnum封装 - 示例:
SmsPurpose.LOGIN = 1,数据库存储为1
3. 时区处理标准
- 禁止:
datetime.utcnow()(生成 naive datetime) - 强制:
datetime.now(timezone.utc)(生成时区感知 datetime)
4. 参数顺序问题
- 多个测试因参数顺序错误导致失败
- 建议在 repository 方法中使用关键字参数(kwargs)提高可读性
📝 后续建议
短期(优先)
- 修复剩余 5 个测试文件(test_user, test_credit, test_ai_model, test_ai_quota, test_ai_usage_log)
- 全局审查 datetime 字段:检查所有模型是否遵循 ADR 006
- 统一参数传递方式:考虑在 repository 层强制使用关键字参数
中期
- 增加 Linter 规则:检测
datetime.utcnow()使用(应替换为datetime.now(timezone.utc)) - 增强迁移检查:在 Alembic 迁移中自动检测
TIMESTAMP WITHOUT TIME ZONE并警告 - 完善测试覆盖:为所有 repository 方法补充边界条件测试
长期
- 建立测试基础设施:创建
BaseRepositoryTest类,统一 fixture 和 helper 方法 - 自动化合规性检查:CI 流程中集成 ADR 合规性验证
- 性能基准测试:为关键 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
📅 时间线
- 2026-02-05 13:18: 创建 Alembic 迁移修复 TIMESTAMPTZ 违规
- 2026-02-05 13:20:
test_recharge_repository.py修复完成(14/14) - 2026-02-05 13:21:
test_attachment_repository.py修复完成(13/13) - 2026-02-05 13:22:
test_file_checksum_repository.py修复完成(11/11) - 2026-02-05 13:23:
test_sms_repository.py修复完成(6/6)
注: 本报告记录了 Phase 2 的修复进度。剩余测试文件将在 Phase 3 继续修复。