jcode 订阅账户契约一致性测试:从设备登录向量到混合版本兼容矩阵

发布时间:2026/9/13 18:51:19
jcode 订阅账户契约一致性测试:从设备登录向量到混合版本兼容矩阵 jcode 订阅账户契约一致性测试从设备登录向量到混合版本兼容矩阵【免费下载链接】jcodeThe most RAM efficient harness项目地址: https://gitcode.com/GitHub_Trending/jcod/jcode本文以 jcode 仓库中docs/dev/ACCOUNT_CONTRACT_CONFORMANCE_TESTS.md的一致性契约设计为主体完整展开 jcode 订阅账户合同device login、浏览器审批、/v1/me账户状态、checkout、计费门户、webhook 排序、吊销、混合版本兼容的可执行测试向量体系并结合 设备登录编排、订阅 API 客户端 与 凭据持久化模块 的源码实现说明每个向量的断言依据与当前代码中已落地/待补齐的部分。读完后你能够理解“fixture 即契约”的双仓库测试协作模式、复现客户端轮询状态机的全部分支行为并按文档给出的执行计划落地共享测试向量。一致性契约要解决什么问题jcode 的订阅账户功能横跨两个仓库客户端半边设备登录轮询、凭据持久化、/v1/me解析、tier 门控在 jcode 本仓库服务端半边邮件投递、审批/拒绝页面、checkout 会话、Stripe webhook、密钥吊销、/v1/me的真相源在私有的solosystems-backend仓库。原文档给出的核心答案是用版本化的 JSON 测试向量fixtures作为双方共同执行的契约客户端用 Rust 测试驱动脚本化 HTTP 服务器回放向量后端用集成测试把同一批 fixture 回放给真实 handler。由此确立一条硬规则原文档 Repository ownership 一节一条 fixture 的变更就是一次契约变更。每个向量文件都带schema_version字段做版本化两个仓库各自 pin 住 fixture 集合并通过混合版本矩阵证明旧客户端能跑过新服务器的向量、反之亦然。契约范围与当前实现对照Grounding原文档的 Grounding (current code) 一节把契约锚定到具体源码。下面按文档列出各部分并用当前仓库中已确认的实现位置与行为做补充。设备流客户端与 wire 合同src/cli/login/jcode_device.rs协议解析与 HTTP 行为在 crates/jcode-base/src/subscription_api.rsPOST {auth_base}/v1/auth/device返回{device_code, verify_url, expires_in (默认 900), interval (默认 5)}。当前客户端解析时会对这两个字段做防御性钳制expires_in缺省回退 600 并钳制到1..3600、interval缺省回退 3 并钳制到1..60subscription_api.rs 第 285-286 行。注意当前代码的解析回退默认值600/3与 DL-02 向量约定的契约默认值900/5并不相同这正是 fixture 落地后需要由schema_version钉死的边界行为。POST {auth_base}/v1/auth/token的响应分类202/428为 pending429为 slow-down200为 approved{api_key, account_id?, email?, tier?}200 {status:pending}是遗留 pending 形态错误码识别authorization_pending|pending、slow_down、expired_token|expired|expired_device_code、access_denied|denied404/410按 expired 处理。当前实现中poll_device_token_oncesubscription_api.rs 第 290-353 行已覆盖上述大部分分支429 解析Retry-After头并返回SlowDown { retry_after }成功响应体先检查遗留 pending 错误码再解析ApprovedAccountKeyWire空api_key 直接判InvalidResponse(empty api_key)且不会持久化任何东西非 JSON 的 200 响应判malformed approved token JSON。错误码提取兼容扁平{error:slow_down}与嵌套{error:{code:...}}两种封装且截断到 80 字符以内error_code()第 229-234 行保证错误信息不会回显响应体中的敏感内容。轮询循环时序jcode_device.rs 第 27-93 行interval.max(1)作为基准延迟deadline 为expires_in.max(interval.max(1))秒后的单调时钟时间点slow_down在PollingBackoff中按retry_after.unwrap_or(delay 5s)下限 base、上限 60s增长离线错误翻倍且封顶 30ssubscription_api.rs 第 470-509 行。这套退避是确定性的并有专门测试polling_backoff_is_deterministic_and_bounded验证具体数值base3s 时 slow_down(None)→8sslow_down(Some(12s))→12s连续 offline→24s→30s封顶pending 后回落 base。凭据持久化subscription_catalog.rs 第 406-426 行persist_account_credentials把JCODE_API_KEY、JCODE_ACCOUNT_ID、JCODE_ACCOUNT_EMAIL、JCODE_TIER写入jcode-subscription.env常量JCODE_ENV_FILE并在每次凭据变更后重新收紧并校验文件权限——若 Unix 权限中 group/other 位非零mode 0o077 ! 0直接报错第 455-474 行。账户状态客户端GET /v1/mesubscription_api.rs 第 355-385 行5 秒超时ME_FETCH_TIMEOUT成功解析后把 tier 缓存落盘store_cached_tier仅在解析出合法 tier 时调用未知/缺失 tier 按 Plus 门控——JcodeTier::parse(mystery)返回Noneeffective_tier()回退到JcodeTier::Plussubscription_catalog.rs 第 357-366 行。已有可执行测试基座脚本化本地 HTTP 服务器spawn_scripted_http_serversrc/cli/login/jcode_device/tests.rs 第 7-29 行绑定127.0.0.1随机端口逐条回放预设响应配合轮询状态机测试polling_pending_slow_down_then_approval、polling_denied_has_clear_redacted_error、polling_timeout_is_deterministic_before_first_request等构成本仓库侧现成的 harness。负路径断言分类crates/jcode-base/src/auth/login_diagnostics.rs把错误消息分类为AuthFailureReason含DeviceFlowFailed、RateLimited等并给出对应恢复提示见后文 RV-02。仓库所有权矩阵原文档把所有测试关注点按“谁拥有、用什么 harness 执行”划分为四行关注点拥有仓库Harness客户端轮询状态机、持久化、/v1/me解析、tier 门控jcode本仓库针对脚本化 HTTP 服务器的 Rust 单元/集成测试共享 wire 测试向量JSON fixturesjcode按版本 tag 镜像进 solosystems-backendtests/fixtures/account-contract/提案邮件投递、审批/拒绝页面、checkout 会话创建、Stripe webhook、密钥吊销、/v1/me真相solosystems-backend私有后端集成测试把同一批 fixtures 回放给真实 handler端到端冒烟真实 stagingsolosystems-backend CI以及 jcode CI 中依赖 staging 凭据的 opt-in 任务对 stagingJCODE_API_BASE执行jcode login jcode脚本化流程Fixture 布局本仓库提案原文档提出的目录结构tests/fixtures/account-contract/ v1/ device_auth/ # POST /v1/auth/device 的响应 token_poll/ # POST /v1/auth/token 的脚本化序列 me/ # GET /v1/me 的响应体 webhook_order/ # 后端专用镜像过来供文档参考 manifest.json # {schema_version, vectors: [...]}每条向量的数据结构为{name, request, response_script: [(status, body)...], expected_outcome, notes}。Rust harness 负责反序列化 manifest 并驱动spawn_scripted_http_server使向量是数据而非代码——这与现有 harness 的Vec(status, headers, body)输入形态jcode_device/tests.rs 第 5 行一脉相承只需把硬编码序列换成 manifest 驱动。1. 设备登录向量DL完整继承原文档的 DL-01 至 DL-12 向量表ID脚本预期DL-01 happy pathdevice 200 完整体token 202、200 approvedTokenApprovedState被填充env 文件写入全部四个键DL-02 defaultsdevice 200 且缺expires_in/interval应用默认值 900/5DL-03 legacy pendingtoken 200{status:pending}之后 approved先分类为 pending再 approvedDL-04 nested errortoken 400{error:{code:authorization_pending}}PendingDL-05 flat OAuth errortoken 400{error:slow_down}SlowDown等待时间 5sDL-06 expiredtoken 400expired_token含expired、expired_device_codeExpired给出“重新发起”提示DL-07 gonetoken 404/410 空 bodyExpiredDL-08 deniedtoken 403{error:{code:access_denied,message:...}}Denied 且服务端 message 被呈现DL-09 empty api_keytoken 200{api_key: }硬错误任何内容都不持久化DL-10 garbage 200token 200 非 JSON解析错误任何内容都不持久化DL-11 unexpected 5xxtoken 500错误信息包含 status 截断后的 bodyDL-12 device rejectdevice 400/422/429在任何轮询发生前中止登录当前实现对这些向量的覆盖情况从源码确认DL-03 遗留 pending成功状态码分支先检查 body 错误码是否为authorization_pending|pending命中则返回Pendingsubscription_api.rs 第 318-324 行测试token_poll_handles_pending_slow_down_success_denied_and_replay已按 428→429→200→400(denied)→400(expired) 顺序全量覆盖。DL-09/DL-10空api_key返回InvalidResponse(empty api_key)畸形 JSON 返回InvalidResponse(malformed approved token JSON)二者都发生在persist_approved_key之前保证“nothing persisted”。DL-11非认证类错误落入AccountApiError::Http { status, code }错误码只保留 80 字符截断值is_temporary()对 5xx 返回 true轮询循环会以离线退避重试并只提示一次Connection interrupted. Retrying with backoff...jcode_device.rs 第 83-89 行。DL-07 的差异点当前 token 端点把 404 分类为LegacyBackend提示后端仍在使用遗留邮箱登录合同而文档规格要求 404/410 按 expired 处理——fixture 落地时该分支的分类策略需要与向量对齐。2. 浏览器审批/拒绝BA后端拥有、向量镜像客户端无法测试审批网页本身因此原文档要求后端具备如下可执行测试向量镜像到本仓库用于闭环断言BA-01审批链接对 device_code 标记 approved 恰好一次幂等。BA-02拒绝链接让下一次轮询返回access_denied并携带拒绝原因。BA-03对已过期 code 的审批返回错误页轮询保持 Expired。BA-04魔法链接 token 一次性第二次点击是 no-op 或错误。BA-05来自与邮件目标不同账户/会话的审批必须失败。BA-06verify_url的 host 必须与 auth 服务源一致客户端负向侧拒绝/不自动打开非 HTTPS 或外部源 URL。文档特别指出当前maybe_open_browser会打开服务器发来的任意 URLsrc/cli/login.rs 第 1445-1451 行 直接调用open::that(target)仅受no_browser抑制这是一个待补齐的检查点。3. 账户状态ME/v1/meIDBody预期ME-01 fullactive flagship 带 usage正常解析tier 被缓存ME-02 minimal缺resets_at、未知 tiermystery容忍parsed_tier()为 None门控回退到 PlusME-03 401{error:invalid_key}错误被呈现缓存 tier 不被覆盖吊销是显式事件见第 6 节ME-04 5xx/timeout延迟 5s触发ME_FETCH_TIMEOUT离线门控使用缓存 tierME-05 status valuesactive、past_due、canceled、trialing客户端按字面渲染 status未知值不崩溃实现要点从源码确认SubscriptionMe的usage字段全部带#[serde(default)]budget_usd还接受spend_limit_usd/limit_usd别名subscription_api.rs 第 19-35 行直接支撑 ME-02 的“容忍”语义manage_url为可选且“永远不得包含 secret”。ME-03 的缓存保护是结构性成立的store_cached_tier只在fetch_subscription_me_with成功解析且parsed_tier()为Some时执行第 381-383 行401 走AccountApiError::Unauthorized错误路径缓存 tier 原样保留。ME-04的 5 秒超时常量ME_FETCH_TIMEOUT已定义第 14 行未知 tier 的 Plus 回退由effective_tier()保证并有测试hosted_catalog_access_does_not_depend_on_legacy_cached_tier覆盖JCODE_TIERmystery场景。4. Checkout 与门户CKCheckout/portal 目前是纯 web 侧功能客户端在定价 URL 处完成交接——定价页常量JCODE_PRICING_URL定义于 subscription_catalog.rs 第 12 行。一致性要求CK-01后端为已登录设备创建 checkout 会话时产生的订阅必须链接到设备登录返回的同一account_id。CK-02后端checkout 完成后N秒内更新/v1/me的 tier向量断言最终一致性上界文档建议 staging 测试取 N60。CK-03客户端checkout 后重新拉取/v1/me缓存 tier 被升级而无需重新登录测试方式新 tier 覆盖旧缓存值后重放 ME-01。CK-04后端门户取消流程把status置为canceled但密钥在计费周期结束前保持有效客户端侧由 ME-05 覆盖渲染。CK-05客户端登录成功后tier为 none/空时登录流程尾部login_jcode_device_flow尾段打印定价提示——以 stderr 文本快照测试验证。当前实现中token 交换成功后还有一段计费激活轮询jcode_device.rs 第 146-212 行poll_for_paid_activation在 10 分钟ACTIVATION_TIMEOUT内轮询/v1/me直到出现付费计划并按结果映射为LoginCompletionActive时重写缓存account_id/email/tier并提示月度消费上限TimedOut/Canceled时保留已保存的有效密钥并打印恢复动作jcode account status/manage/logoutRevoked/Denied时清除本地凭据并要求重新登录。这正是 CK-03/CK-05 客户端侧行为的实现落点。5. Webhook 排序WH后端拥有Stripe webhook 乱序且至少一次投递。后端测试必须把以下顺序回放给 webhook handler 并断言最终状态WH-01checkout.session.completed→invoice.paid正常序。WH-02invoice.paid先于checkout.session.completed乱序。WH-03每个事件的重复投递幂等键。WH-04customer.subscription.deleted与同秒的invoice.paid竞争终态由事件的created时间戳决定而非到达顺序。WH-05签名无效/时间戳过期 → 400无状态变更。WH-06未知事件类型 → 2xx ack无状态变更前向兼容。客户端可观测契约任何 WH 序列沉降后/v1/me恰好反映一个自洽的{tier, status}镜像到me/的 fixture 枚举所有可达终态使客户端测试矩阵保持封闭。6. 吊销RVRV-01后端门户/管理端吊销使 API key 失效模型 API 与/v1/me在有界延迟内staging 断言 ≤ 60s返回 401。RV-02客户端模型 API 的 401 被分类为认证失败并给出指向/login jcode的恢复提示。实现位于 crates/jcode-base/src/auth/login_diagnostics.rsclassify_auth_failure_message按消息特征归类如device flow failed→DeviceFlowFailed429 相关字样 →RateLimitedaugment_auth_error_message追加Next step:恢复提示auth_failure_recovery_hint为每类原因提供具体命令建议如jcode auth doctor、--print-auth-url手动回退。RV-03客户端被吊销的 key 不得在未呈现认证失败的情况下静默回退到其他 provider账户故障转移测试位于 crates/jcode-base/src/provider/account_failover.rs。RV-04后端吊销后重新登录签发新 key旧 key 保持死亡不可复活。客户端侧的 401 语义已有测试佐证me_and_revoke_classify_revoked_keys_without_leaking_them验证/v1/me与DELETE /v1/keys/current对已吊销 key 都分类为Unauthorized且错误字符串中不包含jck_live前缀的密钥内容subscription_api.rs 第 700-719 行——这把 RV-02 与 SN-03 的防泄漏断言绑在了一起。7. 混合版本兼容矩阵DL/ME 向量套组在 2×2 矩阵中运行旧向量 (v1)新向量 (v1.x)已发布客户端stable 通道必须通过必须通过忽略未知字段head 客户端必须通过必须通过被编码为测试的规则未知 JSON 字段被忽略serde 默认行为——永远不要加deny_unknown_fields缺失的可选字段走默认值DL-02、ME-02新错误码落入 “unexpected error” 分支且保留原始 bodyDL-11而不是被误分类为 pending。8. 安全负向测试SNSN-01device_code 熵后端断言 ≥ 128 bits、非可猜测的连续 IDtoken 端点按 code 和 IP 做限流429 路径客户端已处理DL-05。SN-02邮件枚举/v1/auth/device对已知与未知邮箱返回相同响应形态后端。SN-03客户端永不把api_key或完整device_code打印到 stdout/stderr 或日志对登录流程捕获输出做 grep 式测试。现有测试polling_denied_has_clear_redacted_error已断言拒绝错误消息中不出现device-secretjcode_device/tests.rs 第 71-91 行legacy_device_response_is_explained_without_echoing_body断言遗留后端错误不回显响应体。SN-04非 TLS 的 HTTPauth_base在测试之外被拒绝127.0.0.1/localhost除外文档指出当前任何 base 都会被接受需要客户端修改 测试。SN-05超大/恶意 body10 MB body、错误 content-type、NUL 字节——客户端干净报错、不 panictoken_poll/中的 fuzz 式向量。SN-06env 文件权限持久化的凭据文件在 Unix 上必须是 0600针对persist_subscription_credentials的测试。现有测试approved_key_persistence_is_owner_only_and_clear_is_deterministic已断言mode 0o777 0o600且清除后文件中不含 key、account_id、email 的任何残留jcode_device/tests.rs 第 137-182 行。SN-07自动打开浏览器前对 verify_url 的 scheme/host 做白名单对应 BA-06。SN-08审批后轮询复用已消费的 device_code 返回 expired永远不会发出第二把 key后端客户端由 DL-07 语义覆盖。9. 时钟与竞争CRCR-01interval: 0被钳制到 1sinterval.max(1)已隐含该单元测试文档要求显式化。CR-02expires_in: 0时 deadline 为max(expires_in, interval)循环以过期错误终止不热转。现有测试polling_timeout_is_deterministic_before_first_request已验证在发第一个请求前就能确定性超时interval2、expires_in1 →timed out。CR-03连续slow_down使等待单调增长总时长以 deadline 封顶。CR-04审批落在“deadline 检查”与“轮询”之间客户端接受 approved 响应。向量形态直到tdeadline-1仍 pending随后 approved。CR-05客户端计时使用Instant单调时钟墙上时钟偏移必须无影响用大的模拟expires_in 手动结果注入测试。CR-06同邮箱两个并发登录env 文件上最后写入者胜不允许交错/损坏文件文件锁串行化或接受并文档化 last-write-wins 并加测试。CR-07后端approve 与 expire 同秒竞争——恰好持久化一个结果。源码中还有一处与 CR-04 同族的已实现行为值得注意poll_for_api_key在交换请求在途时故意不响应取消jcode_device.rs 第 60-64 行 的注释解释了原因后端可能已原子消费一次性 device 凭据必须让有界请求完成并持久化已批准 key避免用户手里出现一把既看不到也吊销不了的活动 key。测试cancellation_during_consumed_exchange_finishes_and_returns_the_key在 Ctrl-C 于响应前到达的场景下断言最终仍返回Approved。执行计划原文档给出五步落地路径建立tests/fixtures/account-contract/v1/放入上述 DL/ME 向量与 manifest把spawn_scripted_http_server移植为共享测试工具。将现有 jcode_device/tests.rs 用例改为从 manifest 加载保留现有断言无行为变更。补齐写 spec 时发现的客户端缺口SN-03、SN-04、SN-06、SN-07、CR-01/02/06、ME-03 缓存保持。把manifest.json镜像进 solosystems-backend并在那里接入后端套组BA、WH、RV、CK、SN-01/02/08、CR-07。增加混合版本 CI 任务让 stable 通道二进制的登录流程对 head 的 fixtures 通过脚本化服务器运行。小结这套一致性设计的价值在于把“客户端-服务器合同”从口头约定变成了可执行、可版本化、双向验证的资产DL/ME/CK-03/CK-05/RV-02/RV-03/SN-03~08/CR 系列由本仓库的 Rust 测试与脚本化 HTTP 服务器承接BA/WH/RV-01/CK-01/02/04/SN-01/02/08/CR-07 由后端镜像执行schema_version与混合版本矩阵保证任何一方升级都不会悄悄打破另一方。对照当前源码可以看到客户端的轮询状态机、退避策略、凭据 0600 权限、错误脱敏与 tier 回退等断言基础大多已经存在于 subscription_api.rs 与 jcode_device/tests.rs 中剩余工作主要是 fixture 化、补齐安全负向缺口SN-03/04/07 等以及双仓库 CI 接线。【免费下载链接】jcodeThe most RAM efficient harness项目地址: https://gitcode.com/GitHub_Trending/jcod/jcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询