374 lines
23 KiB
Markdown
374 lines
23 KiB
Markdown
# 测试报告(项目自带测试与测试数据集)
|
||||
|
|
|
|||
|
|
> 测试日期:2026-09-11
|
|||
|
|
> 测试对象:`D:\nanfangjijin\group_fqcd_jr`(分支 `yc`,工作区含未提交改动)
|
|||
|
|
> 测试范围:项目内自带的测试与测试数据集(`tests/`、`tools/seed_*.py`、`data/`)
|
|||
|
|
> 测试依据:用户当次指令(用户明确不按 `docs/项目测试/测试架构.md` 执行验收测试)
|
|||
|
|
> 执行环境:Windows + Python 3.13.5 + pytest 8.3.4 + pytest-asyncio 0.26.0,真实本地 MySQL(`jr` 库)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 测试概况
|
|||
|
|
|
|||
|
|
| 层次 | 通过 | 失败 | 跳过 | 说明 |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| 单元测试 `tests/unit` | 466 | 7 | 0 | 另有 1 个用例文件收集失败 |
|
|||
|
|
| 契约测试 `tests/contract` | 4 | 0 | 0 | 全部通过 |
|
|||
|
|
| 集成测试 `tests/integration` | 39 | 30 | 1 | 跳过项为 Redis 真实计数器用例 |
|
|||
|
|
| **合计** | **509** | **37** | **1** | 通过率 509/546 = **93.2%** |
|
|||
|
|
|
|||
|
|
补充事实:
|
|||
|
|
|
|||
|
|
1. `tests/unit/service/test_suitability_service.py` **收集失败**,`pytest` 在收集阶段中断;忽略该文件后才得到上表数据。
|
|||
|
|
2. 失败结果**顺序敏感**:`tests/integration` 全量执行失败 30 条,逐条单独执行时其中 13 条可通过(如 `test_complete_run`、`test_identity_mysql`、`test_memory_extraction`、`test_db_timezone_utc` 第二条),说明失败集合与执行顺序相关。
|
|||
|
|
3. 集成测试**真实读写本地 MySQL**,未使用 Mock 数据库;测试数据会写入并清理业务表。
|
|||
|
|
4. 前端目录 `group_fqcd_jr/前端` 为空,无前端用例可执行。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 缺陷清单
|
|||
|
|
|
|||
|
|
### 2.1 致命:配置发布「同一人自审」路径必然失败
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `tests/integration/test_config_release_mysql.py::test_config_release_lifecycle_is_versioned_and_audited` |
|
|||
|
|
| 复现 | 直接执行该用例 |
|
|||
|
|
| 实际 | `sqlalchemy.exc.OperationalError (3819) Check constraint 'chk_config_release_separation' is violated.` |
|
|||
|
|
| 期望 | 抛出业务异常 `ConfigReleaseError`,并能完成提交/审核/激活/回滚全流程 |
|
|||
|
|
| 根因(事实) | ① `app/service/config_release_service.py:31-36` 注释声明该约束"已于迁移 `20260910_drop_review_separation` 撤下",因此 `approve()` 允许创建人自审;② `alembic/versions/` 目录**不存在**任何 `*separation*` 迁移文件;③ 对 `information_schema.CHECK_CONSTRAINTS` 查询确认线上库**仍存在** `chk_config_release_separation` |
|
|||
|
|
| 影响 | 数据库保持双人复核约束,而代码按"自审合法"执行,二者冲突;单管理员部署下配置发布必定 500,配置中心不可用 |
|
|||
|
|
|
|||
|
|
### 2.2 严重:集成测试基座跨事件循环,约 28 条用例信号失真
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `tests/integration` 中 28 条报错用例(含 `test_offsite_fund_api` 6 条、`test_offsite_notification_send` 3 条、`test_worker_runtime_mysql`、`test_handover_outbox_mysql` 等) |
|
|||
|
|
| 现象 | `RuntimeError: ... got Future <Future pending> attached to a different loop` |
|
|||
|
|
| 根因(事实) | `app/infrastructure/db.py` 在模块导入时创建全局 `engine` + `SessionFactory`;测试同时存在多个事件循环(pytest-asyncio 每个用例一个循环、每个 `TestClient` 一个 portal 线程循环、用例内 `asyncio.run(...)` 再建一个循环)。连接池把上一个循环建立的 MySQL 连接交给新循环使用 |
|
|||
|
|
| 影响 | 用例结果取决于执行顺序:全量跑失败、单条跑通过。测试无法反映真实质量,回归信号不可信 |
|
|||
|
|
| 建议方向 | 用例间统一事件循环作用域;或在 `conftest.py` 中按循环 dispose 引擎;或改用 `httpx.ASGITransport` 异步客户端替代 `TestClient`,避免 portal 线程循环 |
|
|||
|
|
|
|||
|
|
### 2.3 一般:单元测试读取真实 `.env`,5 条用例被本机配置污染
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `test_offsite_document_recognition_adapter.py` 4 条、`test_offsite_mail_worker.py::test_worker_rejects_real_imap_processing_with_mock_recognition` |
|
|||
|
|
| 现象 | ① 用例名为「默认走 Mock 不调外部」却真实发起阿里云 DocMind 调用,报 `UnretryableException ... WinError 10013`;② `health_check` 期望 `disabled` 实得 `ok`;③ Worker 用例返回 `True`,期望 `False` |
|
|||
|
|
| 根因(事实) | `app/core/config.py:100` 配置 `SettingsConfigDict(env_file=".env")`;用例辅助函数 `_settings(**updates)` 只传入少数字段后构造 `Settings(**values)`,其余字段仍从项目 `.env` 读取。本机 `.env` 中 `OFFSITE_OCR_ENABLED=true`、`OFFSITE_DEEPSEEK_ENABLED=true`,且已填真实密钥 |
|
|||
|
|
| 影响 | 单元测试结果依赖开发者本机 `.env`,换机器结论不同;同时存在真实外部调用风险 |
|
|||
|
|
| 建议方向 | 测试构造 `Settings(_env_file=None, ...)`,显式关闭环境文件读取 |
|
|||
|
|
|
|||
|
|
### 2.4 一般:接口用例与响应契约不同步,3 条用例失败
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `test_agent_runs_api.py::test_create_agent_run_returns_202_and_addresses`、`test_worker_runtime_mysql.py` 2 条 |
|
|||
|
|
| 现象 | HTTP 状态 202 正确,但 `body["run_id"]` 抛 `KeyError` |
|
|||
|
|
| 根因(事实) | 实测同一路径返回信封体 `{"data": {"run_id": ...}, "meta": {...}}`(与 `docs/05-接口文档.md` §3.3 一致),用例仍按旧的平铺结构读取 |
|
|||
|
|
| 影响 | 用例未随接口契约更新,掩盖真实回归 |
|
|||
|
|
|
|||
|
|
### 2.5 一般:死模块未清理,1 条回归用例失败
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `tests/unit/api/test_config_release_authority.py::test_legacy_config_release_controller_module_is_gone` |
|
|||
|
|
| 现象 | `assert ModuleSpec(... config_releases ...) is None` 失败 |
|
|||
|
|
| 根因(事实) | `app/api/controllers/config_releases.py` 仍存在于磁盘;该文件未被 `app/main.py` 注册(第 53-61 行只注册 `admin_router` 等 9 个路由),属于第二套状态机入口 |
|
|||
|
|
| 影响 | 用例意图是消除"该用哪套接口"的歧义,清理动作未完成 |
|
|||
|
|
|
|||
|
|
### 2.6 一般:用例文件与 Service 重构不同步,整文件收集失败
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `tests/unit/service/test_suitability_service.py` |
|
|||
|
|
| 现象 | `ImportError: cannot import name 'SuitabilityRequest' from 'app.service.suitability_service'`,pytest 收集中断 |
|
|||
|
|
| 根因(事实) | `app/service/suitability_service.py` 现有对外类型为 `RiskAuthorityProfile` / `SuitabilityToolInput` / `SuitabilityDecision`,已无 `SuitabilityRequest` |
|
|||
|
|
| 影响 | 该文件的全部用例无法执行,并使整次收集带错误码 |
|
|||
|
|
|
|||
|
|
### 2.7 一般:推介材料接口状态码与错误码不符合用例约定,2 条用例失败
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 用例 | `test_promotion_material_api.py::test_promotion_material_http_rejects_missing_key_and_permission` |
|
|||
|
|
| 现象 | 缺少 `Idempotency-Key` 时实际返回 `422`,期望 `400` + `error.code=VALIDATION_ERROR` |
|
|||
|
|
| 用例 | `test_promotion_material_api.py::test_promotion_material_http_workflow_and_advisor_scope` |
|
|||
|
|
| 现象 | 跨投顾读取时实际返回 `SESSION_NOT_FOUND`,期望 `RESOURCE_NOT_FOUND` |
|
|||
|
|
| 影响 | 至少一方与文档不符:要么接口应做幂等键显式校验并返回统一信封,要么用例需按现状更新 |
|
|||
|
|
|
|||
|
|
### 2.8 轻微:环境缺依赖,Redis 相关用例跳过且限流降级
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 现象 | `tests/integration/test_rate_limit_redis.py` 跳过;接口日志出现 `ModuleNotFoundError: No module named 'redis'` |
|
|||
|
|
| 根因(事实) | `requirements.txt` 声明 `redis`,但当前解释器未安装该包;`app/infrastructure/rate_limiter.py:58` 懒加载失败后按设计降级放行(fail-open),不影响主流程 |
|
|||
|
|
| 影响 | 限流真实链路未被验证,限流用例信号缺失 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 未覆盖风险
|
|||
|
|
|
|||
|
|
| 项 | 原因 | 风险等级 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 验收测试(`tools/acceptance_check.py`、越权/防重/参数校验矩阵) | 用户指定仅执行项目自带测试数据 | 中 |
|
|||
|
|
| `docs/项目要求/` 需求与知识文件 | 该目录不存在,无需求/知识文件可提炼检索问答用例 | 中 |
|
|||
|
|
| Redis 限流真实链路 | 本机未安装 `redis` 包 | 中 |
|
|||
|
|
| Milvus / Neo4j 真实链路 | 本次未启动也未验证连通性 | 中 |
|
|||
|
|
| 性能压测(`tools/performance_baseline.py`) | 本次未执行 | 低 |
|
|||
|
|
| 前端用例 | `前端` 目录为空 | 低 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 验收结论(对照用户指定范围)
|
|||
|
|
|
|||
|
|
| 检查项 | 结论 |
|
|||
|
|
|---|---|
|
|||
|
|
| 项目自带单元测试全通过 | 不通过(7 失败 + 1 文件收集失败) |
|
|||
|
|
| 项目自带契约测试全通过 | 通过(4/4) |
|
|||
|
|
| 项目自带集成测试全通过 | 不通过(30 失败,39 通过,1 跳过) |
|
|||
|
|
| 回归通过率 100% | 不通过(37/546 失败) |
|
|||
|
|
|
|||
|
|
**总体结论:不通过。** 其中 1 条为真实业务缺陷(配置发布自审路径必然失败),28 条为测试基座跨事件循环导致的失真,5 条由本机 `.env` 污染引起,其余为用例与代码不同步及环境缺依赖。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 建议修复顺序
|
|||
|
|
|
|||
|
|
1. **P0** 配置发布:确认自审是否为既定业务规则。若是,补 `20260910_drop_review_separation` 迁移并撤下数据库约束;若不是,在 `ConfigReleaseService.approve()` 恢复"创建人不得自审"的业务校验。
|
|||
|
|
2. **P1** 测试基座:统一事件循环或在用例间 dispose 异步引擎,恢复 28 条集成用例的有效信号。
|
|||
|
|
3. **P1** 测试隔离:单元测试构造 `Settings(_env_file=None, ...)`,禁止读取本机 `.env` 与真实密钥。
|
|||
|
|
4. **P2** 同步过期用例:`test_agent_runs_api`(响应信封)、`test_suitability_service`(类型改名)、`test_config_release_authority`(删除死模块)。
|
|||
|
|
5. **P2** 对齐推介材料接口的状态码与错误码,并同步文档。
|
|||
|
|
6. **P3** 安装 `redis` 依赖并启用限流真实链路用例。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 修复记录:配置发布自审(2026-09-11)
|
|||
|
|
|
|||
|
|
业务口径由用户确认:**允许创建人自审**。
|
|||
|
|
|
|||
|
|
### 6.1 现状复核(事实)
|
|||
|
|
|
|||
|
|
| 证据 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| `tests/unit/service/test_config_release_self_review.py` | 明确要求「自审允许,并如实写入 `reviewer_id = 创建人`」,并注明约束已由迁移撤下 |
|
|||
|
|
| `app/service/config_release_service.py` | 原注释同样声明约束"已于迁移 `20260910_drop_review_separation` 撤下" |
|
|||
|
|
| `alembic/versions/` | **不存在**该迁移文件,`information_schema` 也证明约束仍在线上库 |
|
|||
|
|
| `app/service/admin_service.py` | 激活分支要求 `row["reviewer_id"] is not None`,否则报「版本未审核」 |
|
|||
|
|
|
|||
|
|
结论:撤下约束是唯一自洽方案——若改为"自审留空 `reviewer_id`",激活分支会把已审核版本判为未审核,自审版本无法激活。
|
|||
|
|
|
|||
|
|
### 6.2 变更内容
|
|||
|
|
|
|||
|
|
| 文件 | 变更 |
|
|||
|
|
|---|---|
|
|||
|
|
| `alembic/versions/20260911_drop_review_separation.py` | 新增迁移:撤下 `config_release` 的 `chk_config_release_separation`;`downgrade` 可重建 |
|
|||
|
|
| `app/service/config_release_service.py` | `approve()` 自审与多人复核一致如实写入 `reviewer_id`;注释改为指向真实迁移 |
|
|||
|
|
| `app/service/admin_service.py` | 驳回分支自审同样写入 `reviewer_id`,与通过分支一致;注释同步 |
|
|||
|
|
| `tests/integration/test_config_release_mysql.py` | 对应用例编码新规则:自审通过且 `reviewer_id == 创建人`;另补他人复核分支断言 |
|
|||
|
|
| `tests/unit/service/test_config_release_self_review.py` | 注释中的迁移名更正为本仓库真实文件名 |
|
|||
|
|
| `docs/02-数据库建表设计.md` | 移除该约束并加变更记录,说明表名与字段定义未改动 |
|
|||
|
|
|
|||
|
|
### 6.3 数据库变更验证
|
|||
|
|
|
|||
|
|
| 项 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| 升级 `alembic upgrade head` | 通过,head = `20260911_drop_review_separation` |
|
|||
|
|
| 升级后约束 | 仅剩 `chk_config_release_status`,`chk_config_release_separation` 已撤下 |
|
|||
|
|
| 回滚 `alembic downgrade -1` | 通过(库中自审行数为 0,约束可重建) |
|
|||
|
|
| 字段/索引/外键指纹(降级后 vs 升级后) | 68 张表全部一致,差异表为空 |
|
|||
|
|
| 升级前 vs 升级后指纹 | 一致,证明只撤约束、未改任何字段 |
|
|||
|
|
|
|||
|
|
### 6.4 复测结果
|
|||
|
|
|
|||
|
|
| 用例 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| `test_config_release_mysql.py`(单条执行) | 通过 |
|
|||
|
|
| `test_config_release_self_review.py`(4 条) | 通过 |
|
|||
|
|
| `test_admin_service.py`(12 条) | 通过 |
|
|||
|
|
| `ruff check app tests alembic` | 我改动的文件全部通过(另存在 1 处与本次无关的 `I001`:`tests/unit/service/test_offsite_fund_rules.py`) |
|
|||
|
|
|
|||
|
|
注意:全量执行集成测试时该用例仍会失败——原因仍是第 2.2 节的跨事件循环问题,单条执行即通过。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 修复记录:测试基座与环境隔离(2026-09-11)
|
|||
|
|
|
|||
|
|
### 7.1 跨事件循环(报告 2.2 节)
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 变更 | `tests/conftest.py` 在导入任何 app 模块前,把测试期引擎换成 `NullPool`,等价地替换 `SessionFactory` |
|
|||
|
|
| 原因 | 应用引擎在导入时创建,连接池里的连接绑定在建立它的事件循环上;测试同时存在 pytest-asyncio 循环、`TestClient` portal 循环和 `asyncio.run` 循环,连接被跨循环复用即报 `got Future attached to a different loop` |
|
|||
|
|
| 生产影响 | 无。`app/infrastructure/db.py` 未改动,生产仍用默认连接池 |
|
|||
|
|
| 效果 | 集成测试失败数 30 → 4(详见 7.3) |
|
|||
|
|
|
|||
|
|
补充:`tools/seed_test_rbac.py` 需要按 `python -m tools.seed_test_rbac` 运行(直接以脚本路径执行会
|
|||
|
|
因 `sys.path` 缺项目根而报 `No module named 'app'`)。执行种子后,依赖 9001/9002/9003 号段账号的
|
|||
|
|
用例(转人工、运行取消、complete_run)恢复通过。
|
|||
|
|
|
|||
|
|
### 7.2 单元测试读取真实 `.env`(报告 2.3 节)
|
|||
|
|
|
|||
|
|
| 项 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 变更 | `tests/unit/service/test_offsite_document_recognition_adapter.py`、`tests/unit/worker/test_offsite_mail_worker.py` 的 `_settings()` 显式给定外部识别开关与占位凭据 |
|
|||
|
|
| 原因 | `app/core/config.py` 导入时执行 `load_dotenv`,把本机 `.env` 灌进 `os.environ`;仅用 `_env_file=None` 挡不住环境变量,构造参数优先级最高才是可靠隔离 |
|
|||
|
|
| 效果 | 该 5 条用例全部通过,且不再可能触发真实外部调用 |
|
|||
|
|
|
|||
|
|
### 7.3 与文档不同步的用例
|
|||
|
|
|
|||
|
|
| 文件 | 变更 | 依据 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `tests/integration/test_agent_runs_api.py` | 响应体改读 `data` 信封 | `docs/05-接口文档.md` §3.3 与 §19「成功响应均使用第 3.3 节信封」 |
|
|||
|
|
| `tests/integration/test_worker_runtime_mysql.py` | 同上 | 同上 |
|
|||
|
|
|
|||
|
|
### 7.4 复测结果对比
|
|||
|
|
|
|||
|
|
| 层次 | 修复前 | 修复后 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 单元测试 | 466 通过 / 7 失败 | **472 通过 / 1 失败** |
|
|||
|
|
| 契约测试 | 4 通过 | 4 通过 |
|
|||
|
|
| 集成测试 | 39 通过 / 30 失败 / 1 跳过 | **65 通过 / 4 失败 / 1 跳过** |
|
|||
|
|
| 合计 | 509 通过 / 37 失败 | **541 通过 / 5 失败** |
|
|||
|
|
| 另有收集失败文件 | 1 | 1(未变) |
|
|||
|
|
|
|||
|
|
### 7.5 剩余失败与待决策项
|
|||
|
|
|
|||
|
|
| # | 用例 | 性质 | 说明 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 1 | `test_config_release_authority.py::test_legacy_config_release_controller_module_is_gone` | 待确认 | 用例要求删除死模块 `app/api/controllers/config_releases.py`(未被 `main.py` 注册)。删除文件属需确认动作 |
|
|||
|
|
| 2 | `test_suitability_service.py` 整文件收集失败 | 待同步 | 用例引用已改名类型 `SuitabilityRequest`(现为 `SuitabilityToolInput`),需同步用例 |
|
|||
|
|
| 3 | `test_promotion_material_api.py` 2 条 | 待决策 | ①缺少幂等键返回 FastAPI 默认 422,未走统一错误信封(`app/main.py` 只注册了 `AgentError` 处理器);②跨投顾读取返回 `SESSION_NOT_FOUND`(`docs/05` §3.6 口径),用例期望 `RESOURCE_NOT_FOUND`(`docs/09` 口径)——两份文档错误码表冲突,需定权威口径 |
|
|||
|
|
| 4 | `test_run_cancellation_mysql.py::test_cancel_is_idempotent_and_terminates_original_request` | 偶发 | 重复取消时第二次返回 `cancelled` 而非同一 `cancel_requested`;3 次执行中 1 次失败 |
|
|||
|
|
| 5 | `test_worker_runtime_mysql.py::test_http_accept_worker_commit_query_and_repeat[False]` | 稳定复现 | 并发执行同一 run 后,`memory.extraction_requested` 事件为 0(期望 1),疑似并发完成路径的幂等/事件写入问题 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 收尾修复:死模块、错误码口径与校验信封(2026-09-11)
|
|||
|
|
|
|||
|
|
### 8.1 用户决策
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. 删除死模块:确认
|
|||
|
|
2. 错误码冲突:以 SESSION_NOT_FOUND 为准(即 docs/05-接口文档.md §3.6 为权威)
|
|||
|
|
3. 参数校验统一信封:确认
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 变更内容
|
|||
|
|
|
|||
|
|
| 文件 | 变更 |
|
|||
|
|
|---|---|
|
|||
|
|
| `app/api/controllers/config_releases.py` | 删除(第二套配置发布状态机入口,从未被 `main.py` 注册) |
|
|||
|
|
| `app/service/config_application_service.py` | 删除(仅被上述死 Controller 引用,随之失去调用方) |
|
|||
|
|
| `app/main.py` | 新增 `RequestValidationError` 处理器,请求校验失败也返回统一 `{error, meta}` 信封(422 + `AGENT_INPUT_INVALID`,字段级原因进 `field_errors`);提取 `_trace_id()` 供两个处理器共用 |
|
|||
|
|
| `tests/unit/api/test_request_validation_envelope.py` | 新增 2 条契约用例:校验错误信封形态、鉴权仍先于参数校验(401 不被 422 掩盖) |
|
|||
|
|
| `tests/integration/test_promotion_material_api.py` | 错误码对齐权威文档:`RESOURCE_NOT_FOUND`→`SESSION_NOT_FOUND`、`VALIDATION_ERROR`/400→`AGENT_INPUT_INVALID`/422、`FORBIDDEN`→`AGENT_PERMISSION_DENIED` |
|
|||
|
|
| `docs/09-底座使用文档.md` | 错误码表按 `docs/05` §3.5/§3.6 对齐;发布流程说明由"创建人不能审核自己的配置"改为"允许创建人自审" |
|
|||
|
|
|
|||
|
|
### 8.3 复测结果
|
|||
|
|
|
|||
|
|
| 层次 | 最初 | 第 7 节后 | 本节后 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 单元测试 | 466 通过 / 7 失败 | 472 通过 / 1 失败 | **475 通过 / 0 失败** |
|
|||
|
|
| 契约测试 | 4 通过 | 4 通过 | 4 通过 |
|
|||
|
|
| 集成测试 | 39 通过 / 30 失败 | 65 通过 / 4 失败 | **67~68 通过 / 1~2 失败 / 1 跳过**(连跑 3 次:2 / 2 / 1 失败) |
|
|||
|
|
| 合计 | 509 通过 / 37 失败 | 541 通过 / 5 失败 | **约 546 通过 / 1~2 失败** |
|
|||
|
|
|
|||
|
|
### 8.4 剩余问题
|
|||
|
|
|
|||
|
|
| # | 问题 | 性质 | 证据 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 1 | `tests/unit/service/test_suitability_service.py` 整文件收集失败 | 用例与重构后语义不同,需重写 | 旧用例按"调用方传入风险等级、同步 `evaluate(request)`"编写;现实现为异步 `evaluate(request, context)`,风险等级由服务端权威画像解析。仅改导入名无法恢复 |
|
|||
|
|
| 2 | `test_worker_runtime_mysql.py::test_http_accept_worker_commit_query_and_repeat[False]` | 疑似真 bug,稳定复现 | 并发 `execute` 同一 run 后,`memory.extraction_requested` 事件数为 0(期望 1) |
|
|||
|
|
| 3 | `test_complete_run`、`test_memory_extraction`、`test_run_cancellation` 中的个别用例 | 偶发 | 连跑 3 次失败集合变化,单条执行均通过;疑似测试间残留数据或并发时序 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 偶发失败定位:遗留 Worker 进程与测试共用数据库(2026-09-11)
|
|||
|
|
|
|||
|
|
### 9.1 结论
|
|||
|
|
|
|||
|
|
第 8.4 节第 3 项的"偶发"不是测试间污染,而是**本机存在遗留的 worker 进程**,
|
|||
|
|
它与集成测试共用同一个数据库,会主动领走测试造的运行、消费测试写入的 Outbox 事件。
|
|||
|
|
|
|||
|
|
### 9.2 证据
|
|||
|
|
|
|||
|
|
| 证据 | 内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 探针实验 | 造一个 `queued` 运行后静置观察:**1 秒内**被外部进程写入 `worker_id` 并推进为 `failed`(该进程未注册业务 Agent)。进程内没有任何 worker 任务 |
|
|||
|
|
| 失败现场 | `test_cancel_is_idempotent_and_terminates_original_request` 两次取消返回的 `cancel_requested_at` 完全相同、只有 `status` 从 `cancel_requested` 变成 `cancelled` |
|
|||
|
|
| 代码路径 | 只有 `WorkerRuntime.execute()`(`app/worker/runtime.py:356-366`)会把 `cancel_requested` 推进为 `cancelled`,且保留 `cancel_requested_at`——与现场完全吻合 |
|
|||
|
|
| 扫描逻辑 | `app/worker/runtime.py:299` 会认领任意 `queued/running/cancel_requested` 的运行,测试造的运行对它是可见的 |
|
|||
|
|
|
|||
|
|
### 9.3 进程清单(2026-09-11 实测)
|
|||
|
|
|
|||
|
|
| PID | 启动时间 | 判定依据 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 2304 | 15:31:30 | 连着 `111.124.203.45:993`(IMAP SSL)→ `python -m app.worker` |
|
|||
|
|
| 15700 | 未知 | 同样连着 `111.124.203.45:993` → 另一个 worker 实例 |
|
|||
|
|
| 6188 | 15:46:10 | 监听 `:8100` → API 服务(uvicorn) |
|
|||
|
|
| 19876 | 13:03:23 | 监听 `:5173` → 前端 dev server |
|
|||
|
|
|
|||
|
|
### 9.4 处理建议
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1. 跑集成测试前停掉 worker 进程(`python -m app.worker`),否则测试数据会被并发消费;
|
|||
|
|
2. 长期方案:集成测试指向独立测试库(MYSQL_DSN 覆盖),与开发用的 worker 物理隔离。
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
停止 worker 后重跑集成测试可验证该结论(本报告未擅自终止开发者的进程)。
|
|||
|
|
|
|||
|
|
### 9.5 处置结果(2026-09-11)
|
|||
|
|
|
|||
|
|
| 项 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| PID 2304(worker) | 已停止 |
|
|||
|
|
| PID 15700(worker) | **无法停止**:属于 Session 0,`Stop-Process` 与 `taskkill /F` 均返回"拒绝访问",需要由拥有该进程的账户或管理员结束 |
|
|||
|
|
| 进程清单复核 | 停止 2304 后,`netstat` 确认仅剩 15700 持有数据库(2 条)与 IMAP(1 条)连接,扫库行为仍在(探针 1 秒内被领走) |
|
|||
|
|
| 测试残渣清理 | 清理 14 个测试 session(`agent_run` / `conversation_message` / `request_idempotency` 各自归零),其中 5 行为 09-09 历史遗留 |
|
|||
|
|
| 复跑结果(2304 停止后) | 两次集成:`1 failed, 68 passed` 与 `69 passed, 1 skipped`;唯一失败仍是取消用例,与 15700 的扫描时序一致 |
|
|||
|
|
|
|||
|
|
结论:只要 15700 仍在轮询,集成测试就无法做到稳定全绿;彻底解法是结束该进程,
|
|||
|
|
或让集成测试指向独立测试库。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 独立测试库:最终验证(2026-09-11)
|
|||
|
|
|
|||
|
|
### 10.1 方案落地
|
|||
|
|
|
|||
|
|
用户授权后创建了独立测试库,并把引导流程固化成工具:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
CREATE DATABASE IF NOT EXISTS jr_agent_test
|
|||
|
|
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
|||
|
|
GRANT ALL PRIVILEGES ON jr_agent_test.* TO 'jr_app'@'localhost';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 项 | 值 |
|
|||
|
|
|---|---|
|
|||
|
|
| 工具 | `tools/run_tests_on_test_db.py`(`python -m tools.run_tests_on_test_db [pytest 目标…]`) |
|
|||
|
|
| 覆盖方式 | 只在本进程内把 `MYSQL_DSN` 换成测试库;`.env` 不改,开发库与 worker 不受影响 |
|
|||
|
|
| 执行步骤 | 推导测试库 DSN → 空库导入历史基线 → `alembic upgrade head` → 灌 9001/9002/9003 测试账号 → pytest |
|
|||
|
|
| 测试库现状 | 69 张表(含 `alembic_version`),版本 `20260911_drop_review_separation`(head),RBAC 测试账号 3 个 |
|
|||
|
|
|
|||
|
|
### 10.2 过程中踩到的两个坑(均已有可复现证据)
|
|||
|
|
|
|||
|
|
| 坑 | 现象 | 结论 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 迁移链不是从零建库 | `alembic upgrade head` 在建 `svc_conversation_session` 时报 `Failed to open the referenced table 'sys_user'` | 33 张历史基线表在 `alembic/baseline_generated.sql` 里,不在迁移链中,必须先导入 |
|
|||
|
|
| pymysql 多语句执行不生效 | 用 `CLIENT.MULTI_STATEMENTS` 一次发整脚本,连接正常返回但表没建出来 | 改为逐条执行后 41 条语句全部成功 |
|
|||
|
|
|
|||
|
|
(另记:脚本最初写成 `.ps1`,Windows PowerShell 5.1 把无 BOM 的 UTF-8 当 GBK 解析导致中文乱码报错,
|
|||
|
|
已改为 Python 工具,避开该编码陷阱。)
|
|||
|
|
|
|||
|
|
### 10.3 最终结果
|
|||
|
|
|
|||
|
|
| 环境 | 结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| **独立测试库 `jr_agent_test`** | **548 通过 / 1 跳过 / 0 失败**(单元 475 + 契约 4 + 集成 69) |
|
|||
|
|
| 开发库 `jr_agent`(仍受遗留 worker 干扰) | 集成 68~69 通过 / 0~1 失败 |
|
|||
|
|
|
|||
|
|
`ruff check app tests alembic tools` 全绿。第 8.4 节列出的"偶发失败"至此全部收敛为环境干扰,
|
|||
|
|
并已被独立测试库方案消除。
|