Skip to content

test(oauth): 修复测试夹具端口复用和 TOTP 跨时间窗导致的 CI 偶发失败 - #17

Merged
Conner512 merged 1 commit into
mainfrom
fix/read-audit-flaky
Oct 15, 2026
Merged

Conner512 merged 1 commit into
mainfrom
fix/read-audit-flaky

Conversation

@Conner512

Copy link
Copy Markdown
Owner

背景

PR #16 的 CI 里 tests (oauth) 偶发失败(重跑通过):

Web read audit distinguishes denied calls ...
Authorization fixture stopped: 404 {"error":"invalid_request","error_code":"NOT_FOUND"}

翻了 verify.yml 最近 100 次运行的失败日志,同一类夹具问题还出现过几次。

根因 1:freePort() 可能把同一个端口分给两个角色

freePort() 先 listen(0) 拿到端口、立刻关闭再返回。Linux 的临时端口是随机分配的,刚释放的端口可能马上又被下一次调用拿到,于是 authPort / gatewayPort / callbackPort 可能重复:

撞号 现象 CI 记录
callbackPort = authPort OAuth 最后跳到 /callback 时与授权服务器同源,夹具继续跟进,授权服务器返回 404 NOT_FOUND 2026-10-14 read-audit(本次)
authPort = gatewayPort 配置校验报 public origin mode must match issuer/resource 2026-09-30 console-actions
已分配但尚未监听的端口被再次分出 listen EADDRINUSE 2026-09-21 shared-origin

本地已确定性复现:强制把回调端口设成授权端口,稳定得到同样的报错(失败点为 GET /callback)。macOS 按顺序分配临时端口,所以本地并发压测都没撞上。

修复:freePort() 在进程内记录已经分出去的端口,不再重复返回。

根因 2:注册 MFA 用的上一时间窗验证码可能在校验前过期

夹具注册时用 epoch: seconds() - 30 生成上一个 30 秒时间窗的验证码(为了不和随后登录用的当前时间窗验证码重复,以免被当作重放)。如果生成和校验之间正好跨过 30 秒边界,这个验证码就超出了容差,报 Invalid OAuth configuration: MFA verification(2026-09-30 task-context-read)。

修复:如果离下一个时间窗不到 2 秒,先等边界过去再注册。平均每个夹具约多花 0.07 秒。

其他

  • 授权夹具出错时会带上停在哪一步(例如 stopped at GET /callback),以后排查更快
  • 新增 services/oauth/test/fixture-ports.test.mjs:模拟操作系统返回已经分出去的端口,断言 freePort() 不会重复分配。修复前失败、修复后通过
  • 只改测试夹具,不涉及生产代码

验证(本地,Node 26.11)

  • npm run test:oauth:207/209,失败的 CFG-01..06、SHARED-01 是本机 Node 26 非 LTS 触发的生产配置校验,修改前同样失败
  • npm test:399/399
  • npm run test:publication:20/20

未覆盖

历史里另有两次 REGISTRATION_UNAVAILABLE(provisioning / identity-boundaries,2026-09-27、09-28),看起来是另一类并发问题,本 PR 不涉及。

🤖 Generated with Claude Code

freePort() released each probe port before returning it, so on Linux two
fixture roles could receive the same port. When the callback port equalled the
auth port, the authorization fixture followed the callback as a same-origin
redirect and failed with 404 NOT_FOUND (read-audit, 2026-10-14); an auth/gateway
collision produced "public origin mode must match issuer/resource" (2026-09-30).
freePort() now never returns a port it already handed out in this process.

Enrollment verified a previous-step TOTP code; crossing a 30 s boundary between
generation and verification failed with "MFA verification" (2026-09-30). The
fixture now waits out the last two seconds of a step before enrolling.

The authorization fixture also reports which step stopped, for future triage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant