ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache Zeppelin Notebook 传输契约测试:版本化 REST/WebSocket Fixture 格式与捕获-回放适配器实战指南

Apache Zeppelin Notebook 传输契约测试:版本化 REST/WebSocket Fixture 格式与捕获-回放适配器实战指南 数据分析数据可视化大数据后端前端任务调度【免费下载链接】zeppelinWeb-based notebook that enables>项目地址https://gitcode.com/gh_mirrors/zeppe/zeppelin点击查看免费下载导读本文以 Apache Zeppelin 前端仓库中zeppelin-web-angular/e2e/core-contract目录为核心系统讲解 Notebook 传输契约测试transport contract test的设计与实现。该目录定义了一套版本化的 REST 与 WebSocket fixture 格式用于驱动 Notebook 共享适配器shared adapter的捕获capture与回放replay测试。读完本文你将掌握fixture 文件的结构与元数据要求、v1 格式能表达与不能表达的行为边界、敏感信息脱敏规则、捕获服务器的安全启动方式以及四条测试链路的完整用法check:core-contract-fixtures、check:core-contract-server、e2e:core-contract、e2e:core-contract:live。文中的每一项结论均有对应源码路径佐证可放心作为团队契约测试设计的参考。背景定位本目录的测试是仓库内的契约测试in-repository contract test既不是 Pact 风格的消费方/提供方契约也不是真实服务器 E2E 测试的替代品。它与运行中的 Zeppelin 服务器、授权、协作、重连、解释器执行、流式输出等真实场景测试互补详见 core-contract/README.md。一、为什么要做 Notebook 传输契约测试Zeppelin 前端特别是zeppelin-web-angular下的 React 版 Notebook 核心需要同时通过 REST/api/notebook路径与 WebSocket/ws路径与服务器通信。这两类流量高度交织打开一个 Notebook 往往先发GET_NOTE的 WebSocket 帧再补一次 REST 读取段落执行结果可能以 WebSocket 帧流式推送也可能由 REST 响应承载认证、协作等场景还会在帧里带principal、users、roles等身份字段。如果每次改动都用起一个真实 Zeppelin 服务器 跑完整 E2E来验证速度慢、环境依赖重且难以稳定复现网络层面的乱序、迟到、重复、丢失等异常。契约测试的思路是把一次观察到的REST/WebSocket 流量交错固化成一份可版本化的 fixture 文件再由一个 Playwright 适配器在浏览器里按这份 fixture 精确回放让前端代码在伪服务器上得到确定性验证。目录中的核心实现文件如下notebook-transport-fixture.mjsfixture 的捕获器recorder、校验器validator、脱敏器sanitizer与回放适配器Playwright adapter全部在此约 1100 行notebook-transport-fixture.d.mtsTypeScript 类型声明定义了TransportFixture、FixtureRecord、FixtureMetadata、PlaywrightFixtureAdapter、NotebookTransportRecorder等接口notebook-transport-fixture.test.mjs2629 行的 Node 测试套件覆盖格式校验、脱敏、回放顺序等全部行为capture-server.sh启动隔离的 Zeppelin 捕获服务器capture-server.test.mjscapture-server 的测试套件。二、Fixture 文件结构元数据与记录2.1 完整结构示例每份提交到仓库的 fixture 都必须包含以下字段。捕获器createNotebookTransportRecorder在缺少这些字段时会直接拒绝捕获validateFixture会把它们报告为错误而回放适配器只调用validateReplayFixture仅检查记录形状与顺序因此一份未经校验就回放的 fixture 永远不会被检查元数据。{ version: 1, metadata: { scenario: Open a notebook, owner: zeppelin-web-angular, coveredOperations: [GET_NOTE], knownExclusions: [Live interpreter execution is covered by a separate E2E scenario] }, records: [ { kind: websocket, sequence: 1, websocket: { direction: send, payloadText: {\op\:\GET_NOTE\} } } ] }四个元数据字段的含义字段含义要求scenario描述用户可见的业务流程非空字符串如 Open a notebookowner维护该 fixture 的组件非空字符串本仓库为zeppelin-web-angularcoveredOperations该 fixture 代表的 REST 或 WebSocket 操作非空字符串数组如[GET_NOTE]knownExclusions有意不覆盖的行为及其理由字符串数组无排除时可为空数组[]2.2 记录record的结构约束每条记录由kindrest或websocket与严格递增的sequence组成校验规则对应 validateReplayFixtureREST 记录direction只能是request或response请求必须带method大写 HTTP 动词、url与headers对象响应必须带整数status与响应头请求/响应必须至少携带bodyJson结构化 JSON或bodyRaw原始字符串之一以保证形状可比较——见 validateRestRecord。WebSocket 记录direction只能是send客户端发出或receive服务器接收每条记录必须且只能包含payloadText与payloadBase64之一两者同时出现会被校验拒绝payloadBase64必须能通过 base64 往返解码Buffer.from会静默丢弃非法字符因此校验器要求 round-trip 等价——见 validateWebSocketRecord。校验还要求每条请求记录必须有对应的响应记录否则报request has no response防止手工编写的残缺 fixture 蒙混过关sequence必须严格递增、不能重排。2.3 何时新增 fixture当一个 Notebook 操作被纳入共享适配器的契约范围时就应该新增对应 fixture。如果该操作暂时无法用 v1 格式表达必须把明确理由写进该场景的knownExclusions不能默默依赖另一个 fixture来掩盖缺口。三、Version 1 不建模什么边界即设计一份 fixture 记录的是一次观察到的 REST 与 WebSocket 流量交错回放时严格按该顺序投递。但迁移计划该格式为之服务的 ZEPPELIN-6671、ZEPPELIN-6672 等场景不假设HTTP 响应与其相关 WebSocket 事件的顺序有保证后续场景还需要复现重复、乱序、迟到、丢弃等异常。v1 无法表达两者任一顺序都可接受只能钉死捕获到的那个顺序。几个关键的边界事实REST 响应占据 Playwrightrequestfinished事件的位置此时响应体已下载完毕。更早的response事件只提供响应头response.text()是异步的不能把记录位置推到后续 WebSocket 帧之后。stop()与write()会等待这些 body 读取完成stop()还会允许已捕获的响应在停止接受新流量之后继续完成。该格式回放的是完整响应体不是只有头可用、中间 HTTP 分块或应用回调时序。钉死的是 fixture应答的顺序而不是页面提问的顺序。浏览器想什么时候发请求就什么时候发fixture 无法规定。由此推导出且仅推导出两条容忍规则见 createPlaywrightFixtureAdapter 的drain实现连续 REST 请求记录构成一个请求批次批次止于下一条响应或 WebSocket 记录。批次内请求按形状shape匹配、不要求到达顺序响应保持记录的投递顺序。例如记录为请求A、请求B、响应A、响应B时即使 B 先到也能正确回放。当 fixture 正期待一条 WebSocket 帧时提前到达的请求会等待只要后续某条记录能回答它。超出当前批次、没有任何剩余记录能回答的请求会被判定为请求失配而拒绝纯响应型 fixture 没有请求批次因此会拒绝不同的首个请求无法匹配下一条记录的 WebSocket 帧同样被拒绝。两个完全相同的并发请求无法区分。记录按形状方法、路径、头、体匹配到路由如果页面并发发两次相同请求而捕获记录了两条不同响应回放可能把两条响应互换给彼此的路由仍然报告成功。顺序请求不受影响因为它们按到达顺序匹配。assertComplete()要求每条 REST 投递都成功收尾。某条 route 的fulfill失败会拒绝该 route 并阻止成功完成但独立路由仍能收到各自记录的响应。principal出现即脱敏而 Zeppelin 几乎在每个 WebSocket 帧上都放它user、users、roles同理。因此 fixture 中不出现任何人名也就无法表达取决于行为者是谁的场景——ZEPPELIN-6673 的权限与认证场景正需要这种区分。只有accept和content-type两个请求头能存活所以 fixture 无法携带 401 的www-authenticate头或重定向的location头这两者都是 ZEPPELIN-6673 需要的。记录不带任何时序信息依赖延迟例如服务器在 HTTP 响应超时前应用了变更的场景无法回放一份 fixture 只建模一条 WebSocket 连接ZEPPELIN-6672 的重连场景超出 v1 能力。捕获器会拒绝第二条 notebook WebSocket 连接包括重连而不是把多连接压扁进无法回放的 fixture。这些限制是有意的在场景出现之前就扩宽格式等于为猜测而设计。当场景需要时version字段就是引入新能力的地方——为旧版本编写的 fixture 会被拒绝而不是被静默重新解释。四、测试层次四条链路各司其职4.1 快速层格式、脱敏、回放校验无需浏览器运行格式、脱敏、排序与回放检查npm run check:core-contract-fixtures该命令对应 package.json 中的node --test e2e/core-contract/notebook-transport-fixture.test.mjs e2e/core-contract/playwright-runner.test.mjs e2e/core-contract/reject-notebook-core-runtime-plugin.test.mjs覆盖格式校验、脱敏规则与回放适配器对记录的测试仅需数秒。4.2 捕获服务器层真实进程的启停行为Maven integration-test 阶段capture-server 有自己独立的套件它会真实启动capture-server.sh对抗一个 stub覆盖 start、stop 与 pid 文件行为。它要 spawn 进程并绑定空闲本地端口因此耗时数十秒放在Maven 的 integration-test 阶段而非每次构建中npm run check:core-contract-server两层都覆盖不到的地方recorder 对二进制 WebSocket 帧的拒绝。帧只能来自真实 WebSocket 服务器——route.fulfill应答的 socket 永远不会真正打开routeWebSocketmock 的 socket 不会触发page.on(websocket)——所以该路径只由 Node 测试的事件发射器覆盖其在 Playwright 真实调度下的行为未经验证见 notebook-transport-fixture.test.mjs。4.3 浏览器层适配器与 Playwright 对象的真实接触浏览器带来的独特价值是适配器与 Playwright 自己的Request、Response、Route、WebSocket对象的接触手工编写的 double 无法替代——文档明确记载了一个教训某处应该是route.fallback()的调用很长一段时间里在 doubles 面前看起来是正确的。浏览器测试覆盖这种接触包括针对本地服务器的 HTTP-body-first 与 WebSocket-first 两种交换。捕获/回放往返还会检查无 Accept 头的请求因为 Playwright 对page.on(request)监听器与page.route处理器描述请求的方式并不一致npm run e2e:core-contract4.4 各层如何被 Maven/套件编排格式检查Maven test 阶段capture-server 检查Maven integration-test 阶段浏览器测试属于普通 e2e 套件随套件运行live 捕获是唯一不自动运行的一层标记为live直到显式执行npm run e2e:core-contract:live才会触发。五、专注配置playwright.core-contract.config.js 的关键取舍专注命令使用 playwright.core-contract.config.js其非 live 模式有以下特点无认证设置、存储的浏览器会话、全局 setup/teardown 与 dev server不接触PLAYWRIGHT_BASE_URL也不清理另一次运行产生的 notebook专注命令跑Chromium普通 e2e 套件仍在其 Chromium、Firefox、WebKit 三个项目中包含这些合成浏览器测试并排除live。配置细节对应源码live process.env.ZEPPELIN_E2E_LIVE_CAPTURE 1live 模式要求显式提供PLAYWRIGHT_BASE_URL指向隔离捕获服务器运行目录默认取临时目录zeppelin-core-contract-${randomUUID()}可通过ZEPPELIN_CORE_CONTRACT_RUN_DIR覆盖globalSetup/globalTeardown/webServer全部置空避免共享钩子删除无关 notebook 或在离线运行中接触服务器workers: 1、retries: 0、reporter: listoutputDir指向运行目录下的results/live 模式下增加setup项目执行global.setup.ts完成登录浏览器项目携带storageState指向.auth/user.json。六、捕获服务器隔离、安全与防污染6.1 启动/停止与认证模式capture-server 需要lsof来验证监听者归属以及一个构建好的检出built checkout./mvnw clean install -DskipTests -pl zeppelin-web-angular -am。启动失败会报告服务器日志仅凭一次成功的 HTTP 响应不足以确立归属——另一台服务器可能抢占了这个原本空闲的端口脚本会用lsof核对监听进程组是否带有本 root 写入的 JVM 参数标记。CAPTURE_ROOT$(mktemp -d) e2e/core-contract/capture-server.sh start --root ${CAPTURE_ROOT} --port 18080 ZEPPELIN_E2E_SHIRO_INI${CAPTURE_ROOT}/conf/shiro.ini \ ZEPPELIN_CORE_CONTRACT_RUN_DIR${CAPTURE_ROOT}/browser \ CItrue PLAYWRIGHT_BASE_URLhttp://127.0.0.1:18080 npm run e2e:core-contract:live e2e/core-contract/capture-server.sh stop --root ${CAPTURE_ROOT}脚本用法见 capture-server.shcapture-server.sh start|stop --root dir [--mode anonymous|auth] [--port port]--mode默认anonymous认证捕获加--mode auth它会把仓库conf/shiro.ini.template安装到捕获 root同一个ZEPPELIN_E2E_SHIRO_INI设置负责选中它。helper 接线正常与一次成功的认证捕获是两个独立检查多用户权限场景仍是 ZEPPELIN-6673 的职责。npm run check:core-contract-auth运行一个浏览器支撑的 anonymous 设置回归测试在一次性目录里并验证普通套件的认证快照未被覆盖它需要已安装的 Chromium但不需要 Zeppelin 服务器Node-only fixture 检查会跳过这个浏览器回归。路径约束capture root 与仓库路径不得包含空白字符包括通过符号链接到达的物理路径。Zeppelin 启动器按空白切分 JVM 参数脚本在创建文件或启动进程之前就拒绝这类路径。请选择不含空格、制表符与换行的 root 与检出目录。操作锁start 与 stop 在 capture root 持有原子.capture-operation-lock直到操作完成并发操作直接失败。若操作被 SIGKILL 杀死先检查其进程再考虑移除遗留的锁或starting标记——脚本不会猜测另一个操作的锁已过期。6.2 服务器环境隔离丢弃继承污染start_zeppelin会丢弃所有继承的ZEPPELIN_*设置、JVM 选项变量JAVA_OPTS、JAVA_TOOL_OPTIONS、_JAVA_OPTIONS、JDK_JAVA_OPTIONS与CLASSPATH显式选择本地VFSNotebookRepo存储与 loopback 绑定因此 shell 的远程 notebook 配置无法把捕获写入重定向走。JAVA_HOME与PATH仍用于选择已安装的工具链。启动细节包括删除 root 中可能残留的zeppelin-env.sh防止遗留配置覆盖隔离承诺、拷贝conf/log4j2.properties与conf/zeppelin-site.xml.template、设置ZEPPELIN_ADDR127.0.0.1、ZEPPELIN_NOTEBOOK_STORAGEVFSNotebookRepo等并给服务器独立的进程组以便 stop 能连同子进程一起终止。6.3 live 捕获的运行纪律live 捕获复用同一份专注配置但要求显式PLAYWRIGHT_BASE_URL、认证设置与零重试。测试只在finally块中删除自己创建的 note两种模式都不调用共享 API 清理。CItrue会禁用截图与视频。即使 anonymous 模式也要把登录 helper 指向 capture rootroot 中缺失的shiro.ini能防止回退到无关的仓库凭据。认证状态与浏览器结果分别放在临时运行目录下的独立子目录.auth/user.json与results/不会覆盖普通套件的认证快照或测试结果并发运行要各自使用不同的ZEPPELIN_CORE_CONTRACT_RUN_DIR制品不再需要时删除运行目录。七、捕获安全与脱敏规则7.1 捕获范围createNotebookTransportRecorder(metadata)只记录/api/notebook的 REST 流量isNotebookRestUrl检查路径等于或前缀匹配/api/notebook与/ws帧isNotebookWebSocketUrl检查路径名为/ws。它在写 fixture 之前脱敏配置的敏感与易变字段JSON WebSocket 帧被规范化并脱敏二进制帧在捕获阶段直接拒绝直到实现了二进制脱敏策略。但回放仍支持手工编写的二进制 fixture用于协议级测试——例如payloadBase64回放为真实的 Buffer 字节测试见 notebook-transport-fixture.test.mjs。每条 WebSocket 记录必须恰好包含payloadText与payloadBase64之一两个都带会被校验拒绝。调用stop()或write()时仍有未响应的 notebook 请求会让捕获失败请求失败同样如此。请等场景完成后停止。validateFixture也会拒绝没有匹配响应的请求记录。7.2 被规范化的标识符类别字段/规则处理时间戳dateCreated、dateStarted、dateFinished、dateUpdated、lastUpdated、time替换为占位符依赖时序的场景留在 v1 之外WebSocket 信封msgId关联键Angular 用回显 ID 聚焦本地插入/克隆的段落捕获时分配稳定占位符如msgId:1并保留每个 ID 的重复引用回放时绑定客户端真实发送的 ID 并在匹配响应中替换成真实 ID。拒绝不一致或复用的绑定。含已擦除msgId信封值的旧 fixture 必须重新捕获身份关系无法恢复。非信封位置的msgId字段走通用脱敏规则人名/身份userZeppelin 对段落执行主体的称呼、owners、readers、writers、runners/api/notebook/{id}/permissions的应答、users多个会话打开同一 note 时COLLABORATIVE_MODE_STATUS帧列出、roles按字段名整体掩蔽权限集合按条目逐一掩蔽保留数组形状与数量。文本规则不动user与owners因为二者在 URL 与 note 文本中很常见IDnoteId、paragraphId按捕获原样保留维持 URL、body 与帧之间的引用。这是 v1 的可复现性取舍重捕同一流程可能产生不同文件。未来可用双射 ID 映射消除这种变动但 v1 不做重捕获按新录音审阅而非 diff。Zeppelin 段落 ID 内嵌创建时间paragraph_1757...因此即使契约未变重捕也会产生文本不同的 fixture7.3 脱敏命名的判定规则脱敏先作用于字段名。一个名字只要满足下列任一条件即视为敏感某个词是敏感词api[-_]?key、authorization、client[-_]?secret、cookie、credential(s)?、jsessionid、passphrase、passwd、password、principal、private[-_]?key、secret、ticket、token等或以敏感词结尾或对于单个大写连写环境变量风格的名字只要包含敏感词即可。名字按分隔符与驼峰拆分所以accessToken、secretKey、aws_secret_access_key、spark.hadoop.fs.s3a.secret.key、x-api-key、PGPASSWORD、SECRETKEY、private_key、passphrase全部命中而tokenizer、secretary、tokens、max_tokens、privately、keyboard全部安全。实现见 shouldRedactField测试佐证见 notebook-transport-fixture.test.mjs。注意边界名单之外的名字不会被掩蔽——pwd、bearer、sessionId就不在名单上应新增词条而非依赖值的形状。混合大小写且无分隔符的名字如SECRETkey夹在规则之间会漏网。7.4 文本扫描只有两个字段只有两个字段没有内部结构可供按名脱敏因此仅这两个按文本扫描url凭据以无名形式藏在 query 里与bodyRaw整个不透明字符串。规则查找namevalue与name: value、URL 的user:passwordhost、以及延伸到行尾的 header 凭据。文本扫描同时保护占位符的幂等性已是redacted或 URL 编码占位符%3Cticket%3E的值不会被二次改写。WebSocket 帧选择自己的处理方式能解析为 JSON 的帧按 key 脱敏同其他记录不能解析的整串按文本扫描同bodyRaw。如果在 key 脱敏之上再叠加文本扫描会把 note 内部内容改写掉留下无法回放的捕获。7.5 其他一切保持原样这是刻意的json字段与数组内的字符串由其所在字段名覆盖值保持原样。这是刻意设计文本规则无法区分凭据与提到凭据的行文而 note 里满是这种行文。扫描 note 文本曾把ticketCount df.count()、SELECT ... WHERE ticket_id 42、const cookieBanner document.getElementById(x)全部改写对 fixture 是比它关闭的窄缺口更糟的结果。捕获 helper 启动隔离空服务器也出于同一原因fixture 的 note 文本由捕获它的测试写出而不是由机器所有者写出。文本规则的残余盲区它们扫描但仍够不到的地方含空格的未加引号值会保留尾部name: value对只在名字开启无缩进行、引号字符串、对象或 JSON 数组条目时匹配因此缩进 yaml key 与-列表项保持原样URL userinfo 只在user:passwordhost形式匹配tokenhost不受影响名字与值分居两字段如{name: password, value: hunter2}时值旁没有名字可匹配passwordabc被读作比较而非赋值完全没有名字的凭据——裸 token、base64 blob——对这里的每条规则都不可见。文本中纯粹数值对以principal结尾的名字豁免该词同时是会计术语但名为principal的字段无论值是什么都掩蔽。最后一条纪律不要依赖任何上述规则去处理来自持有真实凭据的服务器的捕获。永远从本目录启动的隔离服务器捕获。八、回放失败如何报告Playwright 适配器通过拒绝 route handler报告损坏的 fixturePlaywright 把它呈现为未处理错误并归属到运行中的测试见 createPlaywrightFixtureAdapter某条 route 的 key 已无剩余记录可回答 →立即拒绝某条 route 只是在等待 →不拒绝。fixture 无法区分页面还没发这个请求与页面永远不会发这种情况交给 Playwright 自身的测试超时处理。回放的严格性没有剩余记录能回答的请求 → 失败不匹配下一条记录的 WebSocket 帧 → 失败任何未被消费的剩余记录 → 失败assertComplete()检查cursor ! records.length并区分未消费记录数与未完成的 REST route 数。这些检查证明的是 fixture 形状与适配器传输行为。运行中的 Zeppelin 服务器、授权、协作、重连、解释器执行、流式输出、性能与无障碍仍需独立的 E2E 场景覆盖。九、可继续深入阅读的仓库入口契约测试总说明core-contract/README.mdfixture 实现与适配器notebook-transport-fixture.mjs类型接口notebook-transport-fixture.d.mts2629 行行为测试notebook-transport-fixture.test.mjs测试双doublesfixture-doubles.mjs 与 fixture-doubles.d.mts捕获服务器脚本与测试capture-server.sh、capture-server.test.mjs捕获 stub 服务器capture-stub-zeppelin.mjs浏览器端捕获/回放测试capture-fixtures.spec.ts专注 Playwright 配置playwright.core-contract.config.jsnpm 脚本入口package.json仓库构建方式docs/setup/basics/how_to_build.md结语Apache Zeppelin 的 Notebook 传输契约测试以仓库内、版本化、可回放为原则把 REST 与 WebSocket 的交错流量固化为 fixture配合脱敏、严格校验与多层测试链路为共享 Notebook 适配器提供了无浏览器依赖的快速反馈与真实浏览器接触的纵深验证。其 v1 格式刻意不建模乱序容忍、多连接重连、时序依赖与身份相关场景并以version字段为未来的扩展点——这是一份值得借鉴的契约优先、边界明确、安全至上的传输层测试设计。赞分享数据分析数据可视化大数据后端前端任务调度【免费下载链接】zeppelinWeb-based notebook that enables>项目地址https://gitcode.com/gh_mirrors/zeppe/zeppelin点击查看免费下载相关推荐Apache Zeppelin Notebook REST API 完整实战指南Note、Paragraph、Cron、权限与版本控制Apache Zeppelin Notebook REST API 完整实战指南Note、Paragraph、Cron、权限与版本控制 Apache Zepp数据分析数据可视化大数据后端前端任务调度Apache Zeppelin Notebook REST API 完全指南Apache Zeppelin Notebook REST API 完全指南 概述 Apache Zeppelin 提供了一个功能强大的 REST API允许数据分析数据可视化大数据后端前端任务调度Apache Zeppelin Notebook Repository REST API 实战指南多存储管理与远程配置Apache Zeppelin Notebook Repository REST API 实战指南多存储管理与远程配置 导读 Apache Zeppelin数据分析数据可视化大数据后端前端任务调度上一篇GoQt调试技巧解决cgo与Qt集成的常见问题终极指南下一篇React Image Gallery项目架构解析构建可维护的React组件库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进