认证系统如何做到“改动即安心“?一套完整的 Convex + Better Auth 测试工程化实践
认证系统如何做到改动即安心一套完整的 Convex Better Auth 测试工程化实践【免费下载链接】better-authConvex Better Auth 项目地址: https://gitcode.com/gh_mirrors/con/better-auth凌晨两点同事在群里甩来一条消息线上登录突然大面积报 500回滚了三次才定位到问题——不是密码逻辑而是某次重构顺手改了一个 JWT Cookie 的刷新时机导致会话续期彻底失效。更扎心的是这个改动跑了三遍手工验证都看着没问题因为浏览器缓存和登录态让手点页面根本测不出差异。这不是虚构事故而是认证系统最常见的翻车方式。Convex Better Auth这个开源项目做的正是把认证接进 Convex 后端这件事——注册、登录、会话、Cookie、跨域跳转全链路都在它的职责范围内。而它对待测试工程化的认真程度几乎可以当作一本教科书来读。这篇文章就拆给你看一个认证组件是怎么用三套测试工具织出一张让改动即安心的安全网。登录逻辑一天改八次凭什么敢每次直接上生产先抛一个问题认证链路里哪一类 Bug 最贵不是算法写错而是边界条件——会话过期后该返回什么JWT 刷新在哪个接口生效无会话时调用/get-session会不会误发 Cookie这些问题靠人肉点页面几乎测不出来因为浏览器会自动带 Cookie、自动续期把错误悄悄消化掉。等你发现已经在生产环境了。所以工程化的第一课是把正确变成可重复验证的结论而不是某个时刻的巧合。Convex Better Auth 的做法是把测试拆成三层防线各管一段各司其职。三层防线怎么分工又怎么协作先说结论这套体系的分工非常明确防线工具管什么跑多快第一层 单元测试Vitest插件钩子、适配器、路由匹配等纯逻辑毫秒级第二层 集成测试convex-test组件与内存 Convex 数据库的真实读写秒级第三层 E2EPlaywright浏览器里的完整用户旅程分钟级协作机制是层层拦截、逐级放行一个 Bug 从下游往上游报改动也从上游往下游验。分支逻辑的改动单测没过就直接挡在门口绝不让它花两分钟去跑浏览器而涉及表单、Cookie、页面状态的改动单测再绿也得过 E2E 这道关。用昂贵的浏览器测试去验证廉价的分支逻辑是测试工程化最常见的浪费——这套体系恰恰避开了这一点。第一层毫秒级单测怎么做到边写边测第一个疑问单测快但快得有没有意义有前提是它测在刀刃上。以 src/plugins/convex/index.test.ts 为例它盯的是一个非常具体的问题Convex 插件的 JWT Cookie 刷新钩子到底该在哪些路径上触发it(matches get-session only when a session exists, () { const matcher getJwtSetCookieMatcher(); expect(matcher({ path: /get-session, context: { session: { id: s1 } } })).toBe(true); expect(matcher({ path: /get-session, context: { session: null } })).toBe(false); });同一路径有会话刷新、无会话不刷新——这种同一输入不同状态下输出不同的边界正是事故高发地。单测把它钉死任何一次重构动了这段逻辑npm test立刻报警。还有一个容易被忽略的细节项目在 vitest.config.ts 里把测试环境配成了edge-runtime和认证代码真正运行的边缘运行时保持一致。测试环境越接近生产环境测试结果越可信。而npm test同时跑vitest run --typecheck等于单测加 TypeScript 类型校验一次拿双份保障具体脚本见 package.json。第二层convex-test 怎么在内存里跑真实认证读写单测解决了逻辑对不对但认证迟早要和数据库打交道。问题来了不连真实数据库怎么验证组件和存储层的交互答案是convex-test——一个能把组件注册到内存 Convex 实例上的测试工具。src/test.ts 里短短几行就完成了把 better-auth 组件装进测试实例这件事export function register(t: TestConvex..., name betterAuth) { t.registerComponent(name, schema, modules); }调用时只需register(t, betterAuth)然后就能在一个无外部依赖、秒级启动的测试环境里模拟真实的认证读写路径。配合 src/test/adapter-factory/ 下的多套场景——基础认证流、字段重命名、插件表、额外自定义字段——字段重命名这种最容易埋雷的破坏性改动在这里被提前拆了个稀碎。第三层Playwright 如何复刻完整用户旅程前两层再绿也回答不了一个问题用户真的能完成注册→登出→再登录这件事吗这就轮到 Playwright 上场。用例放在 e2e/tests/ 下三段旅程各司其职注册、注册后登出再登录、会话在页面刷新后持久化。以 e2e/tests/sign-in.spec.ts 为例它把一次完整旅程压缩成了几行test(sign in with email and password after sign up, async ({ page }) { const email test${Date.now()}example.com; await page.goto(/); await browserSignUp(page, email, password); await browserSignOut(page); await browserSignIn(page, email, password); await expect(page.getByTestId(auth-authenticated)).toBeVisible(); });注意那个test${Date.now()}example.com——每次运行都用时间戳生成唯一邮箱从根上杜绝了测试数据互相串扰。而页面操作细节全部封装在 e2e/helpers/auth.ts 里用例本身干净得像个用户故事可读性极高。高频翻车现场与规避清单既然自称踩过坑那就把最常见的几个坑连同规避方式一次说清坑浏览器缓存掩盖了 Cookie 问题。手工验证时登录态常驻刷新逻辑的问题根本暴露不了。规避单测直接断言 matcher 的输入输出见 src/plugins/convex/index.test.ts。坑测试连了共享开发库数据互相污染。规避E2E 用Date.now()唯一邮箱 全新本地后端数据隔离是底线。坑E2E 环境不是环境即代码换台机器就复现不了。规避密钥、环境变量、后端部署全部脚本化见下文。坑为跑通 E2E 去测分支逻辑耗时翻倍还没测到点。规避按三层防线分工逻辑细节交给单测交互链路才交给浏览器。坑类型错误混进测试。规避npm test内置--typecheck跑用例的同时完成类型校验。E2E 最怕环境不稳定这个项目怎么破如果你留意到 E2E 测试里那个本地后端应该会好奇它从哪来的这就是工程化最见功力的一环。e2e/backendHarness.js 会在测试前自动下载预编译的 Convex 本地后端二进制、生成随机管理密钥、拉起一个全新的隔离实例e2e/run-e2e-tests.sh 负责注入测试专用环境变量——包括一把写死的隔离BETTER_AUTH_SECRET——并部署函数e2e/playwright.config.ts 则在测试前自动启动示例前端端口固定、用完即弃。整条流水线跑完即销毁绝不会污染你的开发环境数据。换句话说任何人 clone 下来跑一遍npm run test:e2e得到的都是同一个结果——这就是环境即代码的含义后端、密钥、部署、前端全部由脚本接管。一句话速查表改分支逻辑→ 跑单测毫秒级见分晓别碰浏览器。改字段或表结构→ 先过adapter-factory的集成场景再决定要不要跑 E2E。改登录/会话/Cookie 流程→ 直接上 Playwright三份 spec 全绿才算数。新环境跑测试→ 先npm test拿单测基线再npm run test:e2e走完整链路。任何测试红了先别改产品代码→ 先确认是不是测试数据污染这是翻车清单里的头号元凶。现在轮到你了这四步行动清单如果你的项目也踩过认证的坑不妨照这套思路做一次止血给最关键的一条认证路径补一个单测专盯边界条件——比如无会话时调用某接口不该刷新 Cookie。把 E2E 用到的邮箱改成时间戳唯一值五分钟就能堵住测试串扰的最大漏洞。把测试环境的密钥和环境变量脚本化让新同事 clone 完就能跑通而不是追着你要配置。把三层防线写进团队的 PR 检查清单明确什么改动该过哪一层别让所有验证都堆到浏览器上。认证系统的可靠性永远值得用自动化去守护 。文章里这套方案来自 Convex Better Auth 的完整仓库clone 地址https://gitcode.com/gh_mirrors/con/better-auth每一行测试代码都真实存在欢迎对照源码检验我的说法。如果你也遇到过登录在测试环境全绿、上线就翻车的经历——或者你有更好的防坑手段欢迎在评论区把故事讲出来踩过的坑就是后来者最值钱的路标。【免费下载链接】better-authConvex Better Auth 项目地址: https://gitcode.com/gh_mirrors/con/better-auth创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考