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:
+15
-3
@@ -4,7 +4,7 @@
|
|||||||
>
|
>
|
||||||
> 📝 **更新规则**:每次 Claude 完成 / 开始 / 阻塞重要任务,会主动更新本文件。你也可以自己改(纯 markdown,git 跟踪)。
|
> 📝 **更新规则**:每次 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)
|
### 2026-06-21(凌晨 1 小时 sprint)
|
||||||
|
|
||||||
#### 🆕 v0.7.0 release 收尾(8 个 worktree → main)
|
#### 🆕 v0.7.0 release 收尾(8 个 worktree → main)
|
||||||
|
|||||||
@@ -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_user_info.side_effect = _mock_get_user_info_default
|
||||||
mock_wecom_module.get_department_users.return_value = []
|
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 = AsyncMock()
|
||||||
mock_ai_module.generate_response.return_value = "这是AI的模拟回复"
|
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
|
mock_ai = mock_ai_module
|
||||||
|
|
||||||
# Patch WecomService 类(端点函数中会新建实例)
|
# Patch WecomService 类(端点函数中会新建实例)
|
||||||
# 注意:只 patch 模块中实际引用的名字
|
# 2026-06-22 修复: 必须 patch "app.services.wecom_service.WecomService"
|
||||||
# conversations.py 导入了 WecomService,但没有导入 AIService
|
# 而不是 "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):
|
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.h5.WecomService", return_value=mock_wecom):
|
||||||
with patch("app.api.agents.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):
|
with patch("app.api.agents._get_redis", return_value=mock_redis):
|
||||||
|
|||||||
@@ -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("app.api.h5._get_redis", return_value=mock_redis, create=True):
|
||||||
with patch("redis.asyncio.from_url", return_value=mock_redis):
|
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)
|
transport = ASGITransport(app=app)
|
||||||
# base_url 用 127.0.0.1,让 h5._require_wework_ua 跳过 UA 检测
|
# base_url 用 127.0.0.1,让 h5._require_wework_ua 跳过 UA 检测
|
||||||
# 原因:生产环境要求企微 UA,测试环境是 httpx 客户端没企微 UA
|
# 原因:生产环境要求企微 UA,测试环境是 httpx 客户端没企微 UA
|
||||||
@@ -179,7 +193,7 @@ class TestOAuthCallback:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_auth_code"},
|
json={"code": "valid_auth_code"},
|
||||||
@@ -209,7 +223,7 @@ class TestOAuthCallback:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_auth_code"},
|
json={"code": "valid_auth_code"},
|
||||||
@@ -236,7 +250,7 @@ class TestOAuthCallback:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_auth_code"},
|
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.get_oauth_user_info = AsyncMock(return_value={"userid": "", "user_ticket": ""})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "bad_code"},
|
json={"code": "bad_code"},
|
||||||
@@ -273,7 +287,7 @@ class TestOAuthCallback:
|
|||||||
mock_wecom.get_oauth_user_info = AsyncMock(side_effect=Exception("企微API不可用"))
|
mock_wecom.get_oauth_user_info = AsyncMock(side_effect=Exception("企微API不可用"))
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "will_fail"},
|
json={"code": "will_fail"},
|
||||||
@@ -290,7 +304,7 @@ class TestOAuthCallback:
|
|||||||
mock_wecom.get_user_info = AsyncMock(side_effect=Exception("通讯录API失败"))
|
mock_wecom.get_user_info = AsyncMock(side_effect=Exception("通讯录API失败"))
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_code"},
|
json={"code": "valid_code"},
|
||||||
@@ -521,7 +535,7 @@ class TestGetCurrentEmployeeInfo:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.get(
|
||||||
"/h5/me",
|
"/h5/me",
|
||||||
headers={"Authorization": "Bearer nocache_me_token"},
|
headers={"Authorization": "Bearer nocache_me_token"},
|
||||||
@@ -652,7 +666,7 @@ class TestErrorHandling:
|
|||||||
|
|
||||||
mock_redis.setex = broken_setex
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_code"},
|
json={"code": "valid_code"},
|
||||||
@@ -677,7 +691,7 @@ class TestErrorHandling:
|
|||||||
)
|
)
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "timeout_code"},
|
json={"code": "timeout_code"},
|
||||||
@@ -698,7 +712,7 @@ class TestErrorHandling:
|
|||||||
)
|
)
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.get(
|
||||||
"/h5/me",
|
"/h5/me",
|
||||||
headers={"Authorization": "Bearer wecom_fail_token"},
|
headers={"Authorization": "Bearer wecom_fail_token"},
|
||||||
@@ -729,7 +743,7 @@ class TestTokenTTLAndFormat:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "ttl_test_code"},
|
json={"code": "ttl_test_code"},
|
||||||
@@ -755,7 +769,7 @@ class TestTokenTTLAndFormat:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "info_ttl_code"},
|
json={"code": "info_ttl_code"},
|
||||||
@@ -778,7 +792,7 @@ class TestTokenTTLAndFormat:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "fmt_test_code"},
|
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.get_user_info = AsyncMock(return_value={"name": "", "department": [], "position": "", "avatar": ""})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "valid_code_here"},
|
json={"code": "valid_code_here"},
|
||||||
@@ -866,7 +880,7 @@ class TestOAuth2EndToEnd:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
callback_response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "e2e_auth_code"},
|
json={"code": "e2e_auth_code"},
|
||||||
@@ -905,7 +919,7 @@ class TestOAuth2EndToEnd:
|
|||||||
})
|
})
|
||||||
mock_wecom.close = AsyncMock()
|
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(
|
callback_response = await h5_client.post(
|
||||||
"/h5/oauth/callback",
|
"/h5/oauth/callback",
|
||||||
json={"code": "cached_flow_code"},
|
json={"code": "cached_flow_code"},
|
||||||
@@ -914,7 +928,7 @@ class TestOAuth2EndToEnd:
|
|||||||
token = callback_response.json()["data"]["token"]
|
token = callback_response.json()["data"]["token"]
|
||||||
|
|
||||||
# Step 2: 第一次访问 /me(应从缓存读取,不再调用 WecomService)
|
# 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(
|
me_response = await h5_client.get(
|
||||||
"/h5/me",
|
"/h5/me",
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
|
|||||||
+30
@@ -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
|
||||||
Reference in New Issue
Block a user