feat: permissions CRUD browser integration + E2E enhancements

Backend:
- permission_handler: 完善权限 CRUD 接口(列表/创建/更新/删除)
- auth_handler: 修复认证处理逻辑
- router: 新增权限管理路由
- handler_test: 新增权限 handler 测试覆盖

Frontend:
- permissions.ts/test.ts: 权限服务层完整实现
- profile/settings/service_tests: 服务适配器修正
- client.ts: HTTP 客户端健壮性增强
- vite.config.js: 构建配置优化
- E2E 脚本: run-playwright-cdp-e2e 大幅增强(权限流程覆盖)

Docs:
- REAL_PROJECT_STATUS: 状态更新
- PRODUCTION_CHECKLIST/QUALITY_STANDARD/TECHNICAL_GUIDE/PROJECT_EXPERIENCE_SUMMARY: 团队规范完善
- plans/2026-04-23: 权限浏览器 CRUD 设计方案

验证: go build 0错误
This commit is contained in:
2026-04-24 07:30:18 +08:00
parent 3f3bb82f1d
commit 9b1cea246e
25 changed files with 1868 additions and 133 deletions

View File

@@ -269,6 +269,31 @@ describe('http client', () => {
})
})
it('uses the current non-expired access token even when another refresh is still in flight', async () => {
const fetchMock = vi.mocked(fetch)
fetchMock.mockResolvedValueOnce(
jsonResponse({
code: 0,
message: 'ok',
data: { ok: true },
}),
)
const { get, setAccessToken, setRefreshPromise, startRefreshing } = await loadModules()
setAccessToken('still-valid-access-token', 3600)
startRefreshing()
setRefreshPromise(new Promise(() => {}))
const requestPromise = get('/protected')
await Promise.resolve()
expect(fetchMock).toHaveBeenCalledTimes(1)
expect(fetchMock.mock.calls[0][1]?.headers).toMatchObject({
Authorization: 'Bearer still-valid-access-token',
})
await expect(requestPromise).resolves.toEqual({ ok: true })
})
it('clears the local session when refresh fails before the business request is sent', async () => {
const fetchMock = vi.mocked(fetch)
fetchMock.mockResolvedValueOnce(new Response(null, { status: 401 }))

View File

@@ -188,6 +188,10 @@ async function resolveAuthorizationHeader(auth: boolean): Promise<string | null>
}
let token = getAccessToken()
if (token && !isAccessTokenExpired()) {
return token
}
if (isRefreshing()) {
const promise = getRefreshPromise()
if (promise) {