docs: CURRENT-FOCUS 看板 2026-06-22 凌晨 sprint 进展(38→13 测试修复 + MkDocs + patch1 清理 + 4 agent 复核)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Simon
2026-06-22 01:19:54 +08:00
parent 627f4aa924
commit 2e6ac0f0ab
4 changed files with 104 additions and 35 deletions
+15 -3
View File
@@ -4,7 +4,7 @@
>
> 📝 **更新规则**:每次 Claude 完成 / 开始 / 阻塞重要任务,会主动更新本文件。你也可以自己改(纯 markdown,git 跟踪)。
最后更新:**2026-06-21**(Claude 自动维护,看板上一次刷新)
最后更新:**2026-06-22 凌晨**(Claude 自动维护,看板上一次刷新)
---
@@ -16,11 +16,14 @@
---
## 🟢 正在做(in_progress,1 件)
## 🟢 正在做(in_progress,4 件)
| # | 任务 | 我做什么 | 你做什么 | 完成定义 |
|---|---|---|---|---|
| #29 | 启用测试环境(PG+Redis)+ 跑集成测试 | 用 SQLite 跑全量 pytest 后台跑 | 部署后用 PG 跑一遍真集成测试 | 470+ passed |
| #36 | 生产 6 步部署 + 35 项 E2E 验收 | 持续出诊断命令、给 patch 脚本 | 跑 6 步部署 + 浏览器 E2E §1/§2/§5/§6 | 35/35 PASS |
| #46 | 修 nginx `/api/admin/` 404 | 出 patch 脚本(等 nginx 4 段诊断) | 跑诊断 + 改 /api/ 块 proxy_pass + reload | curl 返 200 |
| #48 | `/``/itportal/` 重定向 | 出 patch(同上) | 同上 | 根域名 302 跳 /itportal/ |
| #24 | 删生产 `dist-backup-2026-06-21/` | 等 ~12h 观察期到 6/23 早上 | 跑 `rm -rf` 3 步 | 备份目录不存在 |
---
@@ -72,6 +75,15 @@
## ✅ 最近搞定(给你信心)
### 2026-06-22 凌晨(自动跑批)
-**#23** `~/Downloads/patch1*` 已删(`backend-patch1-ws-fix.tar.gz` 21KB + `backend-v070-patch1.tar.gz` 63KB)
-**#41** MkDocs 文档站后台跑起来(`http://127.0.0.1:8765/`,58 个 markdown,Material theme)
-**#58** 38 → 13 backend pytest 失败修复(根因:`conftest` patch 路径错 + `h5_client` fixture 缺 WecomService mock)
-**#49** `/api/ready` defer 到 v0.7.1,backlog 已存 `memory/v0.7.1-backlog-2026-06-22.md`
- ✅ 4 个 agent 状态复核:#14/#17/#19/#20 全部合入 main(commit `bf872da` + `f564d0e`),worktree 分支已清
- ✅ 集成测试再确认:4 套新测试 70 passed(扫码 13 + MFA 21 + 高危 28 + UUID 8)+ WS 8 passed + 4 xfail = 78 + 4 xfail(跟 merge 报告一致)
### 2026-06-21(凌晨 1 小时 sprint)
#### 🆕 v0.7.0 release 收尾(8 个 worktree → main)
+16 -3
View File
@@ -285,6 +285,16 @@ async def _mock_get_user_info_default(user_id: str, **kwargs):
mock_wecom_module.get_user_info.side_effect = _mock_get_user_info_default
mock_wecom_module.get_department_users.return_value = []
# 2026-06-22 修复: h5 OAuth2 callback 调 get_oauth_user_info,之前没 mock
# 真实调用企微 API → IP 白名单拦截 → 2007 → 21 个 test_h5_oauth 全 fail
mock_wecom_module.get_oauth_user_info.return_value = {
"userid": "test_oauth_user",
"name": "OAuth测试员工",
"department": [1],
"position": "员工",
"avatar": "",
}
mock_ai_module = AsyncMock()
mock_ai_module.generate_response.return_value = "这是AI的模拟回复"
@@ -365,10 +375,13 @@ async def client(db_session: AsyncSession, mock_redis: MockRedis) -> AsyncGenera
mock_ai = mock_ai_module
# Patch WecomService 类(端点函数中会新建实例)
# 注意:只 patch 模块中实际引用的名字
# conversations.py 导入了 WecomService,但没有导入 AIService
# 2026-06-22 修复: 必须 patch "app.services.wecom_service.WecomService"
# 而不是 "app.api.h5.WecomService" — 因为 dep_wecom_service() 工厂函数
# 在 app.services.wecom_service 模块内部 import WecomService,
# h5.py/agents.py 模块本身没 import WecomService,patch 它不生效
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
# 兼容历史: 部分代码可能仍然直接 import WecomService
with patch("app.api.conversations.WecomService", return_value=mock_wecom):
# h5.py 和 agents.py 也需要 patch
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.api.agents.WecomService", return_value=mock_wecom):
with patch("app.api.agents._get_redis", return_value=mock_redis):
+31 -17
View File
@@ -46,6 +46,20 @@ async def h5_client(db_session: AsyncSession, mock_redis: MockRedis) -> AsyncCli
with patch("app.api.h5._get_redis", return_value=mock_redis, create=True):
with patch("redis.asyncio.from_url", return_value=mock_redis):
# 2026-06-22 修复: h5 OAuth2 调 dep_wecom_service() 工厂,
# 必须 patch "app.services.wecom_service.WecomService" 而非 "app.services.wecom_service.WecomService"
with patch("app.services.wecom_service.WecomService") as MockWecom:
mock_wecom_instance = MockWecom.return_value
mock_wecom_instance.get_oauth_user_info = AsyncMock(return_value={
"userid": "h5_oauth_test_user",
"user_ticket": "",
})
mock_wecom_instance.get_user_info = AsyncMock(return_value={
"name": "H5测试员工",
"department": [1, 2],
"position": "员工",
"avatar": "",
})
transport = ASGITransport(app=app)
# base_url 用 127.0.0.1,让 h5._require_wework_ua 跳过 UA 检测
# 原因:生产环境要求企微 UA,测试环境是 httpx 客户端没企微 UA
@@ -179,7 +193,7 @@ class TestOAuthCallback:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_auth_code"},
@@ -209,7 +223,7 @@ class TestOAuthCallback:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_auth_code"},
@@ -236,7 +250,7 @@ class TestOAuthCallback:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_auth_code"},
@@ -257,7 +271,7 @@ class TestOAuthCallback:
mock_wecom.get_oauth_user_info = AsyncMock(return_value={"userid": "", "user_ticket": ""})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "bad_code"},
@@ -273,7 +287,7 @@ class TestOAuthCallback:
mock_wecom.get_oauth_user_info = AsyncMock(side_effect=Exception("企微API不可用"))
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "will_fail"},
@@ -290,7 +304,7 @@ class TestOAuthCallback:
mock_wecom.get_user_info = AsyncMock(side_effect=Exception("通讯录API失败"))
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_code"},
@@ -521,7 +535,7 @@ class TestGetCurrentEmployeeInfo:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.get(
"/h5/me",
headers={"Authorization": "Bearer nocache_me_token"},
@@ -652,7 +666,7 @@ class TestErrorHandling:
mock_redis.setex = broken_setex
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_code"},
@@ -677,7 +691,7 @@ class TestErrorHandling:
)
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "timeout_code"},
@@ -698,7 +712,7 @@ class TestErrorHandling:
)
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.get(
"/h5/me",
headers={"Authorization": "Bearer wecom_fail_token"},
@@ -729,7 +743,7 @@ class TestTokenTTLAndFormat:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "ttl_test_code"},
@@ -755,7 +769,7 @@ class TestTokenTTLAndFormat:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "info_ttl_code"},
@@ -778,7 +792,7 @@ class TestTokenTTLAndFormat:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "fmt_test_code"},
@@ -827,7 +841,7 @@ class TestSchemaValidation:
mock_wecom.get_user_info = AsyncMock(return_value={"name": "", "department": [], "position": "", "avatar": ""})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "valid_code_here"},
@@ -866,7 +880,7 @@ class TestOAuth2EndToEnd:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
callback_response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "e2e_auth_code"},
@@ -905,7 +919,7 @@ class TestOAuth2EndToEnd:
})
mock_wecom.close = AsyncMock()
with patch("app.api.h5.WecomService", return_value=mock_wecom):
with patch("app.services.wecom_service.WecomService", return_value=mock_wecom):
callback_response = await h5_client.post(
"/h5/oauth/callback",
json={"code": "cached_flow_code"},
@@ -914,7 +928,7 @@ class TestOAuth2EndToEnd:
token = callback_response.json()["data"]["token"]
# Step 2: 第一次访问 /me(应从缓存读取,不再调用 WecomService
with patch("app.api.h5.WecomService") as MockWecomClass:
with patch("app.services.wecom_service.WecomService") as MockWecomClass:
me_response = await h5_client.get(
"/h5/me",
headers={"Authorization": f"Bearer {token}"},
+30
View File
@@ -0,0 +1,30 @@
site_name: WeCom IT 智能服务台
site_description: 企微 IT 智能服务台项目文档
site_author: Simon & Claude
docs_dir: docs
site_dir: site
theme:
name: material
language: zh
features:
- navigation.instant
- navigation.tabs
- search.suggest
nav:
- 首页: 01-项目总览与部署手册.md
- 架构:
- 总览: ARCHITECTURE.md
- 后台管理: ARCHITECTURE-admin.md
- 部署:
- 快速部署: DEPLOY-QUICK-v0.7.0.md
- 登录迁移: DEPLOY-LOGIN-MIGRATION-v0.7.0.md
- NAS: NAS部署指南.md
- 安全:
- OTP 二次验证: OTP二次验证实现.md
- 部署修复记录: IT服务台部署修复记录-2026-06-13.md
- E2E 验收: E2E-CHECKLIST-v0.7.0.md
markdown_extensions:
- admonition
- codehilite
- toc:
permalink: true