实战指南)
opencode http-recorder基于 Effect 的 HTTP 与 WebSocket 流量录制回放VCR 风格测试实战指南【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencodeopencode-ai/http-recorder是 opencode 仓库中一个独立的测试基础设施包把真实的 EffectHttpClient与Socket流量录制一次之后从确定性的 JSON 磁带cassette中回放适用于 Provider 集成、重试、轮询、多步流程等手写 mock 会丢失真实请求形态的测试场景。读完本文你将掌握它的安装与配置方式、HttpRecorder.http/HttpRecorder.socket两个公开 API 的完整用法、脱敏redaction体系以及其顺序匹配与磁带存储的源码级实现原理。一、定位与适用前提README 将其定位为Record real Effect HTTP and WebSocket traffic once, then replay it from deterministic JSON cassettes——录制真实流量一次然后从确定性 JSON 磁带回放。它同时标注了 Public betaAPI 依赖 Effect 4 beta可能随 Effect 不稳定的 transport 模块变化。运行环境约束来自 package.json 与 README支持 Node.js 22engines.node: 22和 Bun不适用于浏览器、Worker 或 DenopeerDependencies要求effect为 beta 版本当前仓库锁定4.0.0-beta.83README 安装示例中写的是4.0.0-beta.74两者都说明该包只接受精确的 Effect beta 版本升级 Effect 时需同步验证README 同时提醒Effect beta 存在一个已知声明错误缺少SchemaErrorTypeId在修复前 TypeScript 消费方需要开启skipLibCheck: true。{ compilerOptions: { skipLibCheck: true } }二、安装bun add effect4.0.0-beta.74 bun add -d opencode-ai/http-recorderbeta effect/vitest vitest包导出两个入口见 package.json 的exports.指向 src/index.ts./internal指向 src/internal.ts 供包自身测试使用。三、Quick Start一个测试完成录制—回放双模式完整的入门示例来自 README被测代码是一个普通的 EffectHttpClient调用应用代码完全不需要知道响应是实时的还是回放的import { assert, describe, it } from effect/vitest import { Effect, Schema } from effect import { HttpClient, HttpClientRequest } from effect/unstable/http import { HttpRecorder } from opencode-ai/http-recorder const User Schema.Struct({ id: Schema.Number, name: Schema.String, }) const getUser Effect.gen(function* () { const http yield* HttpClient.HttpClient const response yield* http.execute(HttpClientRequest.get(https://jsonplaceholder.typicode.com/users/1)) return yield* Schema.decodeUnknownEffect(User)(yield* response.json) }) describe(getUser, () { it.effect(loads a user, () Effect.gen(function* () { const user yield* getUser assert.strictEqual(user.id, 1) assert.strictEqual(user.name, Leanne Graham) }).pipe(Effect.provide(HttpRecorder.http(users/get-one))), ) })用 Vitest 运行首次本地运行会调用真实 API 并写入磁带bunx vitest run users.test.tstest/fixtures/recordings/users/get-one.json之后的运行回放该磁带不再接触上游服务器当CItrue时缺失的磁带直接失败而不是录制。README 给出的流程为模式判定在源码中如何发生auto模式的判定逻辑在 recorder.ts 的resolveAutoMode中export const resolveAutoMode ( cassette: CassetteService.Interface, name: string, ): Effect.Effectrecord | replay | passthrough Effect.gen(function* () { if (isCI()) return replay return (yield* cassette.exists(name)) ? replay : record })isCI()的判断口径比CI1更宽只要CI环境变量存在且不为空串、false、0就视为 CI 环境并强制回放。回放层的入口在 internal-effect.ts每个入站请求先经redactor.request生成脱敏快照再通过replay.claim按序领取对应 interaction若磁带不存在错误信息会被转换为一条TransportErrorFixture ... not found. Run locally to record it (CItrue forces replay).四、公开 API 面只有两个函数HttpRecorder.http(name, options?) HttpRecorder.socket(name, options?)这就是完整公开 API见 src/index.tshttp提供一个基于 fetch 的、可录制/回放的HttpClientLayersocket装饰一个由下层提供的标准 EffectSocket.Socket。http的实现effect.ts只做了 Layer 组合recordingLayer之上依次提供文件磁带服务、FetchHttpClient.layer和NodeFileSystem.layer。也就是说录制层本身不关心网络怎么发它装饰任意上游HttpClient这使它可以在测试中替换真实的 fetch 客户端而不改变应用代码。五、WebSocket 录制与回放WebSocket 磁带保存一份客户端与服务端文本/二进制帧的有序转录transcript。回放严格按时间线推进服务端帧持续释放直到遇到下一条已录制的客户端帧为止然后回放会等待应用发出对应的客户端帧才继续。官方示例echo 测试同样来自 READMEimport { assert, it } from effect/vitest import { NodeSocket } from effect/platform-node import { Effect, Layer } from effect import { Socket } from effect/unstable/socket import { HttpRecorder } from opencode-ai/http-recorder const echo Effect.gen(function* () { const socket yield* Socket.Socket const write yield* socket.writer yield* socket.runString( (message) Effect.gen(function* () { assert.strictEqual(message, hello) yield* write(new Socket.CloseEvent(1000)) }), { onOpen: write(hello) }, ) }) const recordedSocket HttpRecorder.socket(echo/hello).pipe( Layer.provide( NodeSocket.layerWebSocket(wss://ws.postman-echo.com/raw, { closeCodeIsError: (code) code ! 1000, }), ), ) it.effect(exchanges WebSocket frames, () echo.pipe(Effect.provide(recordedSocket)))关键设计点应用通过常规 Effect Layer 装配持有 WebSocket 的 URL 与协议录制器只包装其上的 socket不在录制器配置中重复 URL不同端点或并发连接需要分别提供 socket layer。文本帧与 HTTP body 共用同一套 JSON 字段与 body 脱敏二进制帧以 base64 无损存储回放时客户端与服务端的帧类型必须匹配。从源码结构看帧的数据模型定义在 types.tsWebSocketEvent只有text与binarybase64两种 kinddirection区分 client/serverWebSocketInteraction还保留了握手时的url与脱敏后的headers用于匹配。内部WebSocketRecorderOptions还支持compareClientMessagesAsJson把文本客户端帧按规范化 JSON 而非精确字符串比较与protocols选项说明对键序不稳定的 JSON 帧也做了匹配上的容错。六、刷新磁带删除后重录刻意不提供覆盖模式替换某条磁带的方式是删掉对应文件再重跑测试rm test/fixtures/recordings/users/get-one.json bun run test users.test.tsREADME 明确说明刻意没有提供公开的重写overwrite模式——删除操作让正在刷新哪些录制变得可见、可审查。七、脱敏体系默认规则与源码级细节安全默认值会移除大部分 header并对 header、URL、JSON body 中的常见凭据做脱敏。在 Layer 构造时追加规则HttpRecorder.http(anthropic/messages, { redact: { headers: [x-project-token], allowRequestHeaders: [anthropic-version], queryParameters: [session-id], jsonFields: [user_id], url: (url) url.replace(/\/accounts\/[^/]/, /accounts/{account}), body: (body) body.replaceAll(/usr_[a-z0-9]/g, usr_redacted), }, })选项作用headers追加敏感 header 名保留为[REDACTED]allowRequestHeaders为匹配目的额外保留非敏感的请求 headerallowResponseHeaders为回放额外保留非敏感的响应 headerqueryParameters追加敏感的 URL query 参数名jsonFields递归脱敏请求与响应中匹配的 JSON 键url内置脱敏之后稳定化 URLbody内置 JSON 脱敏之后稳定化请求/响应 body这些选项的完整类型定义见 types.ts 的RedactOptions。组合逻辑在 redactor.ts 的make(options)中可以读出几个 README 未展开的实现事实请求 header 默认保留白名单是content-type、accept、openai-betaDEFAULT_REQUEST_HEADERS响应 header 默认只保留content-typeDEFAULT_RESPONSE_HEADERS用户通过headers声明的敏感名会同时加入请求与响应的脱敏名单内置 JSON 敏感字段redactor.ts为access_token、api_key、apikey、client_secret、password、refresh_token、secret、token匹配前字段名会先做规范化去掉非字母数字并转小写因此apiKey、API_KEY这类变体同样命中url与body两个自定义函数是后置钩子分别在内置 URL 脱敏、内置 JSON 字段脱敏之后执行适合做账号 ID、用户 ID 的占位符替换。测试 record-replay.test.ts 验证了内置 URL 脱敏的具体行为key、api_key、X-Amz-Signature等 query 值会被替换为 URL 编码的%5BREDACTED%5Dhttps://user:passwordhost形式的凭据同样被脱敏自定义 transform 在内置脱敏之后生效。写入前的最后一道防线录制器在落盘前会扫描完整磁带中的常见凭据格式以及看起来像凭据的环境变量的值不安全的磁带会失败而不会覆盖已有录制README 称之为 Unsafe cassette 行为源码中对应 internal-effect.ts 对UnsafeCassetteError的捕获。README 同时强调脱敏是纵深防御而非替代审查——提交前请检查磁带 diff。八、匹配与顺序严格顺序、键序无关、差异可读磁带包含一个有序 interaction 序列运行时第 N 个请求与录制的第 N 个请求比对。README 解释这种严格顺序正是为了正确建模重复的相同请求但响应不同的场景——重试、轮询、缓存测试并发请求按请求开始顺序录制即使响应乱序完成。JSON 对象键在匹配前会被规范化键序无关。匹配逻辑在 matching.ts 中canonicalizeJson递归对对象键排序defaultMatcher比较的是 method、脱敏后 URL、规范化 header 与规范化 body 的完整 JSON 快照matching.tsselectSequential执行按位认领请求超出录制条数时报 interaction N of M not recorded比对失败时输出requestDiff生成的逐字段 diffmethod / url / headers / bodydiff 值超过 300 字符会被截断且命中凭据特征的文本一律以[REDACTED]呈现避免错误信息泄漏秘密matching.ts。仓库自带的轮询示例磁带 record-replay/retry.json 直观展示了这一模型两条请求完全相同POST /pollbody{id:job_1}响应依次为{status:pending}与{status:complete}——回放时第 1 次、第 2 次调用分别命中对应响应。当请求中有意包含易变数据时可以传自定义等价规则HttpRecorder.http(events/create, { match: (incoming, recorded) incoming.method recorded.method new URL(incoming.url).pathname new URL(recorded.url).pathname, })匹配器签名types.tsRequestMatcher (incoming: RequestSnapshot, recorded: RequestSnapshot) boolean两个快照都已是脱敏后的规范化表示method、url、headers、body 四个字段。九、配置项与磁带文件格式RecorderOptions的完整定义README 与 types.ts 一致interface RecorderOptions { readonly directory?: string readonly metadata?: Recordstring, unknown readonly redact?: RedactOptions readonly match?: RequestMatcher }directory默认为cwd/test/fixtures/recordingsmetadata会作为附加 JSON 元数据存入磁带redact/match见第七、八节。磁带是可读的 JSON 文件设计上应当随测试一起提交。HTTP interaction 按请求顺序存储WebSocket 磁带保留观察到的帧序文本保持可读二进制 body 与帧以 base64 无损存储。格式版本为1仓库中的真实磁带长这样record-replay/retry.json{ version: 1, interactions: [ { transport: http, request: { method: POST, url: https://example.test/poll, headers: { content-type: application/json }, body: {\id\:\job_1\} }, response: { status: 200, headers: { content-type: application/json }, body: {\status\:\pending\} } } ] }响应快照中还有一个bodyEncoding字段文本响应省略该字段二进制响应则标记为base64types.ts。回放时HEAD请求与 204/205/304 状态会被构造为无 body 的 Response其余情况按bodyEncoding解码internal-effect.ts。十、当前限制beta 边界README 明确列出当前限制使用决策时应据此判断响应在录制与回放期间都会被缓冲因此不适合断言流式时序、取消或背压的测试WebSocket 回放保留帧时间线与内容不保留真实网络时序或背压WebSocket V1 磁带不重现终端关闭码、关闭原因或传输故障失败与中断的实时运行不会被录制WebSocket 转录在连接结束前驻留内存避免在无边界的会话上使用包要求上面列出的精确 Effect beta 版本磁带格式版本1尚无迁移工具。回放侧还有一个值得注意的收尾校验recorder.ts 中的 finalizer 会在作用域结束时检查磁带是否被用尽——若存在未消费的已录制 interaction测试会以Unused recorded interactions in name: used X of Y失败。这保证测试代码路径的变化少发了请求能被回放立刻发现而不是静默放过。小结opencode-ai/http-recorder以最小 APIhttpsocket两个 Layer 工厂提供了 Effect 生态中的 VCR 式测试能力本地首跑录制、CI 强制回放、严格顺序匹配、纵深脱敏与删除后重录的磁带管理策略均围绕测试确定性与秘密不落盘两个目标设计。其公开 API 面在 src/index.ts 中一目了然而 src/recorder.ts、src/matching.ts、src/redactor.ts 与 test/record-replay.test.ts 则提供了逐条验证实现行为的入口适合进一步深入阅读。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考