Codex 系统手册

API 客户端、模型、协议、提示词和传输支持测试

stage-23.6.438 个文件

这一阶段像后台质检车间,不直接给用户干活,而是反复检查“和外部服务说话”这套底座稳不稳。模型配置和缓存测试管模型名单、默认值、登录后刷新;API、登录、证书和代理测试管请求地址、身份、重试和安全连接;协议、提示词、工具测试管错误说明、文字编码、模板和参数别变形;WebSocket、HTTP、Unix socket 等测试管消息能顺利收发。假客户端和假服务器则像演练道具,让这些检查不用真连云端也能跑。

本阶段的文件38

模型注册表与缓存测试

这些测试从提供者和预设定义开始,经由 model-info 覆盖,进入完整的模型管理器编排和缓存行为。

model-provider-info/src/model_provider_info_tests.rs源码 ↗
testtest

这个文件不提供正式功能,而是用测试来保护正式功能。项目需要把 TOML 配置文件读成 ModelProviderInfo,也就是“某个模型服务商该怎么连接、用什么鉴权、带哪些请求头”的说明。这里逐项验证:简单配置能读出来,查询参数和请求头不会丢,已经移除的 chat wire_api 会给出友好错误,WebSocket 超时能保存,OpenAI 和 Azure 是否支持远程压缩判断正确。它还重点检查 Amazon Bedrock:内置提供商是否存在、默认地址和请求头是否正确、用户只能改 AWS profile 和 region,不能随便改其他字段。最后,它验证 AWS 鉴权不能和普通 API key 或 WebSocket 混用。简单说,这个文件像配置系统的“安全网”。

函数细节21
test_deserialize_ollama_model_provider_toml9–36 ↗
fn test_deserialize_ollama_model_provider_toml()

作用:检查一个最简单的 Ollama 配置能不能从 TOML 文本正确读成程序里的配置对象。这样可以保证本地 Ollama 服务只写名字和地址时也能正常工作。

数据流:进去的是一小段写死在测试里的 TOML 文本,里面有 name 和 base_url。测试用 toml::from_str 把文本读成 ModelProviderInfo,然后和手工写好的期望对象比较。出来的结果不是业务数据,而是测试通过或失败;如果默认值或读取规则变了,assert_eq! 会立刻报错。

调用关系:测试框架运行它时,它主要把活交给外部的 TOML 解析函数 from_str,最后用 assert_eq! 做核对。它覆盖的是最基础的反序列化路径,给后面更复杂的配置测试打底。

调用图:外部调用 2 个(assert_eq!, from_str)。

test_deserialize_azure_model_provider_toml39–70 ↗
fn test_deserialize_azure_model_provider_toml()

作用:检查 Azure OpenAI 的配置能不能正确读入,尤其是 API key 环境变量名和 api-version 查询参数。Azure 访问模型时常常必须带版本参数,漏掉就会请求失败。

数据流:进去的是一段 Azure 风格的 TOML,包含服务地址、环境变量名、query_params。测试把它解析成 ModelProviderInfo,并用 hashmap! 构造期望的参数表。出来时通过 assert_eq! 确认解析结果完全等于预期,包括没有写的字段是否保持默认空值。

调用关系:它由测试框架单独执行,调用 from_str 走真实的配置读取流程,再用 assert_eq! 对照结果。它和 Ollama 测试一起确认不同服务商的同一套配置结构都能被读懂。

调用图:外部调用 3 个(assert_eq!, hashmap!, from_str)。

test_deserialize_example_model_provider_toml73–107 ↗
fn test_deserialize_example_model_provider_toml()

作用:检查自定义服务商能不能配置固定 HTTP 请求头,以及从环境变量取值的请求头。请求头可以理解成每次发请求时夹带的小纸条,很多网关或公司代理会靠它识别请求。

数据流:进去的是一个 Example 服务商的 TOML,里面写了 base_url、env_key、http_headers 和 env_http_headers。测试解析后,拿手工构造的 ModelProviderInfo 对比。出来的结果是确认固定请求头和环境变量请求头都被放进了正确的位置,没有互相混淆。

调用关系:测试框架运行它;它调用 from_str 做配置文本到对象的转换,用 hashmap! 准备期望的表,再用 assert_eq! 判断是否一致。它补上了请求头这类扩展配置的测试空位。

调用图:外部调用 3 个(assert_eq!, hashmap!, from_str)。

test_deserialize_chat_wire_api_shows_helpful_error110–120 ↗
fn test_deserialize_chat_wire_api_shows_helpful_error()

作用:检查用户如果还写了已经移除的 chat wire_api,会不会看到明确、有帮助的错误。这样用户不会只看到一段看不懂的解析失败信息。

数据流:进去的是一段包含 wire_api = "chat" 的 TOML。测试尝试把它解析成 ModelProviderInfo,但这里期待失败,于是取出错误文本。出来时用 assert! 确认错误消息里包含项目定义好的提示语 CHAT_WIRE_API_REMOVED_ERROR。

调用关系:它由测试框架触发,故意走失败路径。它依赖 from_str 返回错误,再用 assert! 检查错误是否友好,保护的是用户体验而不只是程序正确性。

调用图:外部调用 1 个(assert!)。

test_deserialize_websocket_connect_timeout123–133 ↗
fn test_deserialize_websocket_connect_timeout()

作用:检查 WebSocket 连接超时时间能不能从配置里读出来。WebSocket 可以理解成长时间不断线的双向连接,连接超时如果读错,用户可能会等太久或过早失败。

数据流:进去的是一段 TOML,设置 websocket_connect_timeout_ms 为 15000,并声明支持 WebSocket。测试解析它,然后读取 provider.websocket_connect_timeout_ms。出来时通过 assert_eq! 确认这个值就是 15000 毫秒。

调用关系:测试框架调用它;它把解析工作交给 from_str,自己只验证结果字段。它覆盖的是连接类配置,和普通 HTTP 地址、请求头配置测试互相补充。

调用图:外部调用 2 个(assert_eq!, from_str)。

test_supports_remote_compaction_for_openai136–140 ↗
fn test_supports_remote_compaction_for_openai()

作用:检查内置 OpenAI 服务商会被判断为支持远程压缩。远程压缩可以粗略理解为把过长的上下文交给服务端整理,判断错了会影响长对话能力。

数据流:进去没有外部输入,测试调用 create_openai_provider 造出默认 OpenAI 配置。然后调用这个配置上的 supports_remote_compaction 判断能力。出来时用 assert! 确认结果为真。

调用关系:它由测试框架运行,先使用正式代码里的 OpenAI 创建函数,再检查能力判断。它保护的是 OpenAI 这个特殊内置服务商的默认行为。

调用图:调用 1 个内部函数(create_openai_provider);外部调用 1 个(assert!)。

test_personal_access_token_uses_chatgpt_codex_base_url143–149 ↗
fn test_personal_access_token_uses_chatgpt_codex_base_url()

作用:检查当 OpenAI 使用个人访问令牌登录时,真正请求的地址会切到 ChatGPT Codex 专用地址。这样可以避免把这种令牌发到普通 OpenAI API 地址上。

数据流:进去的是默认 OpenAI provider,以及 AuthMode::PersonalAccessToken 这个鉴权模式。测试把 provider 转成实际请求会用的 API provider,然后读取 base_url。出来时用 assert_eq! 确认地址等于 CHATGPT_CODEX_BASE_URL。

调用关系:测试框架运行它;它先调用 create_openai_provider 准备对象,再走正式转换流程 to_api_provider,最后核对地址。它连接了“配置层”和“真正发请求层”的行为。

调用图:调用 1 个内部函数(create_openai_provider);外部调用 1 个(assert_eq!)。

test_supports_remote_compaction_for_azure_name152–174 ↗
fn test_supports_remote_compaction_for_azure_name()

作用:检查名字叫 Azure 的服务商也会被判断为支持远程压缩。Azure OpenAI 和 OpenAI 类似,如果这里判断错了,Azure 用户会少掉某些长上下文能力。

数据流:进去的是测试手工构造的 ModelProviderInfo,名字设为 Azure,其他字段按常见配置填好或留空。测试调用 supports_remote_compaction。出来时 assert! 要求结果为真。

调用关系:它不依赖解析 TOML,而是直接构造对象测试能力判断。它和 OpenAI 的远程压缩测试成对出现,说明这个能力是按服务商身份判断的。

调用图:外部调用 1 个(assert!)。

test_supports_remote_compaction_for_non_openai_non_azure_provider177–199 ↗
fn test_supports_remote_compaction_for_non_openai_non_azure_provider()

作用:检查普通第三方服务商不会被误判为支持远程压缩。这样可以避免程序对不支持的服务发出特殊请求,导致报错。

数据流:进去的是一个名为 Example 的自定义 ModelProviderInfo。测试调用 supports_remote_compaction。出来时用 assert! 检查结果为假,也就是不支持。

调用关系:测试框架运行它;它和 OpenAI、Azure 两个正向测试一起,圈定了远程压缩能力的边界:该开的开,不该开的不开。

调用图:外部调用 1 个(assert!)。

test_deserialize_provider_auth_config_defaults202–227 ↗
fn test_deserialize_provider_auth_config_defaults()

作用:检查自定义鉴权命令的默认值是否正确。自定义鉴权命令就是让程序运行一个外部命令来拿令牌,如果默认超时或工作目录错了,登录会很难排查。

数据流:进去的是临时目录和一段带 [auth] 的 TOML,其中只写 command 和 args。测试用 AbsolutePathBufGuard 临时设置路径基准,再用 from_str 解析。出来时确认 auth 里自动补上 5000 毫秒超时、300000 毫秒刷新间隔,以及相对当前基准目录解析出的 cwd。

调用关系:测试框架运行它;它调用 tempdir 建临时目录,调用 AbsolutePathBufGuard::new 控制相对路径的参照点,再把 TOML 交给 from_str。最后用 assert_eq! 检查默认值,保护鉴权配置的省心用法。

调用图:调用 1 个内部函数(new);外部调用 3 个(assert_eq!, tempdir, from_str)。

test_deserialize_provider_aws_config230–249 ↗
fn test_deserialize_provider_aws_config()

作用:检查 AWS 鉴权配置能不能从 TOML 里读出来,特别是 profile 和 region。profile 可以理解为本机保存的一套 AWS 身份,region 是 AWS 区域。

数据流:进去的是一段 Amazon Bedrock 风格的 TOML,里面有 base_url 和 [aws] 小节。测试解析成 ModelProviderInfo 后读取 aws 字段。出来时用 assert_eq! 确认 profile 是 codex-bedrock,region 是 us-west-2。

调用关系:它由测试框架执行,调用 from_str 走真实配置解析路径,再用 assert_eq! 对照期望。它是 Bedrock 和 AWS 相关测试的基础读取检查。

调用图:外部调用 2 个(assert_eq!, from_str)。

test_create_amazon_bedrock_provider252–281 ↗
fn test_create_amazon_bedrock_provider()

作用:检查程序内置创建的 Amazon Bedrock 服务商配置是否长得正确。这个默认配置会被很多用户直接使用,错一个地址或请求头都可能让 Bedrock 不能访问。

数据流:进去没有外部输入,只调用 ModelProviderInfo::create_amazon_bedrock_provider,并传入没有额外 AWS 配置。测试把返回的对象和完整写出的期望对象比较。出来时确认名称、默认地址、AWS 字段、协议类型、Mantle 客户端请求头等都正确。

调用关系:测试框架运行它;它直接检查正式的 Bedrock 内置创建函数。后面的测试会继续检查这个 provider 被转换成 API provider 后的请求头行为。

调用图:外部调用 1 个(assert_eq!)。

test_amazon_bedrock_provider_adds_mantle_client_agent_header284–296 ↗
fn test_amazon_bedrock_provider_adds_mantle_client_agent_header()

作用:检查 Amazon Bedrock 转成实际请求配置后,会带上 Mantle 客户端标识请求头。这个请求头像“来访登记”,服务端可能靠它识别 Codex 客户端。

数据流:进去的是 create_amazon_bedrock_provider 生成的默认 Bedrock 配置。测试把它转成 API provider,然后从 headers 里取指定请求头。出来时用 assert_eq! 确认请求头的值等于 AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE。

调用关系:测试框架运行它;它先调用 create_amazon_bedrock_provider,再经过 to_api_provider 进入实际发请求前会用的形态。它验证的是配置创建和请求准备之间没有丢掉关键请求头。

调用图:调用 1 个内部函数(create_amazon_bedrock_provider);外部调用 1 个(assert_eq!)。

test_built_in_model_providers_include_amazon_bedrock299–308 ↗
fn test_built_in_model_providers_include_amazon_bedrock()

作用:检查内置服务商列表里确实包含 Amazon Bedrock。否则用户即使不写自定义配置,也找不到这个官方支持的服务商。

数据流:进去没有外部输入,测试调用 built_in_model_providers 取得内置服务商表。然后用 Amazon Bedrock 的固定 ID 去表里查,并调用 is_amazon_bedrock 判断查到的是不是 Bedrock。出来时 assert_eq! 要求结果是 Some(true)。

调用关系:它由测试框架调用,站在“用户能不能从内置列表拿到 provider”的角度检查。它和创建 Bedrock provider 的测试配合,一个看单个对象,一个看它是否被登记进列表。

调用图:外部调用 1 个(assert_eq!)。

test_merge_configured_model_providers_adds_custom_provider311–330 ↗
fn test_merge_configured_model_providers_adds_custom_provider()

作用:检查用户自己写的自定义服务商会被合并进内置服务商列表。没有这个能力,用户就只能用程序预设的几家服务。

数据流:进去的是一个手工构造的 Custom provider,以及只包含它的 HashMap 配置表。测试先准备一份期望结果:内置 provider 表加上 custom。然后调用 merge_configured_model_providers。出来时 assert_eq! 确认合并结果就是期望的表。

调用关系:测试框架执行它;它使用 default 和 HashMap::from 快速拼出输入,再调用正式合并函数。它覆盖的是最普通、最宽松的合并场景:新增一个完全自定义的 provider。

调用图:外部调用 3 个(assert_eq!, default, from)。

test_merge_configured_model_providers_applies_amazon_bedrock_profile_override333–361 ↗
fn test_merge_configured_model_providers_applies_amazon_bedrock_profile_override()

作用:检查用户可以覆盖内置 Amazon Bedrock 的 AWS profile 和 region。这样用户不用重写整个 Bedrock 配置,只改自己的 AWS 身份和区域就行。

数据流:进去的是一份配置表,键是 amazon-bedrock,值里只设置 aws.profile 和 aws.region。测试准备期望结果:内置 Bedrock 仍然存在,但 aws 字段换成用户给的值。出来时 merge_configured_model_providers 返回 Ok,并和期望结果相等。

调用关系:测试框架运行它;它用 default 和 HashMap::from 构造“只覆盖 AWS 字段”的配置,再交给合并函数。它验证 Bedrock 作为内置 provider 有特殊的、安全的覆盖规则。

调用图:外部调用 3 个(assert_eq!, default, from)。

test_merge_configured_model_providers_rejects_amazon_bedrock_non_default_fields364–387 ↗
fn test_merge_configured_model_providers_rejects_amazon_bedrock_non_default_fields()

作用:检查用户不能随便改内置 Amazon Bedrock 的其他字段,比如名字。这样可以防止一个看似叫 amazon-bedrock 的配置,其实已经变成别的服务,造成难以理解的问题。

数据流:进去的是一份 amazon-bedrock 覆盖配置,其中除了 aws.profile,还把 name 改成了 Custom Bedrock。测试调用 merge_configured_model_providers。出来时期望得到 Err,并且错误文字说明 Bedrock 只允许改 aws.profile 和 aws.region。

调用关系:测试框架运行它;它把非法覆盖交给正式合并函数,再用 assert_eq! 检查错误。它和允许覆盖 profile 的测试形成一正一反,明确 Bedrock 可改范围。

调用图:外部调用 3 个(assert_eq!, default, from)。

test_merge_configured_model_providers_allows_amazon_bedrock_default_fields390–410 ↗
fn test_merge_configured_model_providers_allows_amazon_bedrock_default_fields()

作用:检查用户给 Amazon Bedrock 写了一些等同默认值的字段时,不会被误判为非法修改。这样配置文件即使稍微啰嗦一点,也不会无故失败。

数据流:进去的是一份 amazon-bedrock 覆盖配置,aws 为空值,wire_api 也是默认 Responses。测试把它交给 merge_configured_model_providers。出来时要求结果仍是原始内置 provider 表,表示这些默认字段没有产生实际覆盖。

调用关系:测试框架运行它;它用 default 和 HashMap::from 构造输入,再调用合并函数。它补充了拒绝非法字段的测试,保证规则不会严到影响无害配置。

调用图:外部调用 3 个(assert_eq!, default, from)。

test_validate_provider_aws_rejects_conflicting_auth413–428 ↗
fn test_validate_provider_aws_rejects_conflicting_auth()

作用:检查启用 AWS 鉴权时,不能同时配置普通环境变量 API key 或 OpenAI 鉴权。两套身份系统混在一起,就像同时拿两张不同门禁卡刷同一扇门,容易发错凭证。

数据流:进去的是一个从 OpenAI 默认配置改出来的 provider,额外设置 aws,并保留 env_key。测试调用 provider.validate。出来时 assert_eq! 要求返回指定错误,说明 aws 不能和 env_key、requires_openai_auth 混用。

调用关系:测试框架运行它;它先调用 create_openai_provider 得到一个带常规鉴权特征的基础对象,再改成冲突状态,交给 validate 检查。它保护的是配置校验阶段的安全边界。

调用图:调用 1 个内部函数(create_openai_provider);外部调用 1 个(assert_eq!)。

test_validate_provider_aws_rejects_websockets431–446 ↗
fn test_validate_provider_aws_rejects_websockets()

作用:检查启用 AWS 鉴权时不能同时启用 WebSocket。也就是说,Bedrock 这类 AWS 鉴权路径目前不支持这种长连接方式,配置时要提前拦住。

数据流:进去的是一个 provider,设置了 aws,并把 supports_websockets 设为 true。测试调用 validate。出来时 assert_eq! 要求得到“aws 不能和 supports_websockets 混用”的错误。

调用关系:测试框架运行它;它用 create_openai_provider 造基础对象,再改出一个不被允许的组合,最后交给 validate。它和冲突鉴权测试一起确保 AWS provider 的限制清楚、提前报错。

调用图:调用 1 个内部函数(create_openai_provider);外部调用 1 个(assert_eq!)。

test_deserialize_provider_auth_config_allows_zero_refresh_interval449–467 ↗
fn test_deserialize_provider_auth_config_allows_zero_refresh_interval()

作用:检查自定义鉴权配置允许把刷新间隔设为 0。这里的 0 表示不自动按间隔刷新,而不是非法值。

数据流:进去的是临时目录和一段带 [auth] 的 TOML,其中 refresh_interval_ms = 0。测试设置路径基准后解析配置,取出 auth 字段。出来时确认保存的 refresh_interval_ms 是 0,并且 refresh_interval() 返回 None,表示没有定时刷新间隔。

调用关系:测试框架运行它;它调用 tempdir 建测试目录,调用 AbsolutePathBufGuard::new 设置相对路径环境,再用 from_str 解析。最后用 assert_eq! 检查数值和转换后的含义,补上鉴权刷新策略的边界情况。

调用图:调用 1 个内部函数(new);外部调用 3 个(assert_eq!, tempdir, from_str)。

models-manager/src/collaboration_mode_presets_tests.rs源码 ↗
testtest

这个文件不是产品运行时直接用的功能,而是一组测试。可以把它理解成“质检清单”:每次代码改动后,它会确认协作模式的预设还符合约定。第一项检查预设名称是不是直接用了模式自己的显示名,避免界面上出现一套名字、内部又是另一套名字。它还检查模型选择和推理强度这些默认值有没有被意外改掉。第二项检查默认模式的开发者说明文字,确认里面的占位符已经被替换成真实的模式名称列表,并且保留了关于什么时候可以向用户提问的关键规则。这类测试很重要,因为这些文字和默认值会影响用户看到什么、模型怎么行动;如果悄悄出错,可能不会马上崩溃,但行为会变得很难理解。

函数细节2
preset_names_use_mode_display_names5–15 ↗
fn preset_names_use_mode_display_names()

作用:这个测试确认几个协作模式预设的基础信息是对的。它主要防止有人修改预设时,把显示名称、模型默认值或推理强度弄错。

数据流:它读取计划模式预设和默认模式预设,再拿这些值去和模式本身的显示名、预期的空模型设置、预期的推理强度设置做比较。比较全部通过,就说明这些预设仍然符合约定;如果有一个值不一样,测试就会失败,提醒开发者这里被改坏了。

调用关系:这是测试流程里的一项独立检查。它直接调用被测代码里的 plan_preset、default_preset 和 ModeKind 的显示名能力,然后用 assert_eq! 这个测试断言工具逐项核对结果。

调用图:外部调用 1 个(assert_eq!)。

default_mode_instructions_replace_mode_names_placeholder18–36 ↗
fn default_mode_instructions_replace_mode_names_placeholder()

作用:这个测试确认默认模式里的说明文字已经把“模式名称占位符”换成了真实内容。它还确认几句关于如何向用户提问的重要规则没有丢。

数据流:它先从默认预设里取出开发者说明文字,然后检查里面不再包含 {{KNOWN_MODE_NAMES}} 这种模板占位符。接着它根据当前可见的协作模式生成一段应该出现的文字,再确认说明里真的包含这段话。最后它继续检查两句关键提示是否还在,确保模型不会随便使用不存在的提问工具,也知道必要时要用简短明白的问题直接问用户。

调用关系:这是针对默认模式说明文本的专项测试。它使用 default_preset 取得说明,使用 format_mode_names 和 TUI_VISIBLE_COLLABORATION_MODES 生成期望文字,再通过 assert! 判断这些文字是否满足要求;format! 只是用来拼出测试中期望看到的那一句话。

调用图:外部调用 2 个(assert!, format!)。

models-manager/src/test_support.rs源码 ↗
testtest setup

真实运行时,模型列表和模型详情可能来自远端接口或本地缓存。但测试最怕这种“外部状态”:今天网络好就过,明天接口变了就挂。这个文件就是测试用的“离线替身”。它提供两个小工具:一个用来决定测试该用哪个模型名;如果测试已经指定模型,就直接用指定的,否则从项目自带的模型列表里挑一个合适的默认模型。另一个用来构造 ModelInfo,也就是“某个模型的详细说明”。它不会去远端查,而是只看测试配置里已有的模型目录,再复用正式代码里的构造逻辑。这样测试既能贴近真实行为,又不会依赖网络、缓存或外部服务。文件顶部也明确说了:生产代码不应该依赖这里。

函数细节2
get_model_offline_for_tests12–25 ↗
fn get_model_offline_for_tests(model: Option<&str>) -> String

作用:这个函数给测试找一个模型编号,而且保证不联网、不查缓存。如果调用方已经给了模型名,它就原样返回;如果没给,它就从项目内置的模型列表里挑一个默认可用的。

数据流:输入是一个可选的模型名。若里面有值,函数立刻把它变成字符串返回。若没有值,函数读取项目打包自带的模型列表,把模型按优先级排序,再转换成测试容易使用的预设列表;它优先选择标记为“可以显示在选择器里”的模型,否则退而求其次选第一个;如果列表也没有,就返回空字符串。它不修改外部状态,也不访问远端。

调用关系:它是很多测试搭建会话时的第一步工具,例如 make_session_and_context、make_session_configuration_for_tests、session_telemetry 等测试辅助流程都会用它来确定“这次测试用哪个模型”。它内部只把获取内置模型列表这件事交给 bundled_models_response,之后自己完成排序和挑选。

调用图:被 38 处调用(make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, make_session_configuration_for_tests, make_session_with_config_and_rx, make_session_with_history_source_and_agent_control_and_rx, session_new_fails_when_zsh_fork_enabled_without_packaged_zsh, session_telemetry, set_rate_limits_retains_previous_credits, set_rate_limits_updates_plan_type_when_present, get_model_offline (+15 more));外部调用 1 个(bundled_models_response)。

construct_model_info_offline_for_tests28–38 ↗
fn construct_model_info_offline_for_tests(
    model: &str,
    config: &ModelsManagerConfig,
) -> ModelInfo

作用:这个函数给测试构造一个 ModelInfo,也就是模型的详细信息对象,并且同样不联网、不查缓存。它让测试可以像正式流程一样得到模型信息,但数据来源只限于测试配置。

数据流:输入是模型名和 ModelsManagerConfig 配置。函数先看配置里有没有 model_catalog,也就是一份本地模型目录;如果有,就取出里面的模型列表作为候选;如果没有,就使用空列表。然后它把模型名、候选列表和配置交给正式的构造函数,得到一个 ModelInfo 返回。它本身不保存数据,也不改配置。

调用关系:它常被创建测试会话的辅助代码调用,例如 make_session_and_context、make_session_with_config_and_rx、set_rate_limits_updates_plan_type_when_present 等。它的定位是测试里的“离线入口”,真正判断如何从候选模型里拼出 ModelInfo 的工作,则交给 construct_model_info_from_candidates,这样测试和正式逻辑能保持一致。

调用图:调用 1 个内部函数(construct_model_info_from_candidates);被 12 处调用(make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, make_session_configuration_for_tests, make_session_with_config_and_rx, make_session_with_history_source_and_agent_control_and_rx, session_new_fails_when_zsh_fork_enabled_without_packaged_zsh, set_rate_limits_retains_previous_credits, set_rate_limits_updates_plan_type_when_present, construct_model_info_offline, test_session_telemetry (+2 more))。

models-manager/src/model_info_tests.rs源码 ↗
testtest run

这个文件不负责真正运行模型,而是像验收清单一样,检查模型管理器里几个关键规则是否可靠。它主要测试 with_config_overrides 这类“把配置套到模型信息上”的行为:配置可以把“支持推理摘要”打开,但不能因为写了 false 就把模型本来支持的能力关掉;配置也可以改模型的上下文窗口,也就是一次能看多少内容,但不能超过模型声明的最大上限。每个测试都会先造一个模型,再造一份配置,然后比较更新后的结果是否和预期完全一样。这样以后有人改配置逻辑时,如果不小心让能力被误关,或让上下文窗口突破上限,测试就会立刻报错。

函数细节5
reasoning_summaries_override_true_enables_support6–18 ↗
fn reasoning_summaries_override_true_enables_support()

作用:这个测试确认:当配置明确写着“支持推理摘要”为 true 时,一个原本未知或默认不支持的模型会被标记为支持。推理摘要可以理解成模型对自己推理过程的简短说明。

数据流:进去的是一个由模型名字生成的默认模型信息,以及一份只设置了 model_supports_reasoning_summaries: Some(true) 的配置。测试把配置套到模型上,然后手动做出一份“应该得到的模型”,把 supports_reasoning_summaries 改成 true。最后用断言比较两边是否完全一致,不一致就说明覆盖规则坏了。

调用关系:它是测试运行器在跑单元测试时调用的小检查项。函数里使用默认配置补齐没关心的字段,并用 assert_eq! 做最终比对;它站在外部用户配置入口的角度,验证开启能力这个场景没有被写反。

调用图:外部调用 2 个(default, assert_eq!)。

reasoning_summaries_override_false_does_not_disable_support21–32 ↗
fn reasoning_summaries_override_false_does_not_disable_support()

作用:这个测试确认:如果模型本来就支持推理摘要,配置里写 false 也不能把这个能力关掉。它防止一个保守的配置值意外削弱模型能力。

数据流:进去的是一个先被手动标成 supports_reasoning_summaries 为 true 的模型,以及一份 model_supports_reasoning_summaries: Some(false) 的配置。测试套用配置后,期望结果仍然和原模型完全一样。最后通过断言确认没有任何字段被错误改动。

调用关系:它由测试框架自动执行,专门覆盖“配置想关,但模型本来支持”的情况。它借助默认配置生成其余字段,再把最终结果交给 assert_eq! 检查,用来保护覆盖逻辑不要把 false 当成强制关闭。

调用图:外部调用 2 个(default, assert_eq!)。

reasoning_summaries_override_false_is_noop_when_model_is_false35–45 ↗
fn reasoning_summaries_override_false_is_noop_when_model_is_false()

作用:这个测试确认:当模型本来不支持推理摘要,配置里也写 false 时,结果应该什么都不变。这里的 noop 意思是“不做任何改动”。

数据流:进去的是一个默认模型信息,通常 supports_reasoning_summaries 是 false,以及一份 model_supports_reasoning_summaries: Some(false) 的配置。测试套用配置后,不额外构造新状态,而是直接要求结果和原模型相同。出来的是一次相等断言,证明 false 配置在这个场景下只是保持原样。

调用关系:它在测试运行时作为边界情况被执行。前两个测试分别看“打开”和“不能关闭”,这个测试补上“本来就是关的也别乱动”,最后仍由 assert_eq! 判断结果。

调用图:外部调用 2 个(default, assert_eq!)。

model_context_window_override_clamps_to_max_context_window48–62 ↗
fn model_context_window_override_clamps_to_max_context_window()

作用:这个测试确认:用户配置的上下文窗口如果写得太大,系统会把它压到模型允许的最大值。上下文窗口就是模型一次能接收和参考的文字容量。

数据流:进去的是一个模型,它当前 context_window 是 273000,最大 max_context_window 是 400000;配置却要求 model_context_window 为 500000。测试套用配置后,手动准备预期结果:context_window 应该变成 400000,而不是 500000。最后断言两者相等,证明系统没有让配置突破模型上限。

调用关系:它由测试框架调用,用来验证配置覆盖里的安全护栏。函数使用默认配置填充其他不相关字段,再靠 assert_eq! 检查结果;它保护的是“用户可以调大,但不能超过机器承重”的规则,就像电梯限重不能因为乘客想多装就取消。

调用图:外部调用 2 个(default, assert_eq!)。

model_context_window_uses_model_value_without_override65–74 ↗
fn model_context_window_uses_model_value_without_override()

作用:这个测试确认:如果用户没有配置上下文窗口,系统应该继续使用模型自己带的数值。它防止默认配置无意中覆盖模型原始信息。

数据流:进去的是一个带有 context_window 273000 和 max_context_window 400000 的模型,以及一份完全默认、没有设置 model_context_window 的配置。测试套用配置后,要求结果和原模型完全一致。出来的是一次断言,证明没有配置时不会偷偷改上下文窗口。

调用关系:它是上下文窗口相关测试的另一个基础场景,由测试运行器执行。和前一个测试一起看:有覆盖时要受最大值限制,没有覆盖时就保持模型原值;最终仍通过 assert_eq! 给出是否通过的结果。

调用图:外部调用 2 个(assert_eq!, default)。

models-manager/src/manager_tests.rs源码 ↗
testtest

模型管理器要同时面对本地自带的模型清单、服务器返回的新模型、缓存文件、以及 ChatGPT 登录或 API Key 登录等不同身份。这个测试文件用假的模型接口和假的认证方式,把这些场景一个个搭出来,然后检查结果是否符合预期。比如:远端模型要按优先级排序;缓存新鲜时不该再请求网络;缓存过期或版本不对时要重新拉;ChatGPT 认证下远端清单可能是权威来源;API Key 或外部认证覆盖时又要避免误用 ChatGPT 专属刷新。文件里还有一些小工具函数,用来快速造假模型、造测试用管理器、写入假的 auth.json。整体作用就像给模型管理器装了一套“安全带”:真实用户看见的模型列表、默认模型、隐藏模型和兜底信息,都靠这些测试守住。

函数细节46
remote_model27–29 ↗
fn remote_model(slug: &str, display: &str, priority: i32) -> ModelInfo

作用:快速造一个测试用的远端模型,默认让它是可展示的模型。测试里不想每次都手写一大段模型资料时,就用它来省事。

数据流:输入模型的内部名字、显示名字和优先级 → 它把可见性固定为“list”,再交给更通用的造模型函数 → 输出一份完整的 ModelInfo 测试数据。

调用关系:它是多个测试的便捷入口,比如测试自定义模型清单、命名空间后缀匹配、静态管理器按认证模式过滤时都会用它;真正拼出完整模型字段的活儿交给 remote_model_with_visibility。

调用图:调用 1 个内部函数(remote_model_with_visibility);被 4 处调用(get_model_info_matches_hyphenated_provider_namespace_suffix, get_model_info_matches_namespaced_suffix, get_model_info_uses_custom_catalog, static_manager_reads_latest_auth_mode)。

remote_model_with_visibility31–62 ↗
fn remote_model_with_visibility(
    slug: &str,
    display: &str,
    priority: i32,
    visibility: &str,
) -> ModelInfo

作用:造一份字段齐全的假模型资料,并允许测试指定它是显示还是隐藏。这样测试可以专门验证“隐藏模型”“可见模型”等展示规则。

数据流:输入模型名字、显示名、优先级和可见性 → 它组装一段 JSON(通用数据格式),再反序列化成 ModelInfo → 输出一份像服务器返回的一样完整的模型对象。

调用关系:remote_model 会调用它来造普通可见模型;涉及隐藏模型合并、默认模型选择的测试会直接调用它,因为这些测试需要精确控制 visibility 字段。

调用图:被 3 处调用(build_available_models_picks_default_after_hiding_hidden_models, refresh_available_models_merges_hidden_only_chatgpt_remote_with_bundled_catalog, remote_model);外部调用 2 个(json!, from_value)。

assert_models_contain64–72 ↗
fn assert_models_contain(actual: &[ModelInfo], expected: &[ModelInfo])

作用:检查一个模型列表里是否包含预期的模型。它只看模型的 slug,也就是模型的唯一名字,避免测试被其他字段干扰。

数据流:输入实际模型列表和期望模型列表 → 它逐个拿期望模型的 slug 去实际列表里找 → 找不到就让测试失败,并提示缺了哪个模型。

调用关系:刷新、缓存、重新拉取等测试用它确认远端模型确实进入了管理器;它不改变数据,只负责给测试做清晰断言。

调用图:被 4 处调用(refresh_available_models_refetches_when_cache_stale, refresh_available_models_refetches_when_version_mismatch, refresh_available_models_sorts_by_priority, refresh_available_models_uses_cache_when_fresh);外部调用 1 个(assert!)。

TestModelsEndpoint::new83–90 ↗
fn new(responses: Vec<Vec<ModelInfo>>) -> Arc<Self>

作用:创建一个假的模型服务器接口,默认表现得像可以使用 Codex/ChatGPT 后端的接口。测试用它来模拟服务器按顺序返回模型列表。

数据流:输入一批预设响应,每个响应是一组模型 → 它把这些响应放进队列,并把请求次数计数器清零 → 输出一个可共享的假接口对象。

调用关系:大多数需要联网刷新假象的测试都会先用它造 endpoint,再交给 openai_manager_for_tests;之后管理器调用 list_models 时,会从这个队列里取下一份响应。

调用图:被 15 处调用(get_model_info_rejects_multi_segment_namespace_suffix_matching, get_model_info_tracks_fallback_usage, get_model_info_uses_fallback_for_bundled_models_when_chatgpt_remote_is_authoritative, refresh_available_models_drops_removed_remote_models, refresh_available_models_fetches_with_chatgpt_auth_tokens, refresh_available_models_merges_hidden_only_chatgpt_remote_with_bundled_catalog, refresh_available_models_preserves_bundled_catalog_for_empty_chatgpt_remote, refresh_available_models_refetches_when_cache_stale, refresh_available_models_refetches_when_version_mismatch, refresh_available_models_sorts_by_priority (+5 more));外部调用 3 个(new, new, new)。

TestModelsEndpoint::without_refresh92–99 ↗
fn without_refresh(responses: Vec<Vec<ModelInfo>>) -> Arc<Self>

作用:创建一个假的模型接口,但标记为不能走 Codex 后端刷新。它专门用于测试“没有合适认证时,不应该去请求网络”。

数据流:输入预设的模型响应 → 它保存响应队列,但把 uses_codex_backend 设为 false → 输出一个会被管理器判断为不可刷新远端模型的假接口。

调用关系:refresh_available_models_skips_network_without_chatgpt_auth 用它搭场景;管理器问它是否使用 Codex 后端时会得到否,从而跳过 list_models。

调用图:被 1 处调用(refresh_available_models_skips_network_without_chatgpt_auth);外部调用 3 个(new, new, new)。

TestModelsEndpoint::fetch_count101–103 ↗
fn fetch_count(&self) -> usize

作用:读取这个假接口被请求了多少次。测试用它确认缓存是否真的避免了重复联网,或者过期时是否真的重新请求。

数据流:没有业务输入,只读取内部原子计数器(线程安全的数字)→ 取出当前次数 → 返回一个普通整数。

调用关系:刷新相关测试在操作结束后会调用它做核对;计数本身是在 TestModelsEndpoint::list_models 被调用时增加的。

调用图:外部调用 1 个(load)。

TestExternalApiKeyAuth::auth_mode121–123 ↗
fn auth_mode(&self) -> AuthMode

作用:声明这个假的外部认证方式是 API Key。API Key 可以理解成一把固定的访问钥匙,不是 ChatGPT 登录令牌。

数据流:不读取外部输入 → 直接返回 AuthMode::ApiKey → 告诉认证管理器当前外部认证类型。

调用关系:当测试把 TestExternalApiKeyAuth 装进 AuthManager 后,模型管理器会通过认证状态判断是否应该跳过 ChatGPT 专属的模型刷新。

TestExternalApiKeyAuth::resolve125–131 ↗
fn resolve(&self) -> codex_login::ExternalAuthFuture<'_, Option<ExternalAuthTokens>>

作用:模拟外部 API Key 认证已经成功解析出来。它让测试验证:一旦外部 API Key 生效,就应覆盖原来的 ChatGPT 登录。

数据流:不需要输入 → 异步返回一个只含 access token 的 ExternalAuthTokens → 调用方得到一把测试用的外部访问钥匙。

调用关系:AuthManager 在判断当前有效认证时会用到它;相关测试借此确认管理器不会再用 ChatGPT 身份去拉远端模型。

调用图:调用 1 个内部函数(access_token_only);外部调用 1 个(pin)。

TestExternalApiKeyAuth::refresh133–142 ↗
fn refresh(
        &self,
        _context: ExternalAuthRefreshContext,
    ) -> codex_login::ExternalAuthFuture<'_, ExternalAuthTokens>

作用:模拟外部 API Key 认证刷新成功。虽然测试令牌是固定的,但它满足真实认证接口要求。

数据流:输入刷新上下文但不使用 → 异步返回同一个测试 access token → 调用方看到刷新成功。

调用关系:它和 resolve 一起组成完整的假外部认证;如果认证管理器需要刷新外部凭据,会走到这里。

调用图:调用 1 个内部函数(access_token_only);外部调用 1 个(pin)。

TestUnresolvedExternalApiKeyAuth::auth_mode149–151 ↗
fn auth_mode(&self) -> AuthMode

作用:声明这个假的外部认证也声称自己是 API Key。不同点是它后面刷新会失败,用来测试兜底行为。

数据流:不读取输入 → 直接返回 AuthMode::ApiKey → 告诉认证系统这是 API Key 类型。

调用关系:refresh_available_models_uses_cached_chatgpt_when_external_api_key_is_unresolved 会安装它;后续因为认证解析失败,管理器应退回使用已有 ChatGPT 认证。

TestUnresolvedExternalApiKeyAuth::refresh153–158 ↗
fn refresh(
        &self,
        _context: ExternalAuthRefreshContext,
    ) -> codex_login::ExternalAuthFuture<'_, ExternalAuthTokens>

作用:模拟外部 API Key 认证无法解析或刷新。它故意报错,用来确认系统会不会安全地退回到缓存的 ChatGPT 登录。

数据流:输入刷新上下文但不使用 → 异步返回一个错误 → 调用方知道外部认证不可用。

调用关系:AuthManager 尝试使用外部认证时会遇到这个失败;相关测试随后检查模型刷新是否改用已有 ChatGPT 认证继续完成。

调用图:外部调用 2 个(pin, other)。

TestModelsEndpoint::has_command_auth162–164 ↗
fn has_command_auth(&self) -> bool

作用:告诉模型管理器,这个假接口是否带有命令行传入的认证。它用于区分 API Key 风格和 ChatGPT 风格的刷新规则。

数据流:读取对象里的 has_command_auth 布尔值 → 不做转换 → 返回给调用方。

调用关系:模型管理器在决定远端清单怎么和内置清单合并时会问这个问题;部分测试手动把它设为 true 来模拟 API Key 认证。

TestModelsEndpoint::uses_codex_backend166–168 ↗
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool>

作用:告诉模型管理器,这个假接口是否走 Codex/ChatGPT 后端。只有走这个后端时,某些远端模型刷新才有意义。

数据流:读取对象里的 uses_codex_backend → 包成异步结果 → 返回 true 或 false。

调用关系:刷新流程会先问它是否能使用后端;without_refresh 创建的接口会让这里返回 false,从而测试跳过网络请求的分支。

调用图:外部调用 1 个(pin)。

TestModelsEndpoint::list_models170–175 ↗
fn list_models(
        &'a self,
        _client_version: &'a str,
    ) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>>

作用:模拟向服务器索取模型列表。每调用一次,它就从预设队列里拿出下一批模型,并记录请求次数。

数据流:输入客户端版本但测试里忽略 → 请求次数加一,从响应队列弹出一组模型,没有就返回空列表 → 输出模型列表和空的附加信息。

调用关系:OpenAiModelsManager 刷新远端模型时会调用它;缓存命中时不应调用它,所以很多测试用 fetch_count 来验证它有没有被触发。

调用图:外部调用 2 个(fetch_add, pin)。

openai_manager_for_tests178–189 ↗
fn openai_manager_for_tests(
    codex_home: std::path::PathBuf,
    endpoint_client: Arc<dyn ModelsEndpointClient>,
) -> OpenAiModelsManager

作用:创建一个带默认假 ChatGPT 认证的 OpenAI 模型管理器。测试里用它快速得到“像已登录用户一样”的管理器。

数据流:输入临时的 codex_home 路径和假 endpoint → 它创建测试用 ChatGPT 认证管理器 → 交给 openai_manager_for_tests_with_auth 生成真正的管理器。

调用关系:大量测试通过它搭建标准环境;它把认证细节藏起来,让测试重点放在模型刷新和缓存行为上。

调用图:调用 3 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, openai_manager_for_tests_with_auth);被 12 处调用(get_model_info_rejects_multi_segment_namespace_suffix_matching, get_model_info_tracks_fallback_usage, get_model_info_uses_fallback_for_bundled_models_when_chatgpt_remote_is_authoritative, refresh_available_models_drops_removed_remote_models, refresh_available_models_merges_hidden_only_chatgpt_remote_with_bundled_catalog, refresh_available_models_preserves_bundled_catalog_for_empty_chatgpt_remote, refresh_available_models_refetches_when_cache_stale, refresh_available_models_refetches_when_version_mismatch, refresh_available_models_sorts_by_priority, refresh_available_models_uses_cache_when_fresh (+2 more))。

openai_manager_for_tests_with_auth191–197 ↗
fn openai_manager_for_tests_with_auth(
    codex_home: std::path::PathBuf,
    endpoint_client: Arc<dyn ModelsEndpointClient>,
    auth_manager: Option<Arc<AuthManager>>,
) -> OpenAiModelsManager

作用:创建一个可自定义认证状态的 OpenAI 模型管理器。需要测试无认证、API Key、ChatGPT token 等场景时会用它。

数据流:输入 codex_home、endpoint 和可选的 AuthManager → 直接调用 OpenAiModelsManager::new → 输出测试用管理器。

调用关系:openai_manager_for_tests 会调用它;各种认证边界测试也直接调用它,把不同 AuthManager 塞进模型管理器。

调用图:调用 1 个内部函数(new);被 6 处调用(openai_manager_for_tests, refresh_available_models_fetches_with_chatgpt_auth_tokens, refresh_available_models_keeps_merging_for_api_auth, refresh_available_models_skips_network_when_external_api_key_overrides_chatgpt_auth, refresh_available_models_skips_network_without_chatgpt_auth, refresh_available_models_uses_cached_chatgpt_when_external_api_key_is_unresolved)。

static_manager_for_tests199–201 ↗
fn static_manager_for_tests(model_catalog: ModelsResponse) -> StaticModelsManager

作用:创建一个只使用给定静态模型清单的管理器。它适合测试模型匹配、展示列表构建这类不需要真实远端刷新和缓存的逻辑。

数据流:输入一个 ModelsResponse,也就是一包模型清单 → 用空认证创建 StaticModelsManager → 输出静态模型管理器。

调用关系:自定义目录、命名空间匹配、隐藏模型默认值等测试会用它;它绕开网络,让测试更直接。

调用图:调用 1 个内部函数(new);被 4 处调用(build_available_models_picks_default_after_hiding_hidden_models, get_model_info_matches_hyphenated_provider_namespace_suffix, get_model_info_matches_namespaced_suffix, get_model_info_uses_custom_catalog)。

chatgpt_auth_tokens_for_tests203–239 ↗
async fn chatgpt_auth_tokens_for_tests(codex_home: &Path) -> CodexAuth

作用:在临时目录里写入一份假的 ChatGPT 登录文件,并把它读成 CodexAuth。它用于测试真实 token 文件加载路径,而不只是内存里的假认证。

数据流:输入 codex_home 路径 → 构造假的 auth.json,里面有 JWT、access token、refresh token 等字段,写到磁盘 → 再按正常认证加载流程读回来,输出 CodexAuth。

调用关系:refresh_available_models_fetches_with_chatgpt_auth_tokens 调用它;这样测试能覆盖“从文件里加载 ChatGPT token 后可以刷新模型”的场景。

调用图:调用 3 个内部函数(default, from_auth_storage, parse_chatgpt_jwt_claims);被 1 处调用(refresh_available_models_fetches_with_chatgpt_auth_tokens);外部调用 5 个(join, now, to_string, create_dir_all, write)。

get_model_info_tracks_fallback_usage242–266 ↗
async fn get_model_info_tracks_fallback_usage()

作用:测试查询模型信息时,系统能不能标记自己是否用了兜底资料。已知模型不该标记兜底,未知模型应该标记。

数据流:创建默认配置、临时目录和假 endpoint → 先取一个内置已知模型再查询,再查询一个不存在的模型 → 检查返回 slug 和 used_fallback_model_metadata 标记。

调用关系:它使用 TestModelsEndpoint::new 和 openai_manager_for_tests 搭环境;主要覆盖 get_model_info 面对已知与未知模型时的差异。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 5 个(new, assert!, assert_eq!, default, tempdir)。

get_model_info_uses_custom_catalog269–288 ↗
async fn get_model_info_uses_custom_catalog()

作用:测试静态自定义模型清单能作为模型信息来源。即使查询的名字带实验后缀,也应套用匹配模型的能力信息。

数据流:造一个名为 gpt-overlay 的模型并打开图片原始细节支持 → 放进静态管理器 → 查询 gpt-overlay-experiment,检查显示名、上下文窗口和能力字段来自自定义清单。

调用关系:它调用 remote_model 和 static_manager_for_tests;验证 StaticModelsManager 的模型匹配不是只会做完全相等。

调用图:调用 2 个内部函数(remote_model, static_manager_for_tests);外部调用 4 个(assert!, assert_eq!, default, vec!)。

get_model_info_matches_namespaced_suffix291–305 ↗
async fn get_model_info_matches_namespaced_suffix()

作用:测试带命名空间的模型名也能匹配到已知模型。比如 custom/gpt-image 应该继承 gpt-image 的能力信息。

数据流:造一个支持图片原始细节的 gpt-image → 放进静态管理器 → 查询 custom/gpt-image → 返回结果保留原始查询名,但能力字段来自 gpt-image。

调用关系:它用 remote_model 和 static_manager_for_tests 搭清单;覆盖 get_model_info 的“单段命名空间后缀匹配”规则。

调用图:调用 2 个内部函数(remote_model, static_manager_for_tests);外部调用 4 个(assert!, assert_eq!, default, vec!)。

get_model_info_matches_hyphenated_provider_namespace_suffix308–320 ↗
async fn get_model_info_matches_hyphenated_provider_namespace_suffix()

作用:测试供应商命名空间里带连字符时也能匹配后缀。比如 openai-codex/gpt-image 不应该因为 openai-codex 里有横线就失败。

数据流:造一个 gpt-image 模型 → 放进静态管理器 → 查询 openai-codex/gpt-image → 检查返回的 slug 是查询原名,且没有走兜底。

调用关系:它继续使用 remote_model 和 static_manager_for_tests;专门防止命名空间解析规则过于死板。

调用图:调用 2 个内部函数(remote_model, static_manager_for_tests);外部调用 4 个(assert!, assert_eq!, default, vec!)。

get_model_info_rejects_multi_segment_namespace_suffix_matching323–343 ↗
async fn get_model_info_rejects_multi_segment_namespace_suffix_matching()

作用:测试多层命名空间不能随便按最后一段匹配。这样可以避免 ns1/ns2/known-model 被误认为就是 known-model。

数据流:创建默认管理器,取一个内置已知模型名 → 拼成 ns1/ns2/已知名 → 查询模型信息 → 检查系统保留这个完整名字,并标记使用兜底资料。

调用关系:它用 openai_manager_for_tests 搭普通环境;和前两个命名空间测试形成对照,说明只允许特定的简单后缀匹配。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 6 个(new, assert!, assert_eq!, format!, default, tempdir)。

refresh_available_models_sorts_by_priority346–376 ↗
async fn refresh_available_models_sorts_by_priority()

作用:测试刷新后的模型列表会按优先级排序。优先级高的模型应该更靠前,方便用户先看到推荐项。

数据流:准备两个远端模型,一个优先级高一个低 → 刷新模型并读取列表 → 检查两者都在缓存里,且高优先级排在低优先级前,同时只请求一次 endpoint。

调用关系:它调用 TestModelsEndpoint::new、openai_manager_for_tests 和 assert_models_contain;覆盖刷新、缓存写入和展示排序的组合行为。

调用图:调用 3 个内部函数(new, assert_models_contain, openai_manager_for_tests);外部调用 4 个(assert!, assert_eq!, tempdir, vec!)。

refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth379–396 ↗
async fn refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth()

作用:测试 ChatGPT 认证下,非空远端模型清单会成为主要来源。也就是说远端返回的可见模型可以代表当前可用模型集合。

数据流:准备一个远端模型 → 用默认 ChatGPT 测试认证刷新 → 读取 remote models → 检查结果正好等于远端返回,并且只请求一次网络。

调用关系:它用 TestModelsEndpoint::new 和 openai_manager_for_tests;验证 ChatGPT 后端场景下远端清单的权威性。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_uses_cached_remote_only_catalog_for_chatgpt_auth399–430 ↗
async fn refresh_available_models_uses_cached_remote_only_catalog_for_chatgpt_auth()

作用:测试 ChatGPT 认证下,如果远端清单已经缓存且还新鲜,新的管理器也应该直接读缓存,不再联网。

数据流:第一个管理器拉取并写入缓存 → 第二个管理器使用同一个 codex_home,但 endpoint 没有响应 → 第二次刷新应从缓存恢复同样模型,且请求次数为零。

调用关系:它两次使用 openai_manager_for_tests 和 TestModelsEndpoint::new;覆盖跨管理器实例共享磁盘缓存的行为。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 4 个(new, assert_eq!, tempdir, vec!)。

get_model_info_uses_fallback_for_bundled_models_when_chatgpt_remote_is_authoritative433–460 ↗
async fn get_model_info_uses_fallback_for_bundled_models_when_chatgpt_remote_is_authoritative()

作用:测试当 ChatGPT 远端清单是权威来源时,内置模型不在远端清单里就不能被当成完整远端模型。查询它时应走兜底资料。

数据流:让远端只返回一个测试模型 → 刷新后再查询一个内置模型 slug → 返回结果保留 slug,但标记用了 fallback。

调用关系:它调用 openai_manager_for_tests 和 TestModelsEndpoint::new;保护“远端权威”模式不会悄悄混入内置可见模型。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 5 个(assert!, assert_eq!, default, tempdir, vec!)。

refresh_available_models_preserves_bundled_catalog_for_empty_chatgpt_remote463–475 ↗
async fn refresh_available_models_preserves_bundled_catalog_for_empty_chatgpt_remote()

作用:测试 ChatGPT 远端返回空列表时,不要把内置模型清单清空。这样服务器异常或暂时没有数据时,用户仍有基本模型可用。

数据流:endpoint 返回空模型列表 → 管理器刷新 → 读取 remote models → 检查它仍等于打包在程序里的内置模型清单。

调用关系:它用 TestModelsEndpoint::new 和 openai_manager_for_tests;覆盖远端空响应的安全兜底行为。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_merges_hidden_only_chatgpt_remote_with_bundled_catalog478–497 ↗
async fn refresh_available_models_merges_hidden_only_chatgpt_remote_with_bundled_catalog()

作用:测试如果 ChatGPT 远端只返回隐藏模型,系统会保留内置清单并把隐藏模型补进去。隐藏模型不该把正常可见清单挤掉。

数据流:造一个 visibility 为 hide 的远端模型 → 期望结果是内置模型加这个隐藏模型 → 刷新后检查 remote models 正好符合期望。

调用关系:它直接调用 remote_model_with_visibility 构造隐藏模型,再用 openai_manager_for_tests 执行刷新;覆盖隐藏模型合并规则。

调用图:调用 3 个内部函数(new, openai_manager_for_tests, remote_model_with_visibility);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_keeps_merging_for_api_auth500–530 ↗
async fn refresh_available_models_keeps_merging_for_api_auth()

作用:测试 API Key 认证下,远端模型仍然是和内置模型合并,而不是完全替代内置清单。API Key 模式和 ChatGPT 权威模式规则不同。

数据流:准备一个远端 API 模型,并把 endpoint 标成 command auth、非 Codex 后端 → 用 API Key 认证创建管理器并刷新 → 检查结果是内置清单加远端模型,且请求一次。

调用关系:它直接用 openai_manager_for_tests_with_auth 塞入 API Key 认证;验证不同认证模式影响清单合并策略。

调用图:调用 3 个内部函数(from_auth_for_testing, from_api_key, openai_manager_for_tests_with_auth);外部调用 6 个(new, new, new, assert_eq!, tempdir, vec!)。

refresh_available_models_uses_cache_when_fresh533–556 ↗
async fn refresh_available_models_uses_cache_when_fresh()

作用:测试缓存还新鲜时,第二次刷新不会再请求远端。这样能减少网络请求,也避免用户体验变慢。

数据流:第一次刷新拿到远端模型并写缓存 → 第二次用同一管理器刷新 → 检查模型仍在,并且 endpoint 请求次数还是一次。

调用关系:它调用 assert_models_contain 检查模型存在;主要覆盖 RefreshStrategy::OnlineIfUncached 下的缓存命中分支。

调用图:调用 3 个内部函数(new, assert_models_contain, openai_manager_for_tests);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_refetches_when_cache_stale559–590 ↗
async fn refresh_available_models_refetches_when_cache_stale()

作用:测试缓存过期后会重新拉取模型。过期缓存不能一直挡住服务器上的新模型。

数据流:第一次刷新得到旧模型 → 手动把缓存时间改成一小时前 → 第二次刷新从 endpoint 取新模型 → 检查新模型进入列表,请求次数变成两次。

调用关系:它用 manager.cache_manager 的测试钩子修改时间;随后刷新流程应判断缓存陈旧并再次调用 endpoint。

调用图:调用 3 个内部函数(new, assert_models_contain, openai_manager_for_tests);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_refetches_when_version_mismatch593–624 ↗
async fn refresh_available_models_refetches_when_version_mismatch()

作用:测试缓存里的客户端版本和当前程序版本不一致时,会重新请求远端。这样老版本缓存不会污染新版本的模型能力判断。

数据流:第一次刷新写入缓存 → 手动把缓存里的 client_version 改成带 mismatch 的字符串 → 第二次刷新 → 检查拿到新模型,且请求次数为两次。

调用关系:它通过 cache_manager 的测试修改入口制造版本不匹配;刷新逻辑发现版本不对后会走重新拉取分支。

调用图:调用 3 个内部函数(new, assert_models_contain, openai_manager_for_tests);外部调用 3 个(assert_eq!, tempdir, vec!)。

refresh_available_models_drops_removed_remote_models627–669 ↗
async fn refresh_available_models_drops_removed_remote_models()

作用:测试远端已经移除的模型不会一直留在可用列表里。否则用户可能会看到已经不能用的旧模型。

数据流:endpoint 第一次返回 remote-old,第二次返回 remote-new → 把缓存有效期设为零强制每次都刷新 → 最后检查列表里有新模型、没有旧模型,请求两次。

调用关系:它用 TestModelsEndpoint::new 提供两轮响应,并通过 try_list_models 查看当前可展示列表;覆盖刷新替换旧远端结果的行为。

调用图:调用 2 个内部函数(new, openai_manager_for_tests);外部调用 4 个(assert!, assert_eq!, tempdir, vec!)。

refresh_available_models_skips_network_without_chatgpt_auth672–702 ↗
async fn refresh_available_models_skips_network_without_chatgpt_auth()

作用:测试没有 ChatGPT 认证且 endpoint 也不支持刷新时,即使要求在线刷新,也不应该请求网络。

数据流:创建一个 without_refresh 的 endpoint,里面藏着一个远端模型 → 用无 AuthManager 的管理器刷新 → 检查这个模型没有进入缓存,请求次数为零。

调用关系:它调用 TestModelsEndpoint::without_refresh 和 openai_manager_for_tests_with_auth;覆盖无认证时的安全跳过逻辑。

调用图:调用 2 个内部函数(without_refresh, openai_manager_for_tests_with_auth);外部调用 4 个(assert!, assert_eq!, tempdir, vec!)。

TestAuthAwareModelsEndpoint::new712–718 ↗
fn new(auth_manager: Option<Arc<AuthManager>>, responses: Vec<Vec<ModelInfo>>) -> Arc<Self>

作用:创建一个会根据 AuthManager 当前状态判断后端类型的假 endpoint。它比 TestModelsEndpoint 更贴近认证变化场景。

数据流:输入可选 AuthManager 和预设响应队列 → 保存认证引用、响应队列和请求计数器 → 输出可共享的认证感知 endpoint。

调用关系:外部 API Key 覆盖 ChatGPT、外部 API Key 解析失败这两个测试会用它;它让 endpoint 的 uses_codex_backend 结果随着认证状态变化。

调用图:被 2 处调用(refresh_available_models_skips_network_when_external_api_key_overrides_chatgpt_auth, refresh_available_models_uses_cached_chatgpt_when_external_api_key_is_unresolved);外部调用 3 个(new, new, new)。

TestAuthAwareModelsEndpoint::fetch_count720–722 ↗
fn fetch_count(&self) -> usize

作用:读取认证感知假 endpoint 被拉取模型的次数。测试用它判断认证切换后到底有没有发生网络请求。

数据流:读取内部线程安全计数器 → 返回当前数字 → 不修改任何模型数据。

调用关系:相关认证测试在刷新后调用它;计数由 TestAuthAwareModelsEndpoint::list_models 在真正被请求时增加。

调用图:外部调用 1 个(load)。

TestAuthAwareModelsEndpoint::has_command_auth748–750 ↗
fn has_command_auth(&self) -> bool

作用:告诉模型管理器,这个认证感知 endpoint 没有命令行认证。它把测试重点留给 AuthManager 的当前认证状态。

数据流:不读取外部输入 → 固定返回 false → 调用方知道不要按 command auth 处理。

调用关系:模型管理器刷新时会查询这个 trait 方法;在这些外部认证测试里,是否能刷新主要由 uses_codex_backend 决定。

TestAuthAwareModelsEndpoint::uses_codex_backend752–754 ↗
fn uses_codex_backend(&self) -> ModelsEndpointFuture<'_, bool>

作用:异步告诉模型管理器当前是否还能走 Codex/ChatGPT 后端。它会看 AuthManager 里实际生效的认证,而不是固定写死。

数据流:读取 AuthManager 当前 auth → 判断它是否使用 Codex 后端 → 把结果包装成异步返回值。

调用关系:外部 API Key 生效时这里应返回 false,使刷新跳过;外部 API Key 解析失败、退回 ChatGPT 时这里应返回 true,让刷新继续。

调用图:外部调用 1 个(pin)。

TestAuthAwareModelsEndpoint::list_models756–761 ↗
fn list_models(
        &'a self,
        _client_version: &'a str,
    ) -> ModelsEndpointFuture<'a, CoreResult<(Vec<ModelInfo>, Option<String>)>>

作用:模拟认证感知 endpoint 的模型拉取。只有刷新流程真的决定联网时,它才会被调用并弹出一批预设模型。

数据流:输入客户端版本但忽略 → 请求计数加一,从队列取下一批模型,没有则空列表 → 返回模型列表和空附加信息。

调用关系:认证相关刷新测试通过 fetch_count 判断它是否被调用;它是验证“跳过网络”还是“实际拉取”的关键观察点。

调用图:外部调用 2 个(fetch_add, pin)。

refresh_available_models_skips_network_when_external_api_key_overrides_chatgpt_auth765–802 ↗
async fn refresh_available_models_skips_network_when_external_api_key_overrides_chatgpt_auth()

作用:测试外部 API Key 认证覆盖 ChatGPT 登录后,模型管理器不应该再用 ChatGPT 后端刷新模型。

数据流:先创建默认 ChatGPT 认证,再设置一个可解析的外部 API Key → endpoint 里放一个只会在联网时出现的模型 → 刷新后检查该模型没进缓存,请求次数为零。

调用关系:它使用 TestExternalApiKeyAuth、TestAuthAwareModelsEndpoint::new 和 openai_manager_for_tests_with_auth;覆盖外部认证优先级高于 ChatGPT 的场景。

调用图:调用 4 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, new, openai_manager_for_tests_with_auth);外部调用 6 个(clone, new, assert!, assert_eq!, tempdir, vec!)。

refresh_available_models_uses_cached_chatgpt_when_external_api_key_is_unresolved805–843 ↗
async fn refresh_available_models_uses_cached_chatgpt_when_external_api_key_is_unresolved()

作用:测试外部 API Key 认证不可用时,系统可以退回已有 ChatGPT 认证并继续刷新模型。

数据流:创建 ChatGPT 认证,再设置一个刷新会报错的外部 API Key → endpoint 准备一个远端模型 → 刷新后检查该模型进入缓存,请求次数为一次。

调用关系:它使用 TestUnresolvedExternalApiKeyAuth 和认证感知 endpoint;和前一个测试形成对照,证明只有外部认证真正生效时才跳过 ChatGPT 刷新。

调用图:调用 4 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, new, openai_manager_for_tests_with_auth);外部调用 6 个(clone, new, assert!, assert_eq!, tempdir, vec!)。

refresh_available_models_fetches_with_chatgpt_auth_tokens846–879 ↗
async fn refresh_available_models_fetches_with_chatgpt_auth_tokens()

作用:测试从 auth.json 文件加载出的 ChatGPT token 也能用于模型刷新。它覆盖真实文件认证路径,而不是只测内存假对象。

数据流:在临时目录写入并加载测试 ChatGPT token → 用这个认证创建管理器 → 在线刷新 → 检查远端模型进入缓存,endpoint 请求一次。

调用关系:它调用 chatgpt_auth_tokens_for_tests 准备认证;然后通过 openai_manager_for_tests_with_auth 进入正常刷新流程。

调用图:调用 4 个内部函数(from_auth_for_testing, new, chatgpt_auth_tokens_for_tests, openai_manager_for_tests_with_auth);外部调用 4 个(assert!, assert_eq!, tempdir, vec!)。

build_available_models_picks_default_after_hiding_hidden_models882–897 ↗
fn build_available_models_picks_default_after_hiding_hidden_models()

作用:测试构建可用模型列表时,隐藏模型不会被选成默认模型;默认应落到可见模型上。

数据流:造一个隐藏模型和一个可见模型 → 调用 build_available_models → 期望隐藏模型仍在列表里,但可见模型被标记为默认。

调用关系:它使用 remote_model_with_visibility 精确控制可见性,再用 static_manager_for_tests 调用列表构建逻辑;覆盖默认模型选择规则。

调用图:调用 3 个内部函数(remote_model_with_visibility, static_manager_for_tests, from);外部调用 3 个(new, assert_eq!, vec!)。

static_manager_reads_latest_auth_mode900–935 ↗
async fn static_manager_reads_latest_auth_mode()

作用:测试静态模型管理器每次列模型时都会读取最新认证模式。认证从 ChatGPT 变成 API Key 后,模型过滤结果也应跟着变。

数据流:准备一个只支持 ChatGPT 的模型和一个 API 可用模型 → 初始 ChatGPT 认证下列出两个 → 设置外部 API Key 后再列 → 只剩 API 可用模型。

调用关系:它用 AuthManager 的可变外部认证和 StaticModelsManager;验证静态管理器不是创建时只看一次认证,而是运行时读取最新状态。

调用图:调用 4 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, new, remote_model);外部调用 4 个(clone, new, assert_eq!, vec!)。

bundled_models_json_roundtrips938–955 ↗
fn bundled_models_json_roundtrips()

作用:测试程序自带的 models.json 能正确读取、再写回 JSON、再读回来,并且内容不变。这样可以保证打包的模型清单格式是健康的。

数据流:读取内置 models.json 得到 ModelsResponse → 序列化成字符串,再反序列化回来 → 检查前后完全相等,并确认模型列表不是空的。

调用关系:它直接调用 bundled_models_response 和 serde JSON 工具;这是对内置数据文件格式的基础健康检查。

调用图:外部调用 5 个(assert!, assert_eq!, bundled_models_response, from_str, to_string)。

models-manager/src/model_info_overrides_tests.rs源码 ↗
testtest run

这个文件是测试代码,不是正式业务入口。它像两张验收清单,专门检查模型管理器在没有从网络拿到模型资料时,会怎么给模型生成一份“离线默认信息”。重点是 truncation policy,也就是“内容太长时怎么截断”的规则。第一个测试不设置额外配置,确认系统会使用默认的 10,000 字节限制;字节可以理解成文本占用空间的大小。第二个测试设置 tool_output_token_limit 为 123,确认系统改用 123 个 token 的限制;token 可以粗略理解成模型读文本时切成的小块。每个测试都会建一个临时目录,造一个假的模型接口,避免真的访问网络,然后调用 manager.get_model_info 来看结果是不是符合预期。

函数细节2
offline_model_info_without_tool_output_override11–25 ↗
async fn offline_model_info_without_tool_output_override()

作用:这个测试确认:如果用户没有特别设置工具输出限制,模型管理器会使用默认的离线截断规则,也就是最多 10,000 字节。它用来防止默认行为被无意改坏。

数据流:进去的是一个临时的 codex 主目录、默认配置 ModelsManagerConfig,以及一个没有任何返回数据的假模型接口。函数用这些东西创建测试用的 OpenAI 模型管理器,然后请求 gpt-5.2 的模型信息。出来的是一份 model_info,测试会检查其中的 truncation_policy 是否等于“按 10,000 字节截断”;它不会改真实文件或访问真实网络。

调用关系:这个函数由测试框架 tokio 在跑测试时自动启动。它先用 TempDir::new 准备隔离环境,用 ModelsManagerConfig::default 做默认配置,再通过 openai_manager_for_tests 组装一个测试版管理器,最后把核心检查交给 manager.get_model_info,并用 assert_eq! 判断结果是否正确。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert_eq!, default, openai_manager_for_tests)。

offline_model_info_with_tool_output_override28–45 ↗
async fn offline_model_info_with_tool_output_override()

作用:这个测试确认:如果用户配置了 tool_output_token_limit,模型管理器会尊重这个设置,用 token 数量来限制工具输出,而不是继续使用默认字节限制。

数据流:进去的是一个临时目录、一份带有 tool_output_token_limit: Some(123) 的配置,以及一个空的假模型接口。函数创建测试用管理器后,请求 gpt-5.4 的模型信息。出来的是 model_info,测试会检查它的 truncation_policy 是否变成“按 123 个 token 截断”;也就是说,配置里的 123 被真正传进了模型信息结果。

调用关系:这个函数同样由 tokio 测试框架自动运行。它的流程和前一个测试很像,但在创建配置时覆盖了默认值。随后它调用 openai_manager_for_tests 搭好测试环境,再调用 manager.get_model_info 触发离线模型信息生成,最后用 assert_eq! 验证这条配置覆盖链路没有断。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, new, new, assert_eq!, openai_manager_for_tests)。

core/tests/suite/models_cache_ttl.rs源码 ↗
testtest run

Codex 会把服务器返回的可用模型列表存在本地文件里,就像把菜单拍照存在手机里,下次不用每次都问店员。但这个缓存有几个坑:缓存太旧怎么办?服务器说内容没变怎么办?客户端版本升级后,旧缓存还能不能信?这个测试文件就围绕这些问题造出假服务器、假模型和假缓存文件,然后观察程序的选择。它会验证:如果一次对话响应里带回的模型 ETag(一种“内容指纹”,用来判断内容有没有变)和缓存一致,就只更新缓存时间,不重新请求 /models;如果缓存里记录的客户端版本和当前版本一致,就直接用缓存;如果版本缺失或不同,就必须重新向服务器拉模型列表。文件里还提供了读写缓存 JSON 文件的小工具,以及构造测试用模型信息的函数,方便每个测试准备同样格式的数据。

函数细节9
renews_cache_ttl_on_matching_models_etag47–148 ↗
async fn renews_cache_ttl_on_matching_models_etag() -> Result<()>

作用:这个测试确认:当服务器在普通响应里告诉客户端“模型列表指纹没变”时,本地模型缓存的有效期会被延长,而且不会额外再请求一次 /models。这能避免网络浪费,同时保证离线时还能继续用这份缓存。

数据流:它先启动一个假服务器,准备一个远端模型列表和固定的 ETag。然后让 Codex 第一次联网拉模型,生成缓存文件;接着故意把缓存时间改成很久以前,模拟“过期缓存”。之后它发起一次普通用户输入,假服务器在响应头里带上同一个 ETag。测试最后读取缓存文件,检查缓存时间已经变新,同时确认 /models 只被请求过一次,并且离线列模型时还能看到之前的远端模型。

调用关系:这是整个文件里最完整的一条场景测试。它用 test_remote_model 造测试模型,用 rewrite_cache_timestamp 故意改旧缓存;rewrite_cache_timestamp 又通过 read_cache 和 write_cache 完成文件修改。它还借助测试框架创建 Codex、挂载假响应、等待 TurnComplete 事件,最后用 read_cache 验证缓存确实被续期。

调用图:调用 11 个内部函数(mount_models_once_with_etag, mount_response_once, sse, sse_response, local_selections, test_codex, turn_permission_fields, read_cache, rewrite_cache_timestamp, test_remote_model (+1 more));外部调用 7 个(clone, default, start, assert!, assert_eq!, wait_for_event, vec!)。

uses_cache_when_version_matches151–195 ↗
async fn uses_cache_when_version_matches() -> Result<()>

作用:这个测试确认:如果缓存文件里的客户端版本号和当前程序版本一致,程序应该相信缓存,不需要访问服务器。这样能让启动或列模型更快,也减少不必要的网络请求。

数据流:它先准备一个写入本地缓存的模型,并把缓存里的 client_version 写成当前版本。虽然假服务器也准备了另一个远端模型列表,但测试期望程序不会去拿。构建 Codex 后,它调用模型管理器列出模型,结果应该包含缓存里的模型,并且假服务器收到的 /models 请求数应该是 0。

调用关系:这个测试用 test_remote_model 生成缓存模型,用 write_cache_sync 在 Codex 启动前把缓存文件写好。随后它通过 test_codex 搭好测试环境,再调用模型管理器的 list_models,检查缓存命中这一条路径是否生效。

调用图:调用 4 个内部函数(mount_models_once, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(start, assert!, assert_eq!, vec!)。

refreshes_when_cache_version_missing198–242 ↗
async fn refreshes_when_cache_version_missing() -> Result<()>

作用:这个测试确认:如果缓存文件没有记录客户端版本号,程序不能盲目信任它,而应该重新向服务器拉模型列表。这样可以避免老格式缓存被新版本程序误用。

数据流:它先写入一份本地缓存,里面有一个旧模型,但 client_version 是空的。假服务器则准备返回另一个名为 remote-missing 的模型。构建 Codex 后,测试请求模型列表;正确结果是返回服务器的新模型,并且 /models 被请求了一次。

调用关系:它和版本匹配的测试形成对照:同样通过 test_remote_model 造模型、write_cache_sync 预写缓存、test_codex 启动环境,但因为缓存缺少版本字段,模型管理器应该走联网刷新路径,而不是直接用缓存。

调用图:调用 4 个内部函数(mount_models_once, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(start, assert!, assert_eq!, vec!)。

refreshes_when_cache_version_differs245–292 ↗
async fn refreshes_when_cache_version_differs() -> Result<()>

作用:这个测试确认:如果缓存里写的客户端版本号和当前程序版本不一样,程序应该重新刷新模型列表。这样可以防止程序升级后继续使用不兼容的旧模型信息。

数据流:它先准备一个缓存模型,并把缓存里的版本号故意改成“当前版本加上 -diff”,让它和真实版本不同。假服务器准备返回 remote-different 模型,并挂载多次响应以容纳可能的重试或重复请求。构建 Codex 后,它列出模型,检查结果来自服务器的新模型,并确认至少有一次 /models 请求发生。

调用关系:这个测试同样使用 test_remote_model 造数据,用启动前钩子写缓存。它主要覆盖模型管理器判断“缓存版本不一致”的分支,确保这种情况下会把工作交给远端 /models 接口,而不是继续吃旧缓存。

调用图:调用 4 个内部函数(mount_models_once, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(start, new, assert!, vec!)。

rewrite_cache_timestamp294–299 ↗
async fn rewrite_cache_timestamp(path: &Path, fetched_at: DateTime<Utc>) -> Result<()>

作用:这个辅助函数用来把缓存文件里的 fetched_at 时间改成指定时间。测试用它来模拟“缓存已经很旧”的情况,而不用真的等待时间过去。

数据流:它接收一个缓存文件路径和一个新时间。先调用 read_cache 把 JSON 缓存读成 ModelsCache 结构,再把里面的 fetched_at 字段替换掉,最后调用 write_cache 把修改后的缓存写回原文件。结果是文件内容基本不变,只有缓存获取时间被改了。

调用关系:它只被 renews_cache_ttl_on_matching_models_etag 使用。这个测试需要先制造过期缓存,再验证 ETag 匹配时会续期,所以它负责中间“把缓存做旧”的小步骤,并把实际读写交给 read_cache 和 write_cache。

调用图:调用 2 个内部函数(read_cache, write_cache);被 1 处调用(renews_cache_ttl_on_matching_models_etag)。

read_cache301–305 ↗
async fn read_cache(path: &Path) -> Result<ModelsCache>

作用:这个辅助函数负责从磁盘读取模型缓存文件,并把 JSON 内容变成测试代码能直接检查的 ModelsCache 数据。它让测试不用手动处理一长串文本。

数据流:它接收缓存文件路径,先用异步文件读取把字节内容拿出来,再用 JSON 解析把这些字节转换成 ModelsCache。成功时返回缓存对象;如果文件不存在、读失败或 JSON 格式不对,就返回错误。

调用关系:它被 renews_cache_ttl_on_matching_models_etag 用来检查最终缓存时间,也被 rewrite_cache_timestamp 用来先读出旧缓存再修改。它处在测试和真实缓存文件之间,像一个“翻译员”,把磁盘上的 JSON 翻译成内存里的结构。

调用图:被 2 处调用(renews_cache_ttl_on_matching_models_etag, rewrite_cache_timestamp);外部调用 2 个(from_slice, read)。

write_cache307–311 ↗
async fn write_cache(path: &Path, cache: &ModelsCache) -> Result<()>

作用:这个辅助函数负责把 ModelsCache 写回磁盘上的缓存文件。它用于异步测试流程里,避免阻塞正在运行的测试任务。

数据流:它接收缓存文件路径和一个 ModelsCache。先把缓存对象转成格式化后的 JSON 字节,再用异步写文件把内容保存到指定路径。结果是磁盘上的缓存文件被更新为传入的数据。

调用关系:它被 rewrite_cache_timestamp 调用,用来保存改过时间戳的缓存。也就是说,rewrite_cache_timestamp 决定改什么,write_cache 负责真正落盘。

调用图:被 1 处调用(rewrite_cache_timestamp);外部调用 2 个(to_vec_pretty, write)。

write_cache_sync313–317 ↗
fn write_cache_sync(path: &Path, cache: &ModelsCache) -> Result<()>

作用:这个辅助函数也负责写缓存文件,但它使用同步写法,适合在测试环境还没完全异步启动前使用。测试用它在 Codex 构建之前预先塞入一份缓存。

数据流:它接收缓存路径和 ModelsCache,先把缓存转成漂亮格式的 JSON 字节,然后用普通文件写入把它保存到磁盘。调用结束后,测试目录里就有了一份预设好的模型缓存文件。

调用关系:它被几个测试通过 pre_build_hook 间接使用:在 Codex 真正启动前写好缓存,随后模型管理器启动时就能读到这份缓存。它不调用本文件的其他辅助函数,而是直接完成序列化和写文件。

调用图:外部调用 2 个(to_vec_pretty, write)。

test_remote_model329–379 ↗
fn test_remote_model(slug: &str, priority: i32) -> ModelInfo

作用:这个辅助函数用来快速造一个完整的测试模型信息。因为真实的 ModelInfo 字段很多,每个测试都手写会很啰嗦,也容易漏字段。

数据流:它接收模型 slug,也就是模型的内部名字,以及 priority,也就是排序优先级。然后填入显示名、描述、推理档位、工具支持、上下文窗口等一整套默认测试值,最后返回一个 ModelInfo。调用者只需要关心模型叫什么、优先级是多少。

调用关系:它被四个主要测试共同使用,用来构造缓存模型或服务器返回的模型。它让这些测试可以专注验证缓存规则,而不是被 ModelInfo 的大量字段分散注意力;其中截断策略和输入模态等默认值则交给底层辅助函数生成。

调用图:调用 2 个内部函数(bytes, default_input_modalities);被 4 处调用(refreshes_when_cache_version_differs, refreshes_when_cache_version_missing, renews_cache_ttl_on_matching_models_etag, uses_cache_when_version_matches);外部调用 3 个(default, new, vec!)。

API 客户端与认证测试

这些文件验证整个 API 栈中的客户端侧 HTTP/WebSocket 契约、认证/令牌处理、传输形态,以及面向后端的支持行为。

backend-client/src/client/rate_limit_resets_tests.rs源码 ↗
testtest run

这个文件像一张合同检查表。所谓速率限制,就是服务端规定一段时间内最多能用多少次;重置额度就是让用户用一张“券”把限制窗口提前清掉。这里测试两种后端路径风格:Codex API 和 ChatGPT API,确保同一个功能在不同服务器地址下会拼出正确的接口路径。它还检查请求体里的字段名必须是 redeem_request_id,不能随便改;同时确认服务端返回的额度摘要和消费结果能被正确读进 Rust 类型里。一个细节是,返回里的 credit 字段虽然存在,但命令行客户端不关心它,所以反序列化时会忽略,只保留真正要用的 code 和 windows_reset。

函数细节2
rate_limit_reset_contract_uses_expected_paths_and_payloads7–59 ↗
fn rate_limit_reset_contract_uses_expected_paths_and_payloads()

作用:这是主测试,确认客户端和后端约定的接口地址、请求 JSON、响应 JSON 都保持一致。有人改了路径或字段名时,这个测试会立刻失败,提醒这是会影响真实请求的破坏性改动。

数据流:它不接收外部输入,而是在测试里造出几个固定的客户端和几段固定的 JSON。然后它把客户端生成的网址、Rust 结构体转成的 JSON、JSON 读回来的结果,逐一和预期值比较。最后输出不是业务数据,而是测试是否通过;如果任何一处不一样,测试框架就会报错。

调用关系:它由 Rust 的测试运行器在跑测试时调用。测试过程中,它借助本文件的 test_client 造出简化版客户端,再用 assert_eq! 做比较,用 serde_json 的 from_value 和 json! 把 JSON 和 Rust 类型互相转换,目的就是验证客户端代码和接口契约没有走样。

调用图:外部调用 3 个(assert_eq!, from_value, json!)。

test_client61–71 ↗
fn test_client(base_url: &str, path_style: PathStyle) -> Client

作用:这是测试里的小工具,用来快速造一个假的 Client。它避免每个断言都重复写一大堆客户端初始化代码,让测试只关注“网址和数据对不对”。

数据流:它接收一个基础网址和一种路径风格。然后把这些信息塞进 Client,同时创建一个 reqwest HTTP 客户端,并使用未登录的认证提供者;其他账号相关字段都设成空或默认值。出来的是一个可用于拼接口地址的 Client,但测试里不会真的拿它去联网。

调用关系:它服务于 rate_limit_reset_contract_uses_expected_paths_and_payloads,在测试需要不同后端地址时被反复使用。它内部把创建 HTTP 客户端的工作交给 reqwest::Client::new,把认证占位交给 unauthenticated_auth_provider,这样测试环境简单且不依赖真实登录。

调用图:调用 1 个内部函数(new);外部调用 1 个(unauthenticated_auth_provider)。

login/src/token_data_tests.rs源码 ↗
testtest time

这里测试的是 JWT,也就是一种常见的登录令牌,里面像身份证一样夹带了用户邮箱、套餐类型、账号属性和过期时间。真实登录时,如果这些字段读错了,程序可能会把企业用户当个人用户,或者看不懂令牌什么时候失效。文件先用 fake_jwt 做出假的令牌,避免测试依赖真实服务器;然后逐个检查不同情况:Pro、Go、企业套餐、按量计费套餐、FedRAMP 账号、缺字段、过期时间,以及坏格式令牌。它还直接测试“哪些套餐算工作区账号”。整体像一组验钞机测试样张:每张样张代表一种登录令牌,确保解析器遇到真实令牌时不会认错。

函数细节11
fake_jwt8–27 ↗
fn fake_jwt(payload: serde_json::Value) -> String

作用:这个函数用来造一个假的 JWT 登录令牌,方便测试不用真的去登录。它把测试想要的内容塞进令牌中,让后面的测试可以专心检查解析结果。

数据流:输入是一段 JSON 形式的令牌内容,比如邮箱或套餐字段;它先做一个固定的令牌头,再把头和内容转成 JSON 字节,并用 URL 安全的 Base64 编码成 JWT 常见的三段格式;输出是一条看起来像 JWT 的字符串。它不会验证签名,只是造测试用假数据。

调用关系:多个测试函数都会先调用它来准备样本令牌。它自己主要借助 JSON 序列化和字符串拼接,把测试数据包装成解析函数能吃的样子。

调用图:被 8 处调用(id_token_info_handles_missing_fields, id_token_info_parses_email_and_plan, id_token_info_parses_fedramp_account_claim, id_token_info_parses_go_plan, id_token_info_parses_hc_plan_as_enterprise, id_token_info_parses_usage_based_business_plans, jwt_expiration_handles_missing_exp, jwt_expiration_parses_exp_claim);外部调用 2 个(format!, to_vec)。

id_token_info_parses_email_and_plan30–41 ↗
fn id_token_info_parses_email_and_plan()

作用:这个测试确认令牌里写着邮箱和 Pro 套餐时,解析代码能把它们正确读出来。它保证最常见的个人付费账号不会被识别错。

数据流:进去的是一个临时造出的假令牌,里面有 user@example.com 和 chatgpt_plan_type 为 pro;测试把它交给令牌解析代码;出来的结果应该包含同一个邮箱,并把 pro 显示成更适合人看的 “Pro”。

调用关系:它先用 fake_jwt 造样本,再用断言检查解析后的字段。这个测试属于基础场景,证明后续套餐判断建立在可靠的解析结果上。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

id_token_info_parses_go_plan44–55 ↗
fn id_token_info_parses_go_plan()

作用:这个测试确认 Go 套餐能被识别并显示成正确名称。它防止新增或较特殊的套餐在登录信息里被忽略。

数据流:输入是假令牌,里面有邮箱和原始套餐值 go;测试调用解析逻辑后,检查邮箱没丢,并确认套餐显示结果是 “Go”;如果解析代码把 go 当未知值或改错名字,测试就会失败。

调用关系:它和其他套餐测试一样,先靠 fake_jwt 生成令牌,再用断言验收解析结果。它补充覆盖 Pro 之外的个人套餐。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

id_token_info_parses_hc_plan_as_enterprise58–70 ↗
fn id_token_info_parses_hc_plan_as_enterprise()

作用:这个测试确认原始套餐值 hc 会被当成 Enterprise,也就是企业类账号。它很重要,因为企业账号通常会影响功能权限和工作区判断。

数据流:输入是假令牌,套餐字段是 hc;解析后,测试要求显示名称是 “Enterprise”,并且 is_workspace_account 返回 true;结果说明这个账号被当作工作区账号,而不是普通个人账号。

调用关系:它通过 fake_jwt 准备带 hc 字段的令牌,然后检查解析和账号分类两件事。它连接了“读出套餐”和“判断账号类型”这两层逻辑。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

id_token_info_parses_usage_based_business_plans73–108 ↗
fn id_token_info_parses_usage_based_business_plans()

作用:这个测试检查两种按量计费的商务套餐能不能被正确识别。它还确认这些套餐既保留原始名字,又能显示成人能读懂的名字,并且都算工作区账号。

数据流:测试先造一个 self_serve_business_usage_based 令牌,解析后检查显示名、原始值和工作区标记;接着再造一个 enterprise_cbp_usage_based 令牌,做同样检查;输出不是新数据,而是通过断言确认解析结果符合预期。

调用关系:它多次调用 fake_jwt 准备不同套餐样本,再分别检查解析结果。这个测试覆盖更长、更容易写错的套餐字符串,是对套餐映射表的一种保护。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

id_token_info_handles_missing_fields111–118 ↗
fn id_token_info_handles_missing_fields()

作用:这个测试确认令牌缺少邮箱和套餐字段时,解析代码不会崩溃,也不会凭空编造信息。它保证登录信息不完整时程序仍然安全、可预期。

数据流:输入是假令牌,只有 sub 这样的用户标识,没有邮箱、套餐和 FedRAMP 字段;解析后,邮箱应该是空,套餐应该是空,FedRAMP 标记应该是 false;也就是说,缺什么就诚实地返回没有。

调用关系:它使用 fake_jwt 构造缺字段样本,再用断言检查默认行为。这个测试和正常字段测试相反,专门守住边界情况。

调用图:调用 1 个内部函数(fake_jwt);外部调用 3 个(assert!, assert_eq!, json!)。

id_token_info_parses_fedramp_account_claim121–133 ↗
fn id_token_info_parses_fedramp_account_claim()

作用:这个测试确认令牌里的 FedRAMP 账号标记能被读出来。FedRAMP 可以理解为美国政府相关的合规环境标识,识别错可能会影响使用的服务环境和安全要求。

数据流:输入是假令牌,里面有 chatgpt_account_id 和 chatgpt_account_is_fedramp 为 true;解析后,测试要求账号 ID 仍是 account-fed,并且 FedRAMP 判断为 true;这证明账号身份和合规标记都没有丢。

调用关系:它先调用 fake_jwt 生成带合规字段的令牌,再检查解析结果。它补充了套餐之外的账号属性测试。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

jwt_expiration_parses_exp_claim136–143 ↗
fn jwt_expiration_parses_exp_claim()

作用:这个测试确认 JWT 里的 exp 过期时间字段能被正确转换成时间对象。它防止程序误判令牌是否已经过期。

数据流:输入是假令牌,里面的 exp 是 1700000000 这个 Unix 时间戳,也就是从 1970 年开始数的秒数;解析逻辑读取这个数字并转成 UTC 时间;测试检查转换出来的时间正好对应这个时间戳。

调用关系:它用 fake_jwt 造出带 exp 字段的令牌,再比较解析出的时间。它专门覆盖过期时间解析这条路径。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

jwt_expiration_handles_missing_exp146–151 ↗
fn jwt_expiration_handles_missing_exp()

作用:这个测试确认令牌没有 exp 过期时间时,解析代码会返回“没有过期时间”,而不是报错。这样可以区分“格式坏了”和“只是没写这个字段”。

数据流:输入是假令牌,里面只有 sub,没有 exp;解析过期时间后,结果应该是 None,也就是没有值;测试确认代码没有崩溃,也没有随便填一个时间。

调用关系:它通过 fake_jwt 生成缺少 exp 的样本,然后检查过期时间解析的空值行为。它和 jwt_expiration_parses_exp_claim 一起覆盖有字段和无字段两种情况。

调用图:调用 1 个内部函数(fake_jwt);外部调用 2 个(assert_eq!, json!)。

jwt_expiration_rejects_malformed_jwt154–157 ↗
fn jwt_expiration_rejects_malformed_jwt()

作用:这个测试确认明显不像 JWT 的字符串会被拒绝。它保证解析器不会把乱七八糟的输入当成正常登录令牌。

数据流:输入是字符串 not-a-jwt;过期时间解析逻辑尝试读取它时应该失败;测试拿到错误,并确认错误文字是 “invalid ID token format”。

调用关系:这个测试不需要 fake_jwt,因为它故意传入坏格式。它检查的是错误路径,确保用户或上游传错令牌时能得到明确失败。

调用图:外部调用 1 个(assert_eq!)。

workspace_account_detection_matches_workspace_plans160–178 ↗
fn workspace_account_detection_matches_workspace_plans()

作用:这个测试直接检查哪些套餐会被判断成工作区账号。它保证 Business 这类团队/企业套餐算工作区,而 Pro、Pro Lite 这类个人套餐不算。

数据流:测试手动创建几个 IdTokenInfo 对象,分别塞入 Business、Pro、ProLite 套餐;然后调用 is_workspace_account;结果应该是 Business 为 true,Pro 和 ProLite 为 false。

调用关系:它不通过 JWT 解析,而是直接测试账号分类规则本身。它使用默认的 IdTokenInfo 作为底子,只改套餐字段,用来隔离验证“套餐到工作区账号”的判断逻辑。

调用图:外部调用 3 个(assert_eq!, Known, default)。

login/src/auth/default_client_tests.rs源码 ↗
testtest run

这个文件不是真正给用户运行的功能,而是给开发者和持续集成系统跑的“体检项目”。它重点检查几件事:生成出来的 Codex User-Agent(可以理解成客户端递给服务器的“自我介绍名片”)是不是以正确来源开头;哪些 originator(来源标识,说明请求来自哪个官方客户端)算第一方;创建默认 HTTP 客户端时,会不会自动带上 originator、User-Agent 和地区驻留要求这些请求头。它还用一个本地假服务器接住请求,像收快递时当场拆包检查标签一样,确认真正发出去的请求里有这些头。最后,它测试 User-Agent 后缀里如果出现回车或空字符这类危险字符,会被替换成安全的下划线。macOS 上还额外检查 User-Agent 是否包含系统版本和 CPU 架构信息。

函数细节7
test_get_codex_user_agent7–12 ↗
fn test_get_codex_user_agent()

作用:这个测试确认生成的 Codex User-Agent 会用当前 originator 作为开头。简单说,就是检查客户端递给服务器的“名片”有没有先写清楚自己是谁。

数据流:进去的是当前代码环境里算出的 originator 和 User-Agent → 测试把 originator 拼成应该出现的前缀 → 再检查 User-Agent 是否以这个前缀开头;如果不是,测试失败,不会产生业务输出。

调用关系:它由 Rust 测试框架在测试阶段自动运行。它主要靠格式化字符串和断言来做检查,是对 User-Agent 生成规则的一个基础护栏。

调用图:外部调用 2 个(assert!, format!)。

is_first_party_originator_matches_known_values15–22 ↗
fn is_first_party_originator_matches_known_values()

作用:这个测试确认普通 originator 的“官方来源”判断规则符合预期。它防止把不该算官方的名字误判成官方,也防止真正官方的名字被漏掉。

数据流:进去的是一组写死的来源名字 → 测试逐个交给判断函数并拿结果和预期真假对比 → 如果某个名字的判断结果不对,断言会失败,提醒规则被改坏了。

调用关系:它在测试运行时由测试框架调用,使用断言来固定一组已知样例的行为。它相当于给 originator 分类规则立了几块路标,之后改代码不能随便越界。

调用图:外部调用 1 个(assert_eq!)。

is_first_party_chat_originator_matches_known_values25–33 ↗
fn is_first_party_chat_originator_matches_known_values()

作用:这个测试专门检查聊天类 originator 的官方来源判断。因为聊天客户端和普通客户端的来源规则不完全一样,所以需要单独确认。

数据流:进去的是几组聊天相关和非聊天相关的来源名字 → 测试检查每个名字是否被判断为第一方聊天来源 → 输出不是业务数据,而是通过或失败的测试结果。

调用关系:它和普通 originator 的测试并列存在,由测试框架执行。它用断言把聊天来源的特殊规则固定下来,避免以后改动时把聊天端和普通端混在一起。

调用图:外部调用 1 个(assert_eq!)。

test_create_client_sets_default_headers36–90 ↗
async fn test_create_client_sets_default_headers()

作用:这个测试确认默认创建出来的 HTTP 客户端真的会在请求里带上必要请求头。请求头可以理解成信封上的附加标签,服务器靠它们知道请求来自哪里、是什么客户端、是否有地区要求。

数据流:进去的是默认客户端配置,以及临时设置的美国地区驻留要求 → 测试启动一个本地假服务器,让客户端向它发 GET 请求 → 假服务器记录收到的请求,测试再拆开请求头检查 originator、User-Agent 和 residency 是否正确;最后把临时地区设置清掉,避免影响别的测试。

调用关系:它由异步测试框架运行,并且会先用 skip_if_no_network! 在不适合跑网络测试的环境里跳过。它把活儿交给 wiremock 这类测试工具启动假服务器、接收请求,再用断言确认 create_client 的默认行为没有漏掉关键头。

调用图:外部调用 6 个(given, start, new, assert!, assert_eq!, skip_if_no_network!)。

test_invalid_suffix_is_sanitized93–101 ↗
fn test_invalid_suffix_is_sanitized()

作用:这个测试确认 User-Agent 后缀里如果有回车符,会被清理成安全字符。这样可以避免一个看不见的特殊字符破坏 HTTP 请求格式。

数据流:进去的是一个正常前缀和一个带回车符的坏后缀 → 测试把它们拼成 User-Agent 后交给清理函数 → 期望出来的字符串里回车被替换成下划线,整体格式仍然可用。

调用关系:它在测试阶段由测试框架调用,用断言检查 sanitize_user_agent 的安全清理效果。它是针对特殊坏字符的回归测试,防止以后这类保护被删掉或改错。

调用图:外部调用 1 个(assert_eq!)。

test_invalid_suffix_is_sanitized2104–112 ↗
fn test_invalid_suffix_is_sanitized2()

作用:这个测试确认 User-Agent 后缀里如果有空字符,也会被清理掉。空字符虽然平时看不见,但在协议字符串里可能造成麻烦。

数据流:进去的是一个正常前缀和一个含空字符的后缀 → 测试让清理函数处理完整 User-Agent → 期望结果是空字符位置变成下划线,最后用断言核对。

调用关系:它和回车符清理测试互相补充,也由测试框架自动运行。它继续覆盖 sanitize_user_agent 对另一种非法字符的处理,保证安全规则不是只对某一种情况有效。

调用图:外部调用 1 个(assert_eq!)。

test_macos116–125 ↗
fn test_macos()

作用:这个测试只在 macOS 上运行,用来确认 macOS 生成的 User-Agent 格式里包含系统版本和处理器架构。这样服务器或日志系统看到请求时,能更准确知道客户端运行环境。

数据流:进去的是当前 macOS 环境下生成的 User-Agent 和 originator → 测试把 originator 转成正则表达式里安全的文本,再拼出预期格式 → 用正则检查 User-Agent 是否符合“来源/版本号 (Mac OS 版本; 架构) 额外信息”的形状。

调用关系:它由测试框架在 macOS 条件满足时运行。它把格式检查交给 regex_lite 的正则表达式工具,再用断言决定是否通过,是平台专属 User-Agent 规则的保护网。

调用图:外部调用 4 个(new, assert!, format!, escape)。

chatgpt/src/workspace_settings_tests.rs源码 ↗
testtest run

这个文件是测试文件,不是正式运行时给用户直接用的功能。它围绕一个叫 encode_path_segment 的函数做检查:这个函数的任务,是把一小段文字变成适合放进网址路径里的形式。比如普通英文字母、数字和一些安全符号应该原样保留;但斜杠“/”和空格这类容易改变网址含义的字符,要改写成百分号编码(也就是用 % 后面跟数字字母来表示)。可以把它想成给包裹贴标签:安全的字直接写,不安全的符号要换成快递系统能稳定识别的写法。这里有两个测试,一个确认安全字符不会被乱改,另一个确认斜杠和空格会被正确转义。这样以后有人改代码时,如果不小心破坏了网址编码规则,测试会立刻报错。

函数细节2
encode_path_segment_leaves_unreserved_ascii_unchanged4–9 ↗
fn encode_path_segment_leaves_unreserved_ascii_unchanged()

作用:这个测试确认 encode_path_segment 不会多此一举地改动本来就安全的字符。也就是说,像字母、数字、短横线、下划线、点和波浪号这些字符,放进网址路径里应该保持原样。

数据流:进去的是字符串 "account-123_ABC.~",测试把它交给 encode_path_segment。函数处理后,测试用 assert_eq!(一个用来比较“实际结果”和“期望结果”是否一样的测试工具)检查输出是不是仍然等于原字符串。如果一样,测试通过;如果被错误改写了,测试失败。

调用关系:它会在 Rust 的测试运行器执行测试时被调用。它只做一件事:调用 encode_path_segment 得到结果,然后交给 assert_eq! 判断结果是否符合预期,用来守住“安全字符不应被改变”这条规则。

调用图:外部调用 1 个(assert_eq!)。

encode_path_segment_escapes_path_separators_and_spaces12–17 ↗
fn encode_path_segment_escapes_path_separators_and_spaces()

作用:这个测试确认 encode_path_segment 会把容易破坏网址路径含义的字符正确改写。特别是斜杠会被写成 %2F,空格会被写成 %20。

数据流:进去的是字符串 "account/123 with space",里面有斜杠和空格。测试把它交给 encode_path_segment,期望出来的是 "account%2F123%20with%20space"。随后 assert_eq! 比较实际输出和期望输出;一致就说明编码规则正确,不一致就说明网址路径可能会被错误拆分或解析。

调用关系:它同样由测试运行器在跑测试时调用。它负责覆盖另一种关键情况:遇到斜杠和空格时,encode_path_segment 必须把活儿做好;最后由 assert_eq! 给出通过或失败的判断。

调用图:外部调用 1 个(assert_eq!)。

codex-api/src/api_bridge_tests.rs源码 ↗
testtest

这个文件不负责真正发网络请求,而是像做情景演练一样,手工造出各种 API 错误:比如服务器太忙、请求触发网络安全策略、用量超限、身份认证失败等。然后它把这些错误交给 map_api_error,看它会不会变成正确的 CodexErr。这里的“HTTP 状态码”就是服务器用数字表示结果的方式,比如 400 表示请求有问题,401 表示没登录或授权失败,429 表示请求太多,503 表示服务器暂时扛不住。测试还会检查响应正文里的 JSON(一种常见的数据文本格式)和响应头(服务器随回复附带的小纸条)是否被正确读取。可以把它理解成翻译员考试:同一句“出错了”,不同场景要翻译成“服务器过载”“安全策略拦截”“额度用完”或“认证异常”。这些测试保证以后改代码时,不会不小心把这些重要区别弄丢。

函数细节10
map_api_error_maps_server_overloaded6–9 ↗
fn map_api_error_maps_server_overloaded()

作用:这个测试确认最直接的“服务器过载”错误会被翻译成 Codex 自己的“服务器过载”错误。这样上层程序就能知道问题不是用户操作错了,而是服务端暂时太忙。

数据流:进去的是一个现成的 ApiError::ServerOverloaded → 测试把它交给 map_api_error 做转换 → 出来后检查结果是不是 CodexErr::ServerOverloaded;它不改动外部数据,只验证转换结果。

调用关系:测试运行器会调用这个测试函数。它主要围绕 map_api_error 做一次最基础的校验,最后用 assert! 这个断言工具确认结果符合预期。

调用图:外部调用 1 个(assert!)。

map_api_error_maps_server_overloaded_from_503_body12–27 ↗
fn map_api_error_maps_server_overloaded_from_503_body()

作用:这个测试确认另一种服务器过载写法也能被认出来:服务器用 HTTP 503 和响应正文里的 server_is_overloaded 来表达过载。它防止程序只认一种错误形式,漏掉真实接口返回的形式。

数据流:进去的是测试临时拼出来的 JSON 错误正文、503 状态码、请求地址等信息 → 它们被包装成 TransportError::Http,再放进 ApiError::Transport → map_api_error 读取状态码和正文后,应该输出 CodexErr::ServerOverloaded。

调用关系:测试运行器触发它后,它用 json! 造出模拟服务器回复,用 Transport 包装成传输层错误,再交给 map_api_error;最后用 assert! 检查翻译结果。

调用图:外部调用 3 个(assert!, Transport, json!)。

map_api_error_maps_cyber_policy_from_400_body30–54 ↗
fn map_api_error_maps_cyber_policy_from_400_body()

作用:这个测试确认当服务器说请求触发了网络安全策略时,程序会给出专门的 CyberPolicy 错误,而不是普通的请求错误。这样用户能看到更准确的原因。

数据流:进去的是一个 400 状态码和包含 code: cyber_policy、message: 风险提示的 JSON 正文 → map_api_error 解析这些内容 → 出来应该是 CodexErr::CyberPolicy,并且里面的提示文字必须和服务器给的一样。

调用关系:它模拟普通 HTTP 接口返回的安全策略错误,交给 map_api_error 判断。之后用模式匹配取出 CyberPolicy 的 message,再用 assert_eq! 检查文字;如果类型不对,就用 panic! 让测试失败。

调用图:外部调用 4 个(assert_eq!, Transport, panic!, json!)。

map_api_error_maps_wrapped_websocket_cyber_policy_from_400_body57–79 ↗
fn map_api_error_maps_wrapped_websocket_cyber_policy_from_400_body()

作用:这个测试确认 WebSocket 连接里的错误包了一层外壳时,安全策略错误仍然能被识别。WebSocket 可以理解成一条持续打开的双向通信通道,它的错误格式可能和普通 HTTP 不完全一样。

数据流:进去的是一个看起来像 WebSocket 错误事件的 JSON,外层有 type 和 status,内层才是真正的 cyber_policy 错误 → map_api_error 需要穿过这层外壳找到真实错误 → 输出 CodexErr::CyberPolicy,并保留服务器给的提示信息。

调用关系:测试运行器调用它后,它用 json! 造 WebSocket 风格的错误正文,用 Transport 包成 HTTP 传输错误,再交给 map_api_error。最后用 assert_eq! 检查取出的提示文字,类型不对就 panic!。

调用图:外部调用 4 个(assert_eq!, Transport, panic!, json!)。

map_api_error_uses_cyber_policy_fallback_for_missing_message82–103 ↗
fn map_api_error_uses_cyber_policy_fallback_for_missing_message()

作用:这个测试确认如果服务器只说 code 是 cyber_policy,却没有给具体提示文字,程序会补上一句默认说明。这样用户不会只看到空白或很难懂的错误。

数据流:进去的是一个 400 错误正文,里面只有 cyber_policy 代码,没有 message → map_api_error 识别出这是安全策略错误,并填入默认提示 → 出来是 CodexErr::CyberPolicy,message 是固定的兜底文案。

调用关系:它覆盖的是“服务器信息不完整”的情况。测试把简化的 JSON 交给 map_api_error,然后用 assert_eq! 确认兜底文案正确;如果没有得到 CyberPolicy,就用 panic! 报错。

调用图:外部调用 4 个(assert_eq!, Transport, panic!, json!)。

map_api_error_keeps_unknown_400_errors_generic106–125 ↗
fn map_api_error_keeps_unknown_400_errors_generic()

作用:这个测试确认未知的 400 错误不会被误判成安全策略错误。这样可以避免程序把别的问题说成“网络安全风险”,造成误导。

数据流:进去的是一个 400 状态码,正文里有 message,但 code 是 some_other_policy,不是 cyber_policy → map_api_error 看到它不属于已知特殊情况 → 出来应是普通的 CodexErr::InvalidRequest,并保留原始正文作为错误信息。

调用关系:它和前几个 cyber_policy 测试形成对照:只有明确的安全策略代码才走专门分支。测试用 json! 和 Transport 造错误,再用 assert_eq! 检查原始正文没有被错误改写。

调用图:外部调用 4 个(assert_eq!, Transport, panic!, json!)。

map_api_error_maps_usage_limit_limit_name_header128–162 ↗
fn map_api_error_maps_usage_limit_limit_name_header()

作用:这个测试确认用量超限时,程序能从响应头里读出具体是哪一种限制被撞到了。比如不是笼统地说“超限”,而是知道限制名叫 codex_other。

数据流:进去的是一些响应头、429 状态码和表示 usage_limit_reached 的 JSON 正文 → map_api_error 解析“请求太多/额度到了”的错误,并读取相关响应头 → 出来是 CodexErr::UsageLimitReached,里面的 rate_limits.limit_name 应该是 codex_other。

调用关系:测试先用 HeaderMap::new 建一个响应头容器,再用 from_static 放入固定头值,然后把它们和正文一起交给 map_api_error。最后用 assert_eq! 检查用量限制快照里的限制名是否被正确带出来。

调用图:外部调用 6 个(new, assert_eq!, Transport, from_static, panic!, json!)。

map_api_error_does_not_fallback_limit_name_to_limit_id165–195 ↗
fn map_api_error_does_not_fallback_limit_name_to_limit_id()

作用:这个测试确认程序不会把“限制 ID”误当成“限制名称”。这很重要,因为两个字段含义不同,随便兜底可能会让后续显示或统计变得不准确。

数据流:进去的是 429 用量超限错误,响应头里只有活动限制的 ID,没有对应的限制名称头 → map_api_error 解析出用量超限,但不应该强行把 ID 填进 limit_name → 出来是 CodexErr::UsageLimitReached,limit_name 保持为空。

调用关系:它专门检查 map_api_error 的谨慎行为:缺什么就缺什么,不乱猜。测试构造 HeaderMap 和 JSON 错误后交给转换函数,再用 assert_eq! 确认 limit_name 是 None。

调用图:外部调用 6 个(new, assert_eq!, Transport, from_static, panic!, json!)。

map_api_error_ignores_unparseable_rate_limit_reached_type_headers198–226 ↗
fn map_api_error_ignores_unparseable_rate_limit_reached_type_headers()

作用:这个测试确认如果响应头里的“触发了哪类限流”字段看不懂,程序会忽略它,而不是崩溃或填入乱值。这样面对未来新增值或奇怪字节时更稳。

数据流:进去的是两种异常头值:一种是当前不认识的字符串,另一种是不能按普通文本理解的字节;每次都配上 429 用量超限正文 → map_api_error 尝试读取但发现无法可靠解析 → 出来仍是 CodexErr::UsageLimitReached,只是 rate_limit_reached_type 为空。

调用关系:测试用一个小循环覆盖两种坏头值。每轮都新建 HeaderMap、构造 Transport 错误、调用 map_api_error,再用 assert_eq! 确认不可解析字段被安全忽略。

调用图:外部调用 7 个(new, assert_eq!, Transport, from_bytes, from_static, panic!, json!)。

map_api_error_extracts_identity_auth_details_from_headers229–261 ↗
fn map_api_error_extracts_identity_auth_details_from_headers()

作用:这个测试确认认证失败时,程序会把响应头里有助于排查的问题细节保存下来。比如请求编号、Cloudflare 追踪编号、授权错误代码,以及身份系统返回的具体错误码。

数据流:进去的是一个 401 未授权错误,带有多个响应头:request id、cf ray、授权错误,以及一个 base64 编码的 JSON 错误;base64 是把数据转成适合放在文本里的编码方式 → map_api_error 读取并解码这些信息 → 出来是 CodexErr::UnexpectedStatus,里面包含这些排查字段。

调用关系:这个测试模拟 chatgpt.com 后端接口认证失败的场景。它先准备 HeaderMap 和编码后的错误头,再交给 map_api_error;随后用 assert_eq! 分别检查每个诊断字段是否被保存,类型不对就 panic!。

调用图:外部调用 6 个(new, assert_eq!, Transport, from_static, from_str, panic!)。

codex-api/tests/clients.rs源码 ↗
testtest run

这个测试文件像一个“假后台服务实验室”。它不真的访问网络,而是做了几种假的传输器和假的认证器:有的只记录请求,有的第一次故意失败,有的第一次认证失败。测试会把 ResponsesClient 接到这些假零件上,然后观察它准备出来的请求。这样就能确认几件关键事:请求路径是不是 /responses;JSON 请求体有没有被原样保留;认证头、Accept 头、Azure 会话头有没有加上;网络临时失败或临时认证失败时是否会重试;认证配置本身错误时是否不会乱重试。这里的互斥锁(一把锁,防止两个测试任务同时改同一份记录)用来安全保存请求记录。整体上,它保证“发给模型服务的信封”地址、内容、邮票和重寄规则都正确。

函数细节29
assert_path_ends_with31–38 ↗
fn assert_path_ends_with(requests: &[Request], suffix: &str)

作用:检查测试里只发出了一次请求,并且这次请求的网址结尾符合预期。有人用它来确认客户端真的把请求发到了应该去的接口路径。

数据流:进去的是一组已记录的请求和一个期望的网址结尾 → 它先确认请求数量正好是 1,再拿第一条请求的 URL 看是否以指定后缀结束 → 没问题就什么也不返回,有问题测试立刻失败并说明实际 URL。

调用关系:它是测试里的小检查工具。responses_client_uses_responses_path 发完一次流式请求后,把记录到的请求交给它,由它判断 ResponsesClient 是否用了 /responses 这个路径。

调用图:被 1 处调用(responses_client_uses_responses_path);外部调用 2 个(assert!, assert_eq!)。

request_body_bytes40–45 ↗
fn request_body_bytes(request: &Request) -> &[u8]

作用:从一个已经准备好的请求里取出 JSON 请求体的原始字节。它让测试可以直接检查客户端最终要发出去的内容。

数据流:进去的是一个 Request → 它查看请求体是否是 EncodedJson,也就是已经编码好的 JSON → 如果是,就返回里面的字节;如果不是,就直接让测试失败,因为这说明请求体形态不符合预期。

调用关系:azure_default_store_attaches_ids_and_headers 用它读取请求体,再解析成 JSON,检查 Azure 场景下输入消息的 id 有没有被保留下来。

调用图:被 1 处调用(azure_default_store_attaches_ids_and_headers);外部调用 1 个(panic!)。

RecordingState::record53–59 ↗
fn record(&self, req: Request)

作用:把一次流式请求保存起来,方便测试稍后查看。它就像录像按钮,客户端一发请求,它就把请求副本记进本子里。

数据流:进去的是一个 Request → 它先拿到互斥锁,安全打开共享的请求列表 → 把请求追加进去,函数本身不返回有意义的数据,但会改变内部记录。

调用关系:RecordingTransport::stream 收到请求时会调用它。这样真正的网络不会发生,测试却能在之后通过 RecordingState::take_stream_requests 拿到请求来检查。

调用图:被 1 处调用(stream)。

RecordingState::take_stream_requests61–67 ↗
fn take_stream_requests(&self) -> Vec<Request>

作用:取走之前记录的所有流式请求,并把记录列表清空。这样每个测试都能拿到干净的一批结果。

数据流:进去的是 RecordingState 自己保存的共享列表 → 它加锁后把列表里的请求整体拿走,同时把原位置换成空列表 → 出来的是这批 Request。

调用关系:多个测试在客户端调用完成后使用它,例如检查路径、请求体、认证头和 Azure 头。它和 RecordingState::record 配成一对:一个记,一个取。

调用图:外部调用 1 个(take)。

RecordingTransport::new76–78 ↗
fn new(state: RecordingState) -> Self

作用:创建一个只记录请求、不真正联网的假传输器。测试用它来观察客户端会发什么,而不是让请求真的出门。

数据流:进去的是一个 RecordingState → 它把这个共享状态放进 RecordingTransport → 出来的是可交给 ResponsesClient 使用的假网络层。

调用关系:大多数测试都会先调用它,再把返回的 RecordingTransport 传给 ResponsesClient::new。之后客户端调用 stream 时,请求会被 RecordingTransport::stream 记录下来。

调用图:被 6 处调用(azure_default_store_attaches_ids_and_headers, responses_client_stream_request_preserves_exact_json_body, responses_client_uses_responses_path, streaming_client_adds_auth_headers, streaming_client_does_not_retry_auth_build_error, streaming_client_retries_on_transient_auth_error)。

RecordingTransport::execute82–84 ↗
async fn execute(&self, _req: Request) -> Result<Response, TransportError>

作用:这是普通非流式请求的占位实现,在这些测试里不应该被调用。若被调用,说明客户端走错了通道。

数据流:进去的是一个请求,但它故意不处理 → 直接返回一个构建类错误,消息说明 execute 不该运行 → 不会产生真实响应,也不会记录请求。

调用关系:RecordingTransport 实现 HttpTransport 必须提供 execute,但本文件测试的是流式请求,所以真正应该走 RecordingTransport::stream。这个函数像报警器,防止测试误走非流式路径。

调用图:外部调用 1 个(Build)。

RecordingTransport::stream86–95 ↗
async fn stream(&self, req: Request) -> Result<StreamResponse, TransportError>

作用:模拟一次成功的流式网络请求,同时把请求记录下来。它让测试不用真实服务器也能检查客户端准备的请求。

数据流:进去的是一个 Request → 它调用 RecordingState::record 保存请求 → 然后返回一个状态码 OK、头为空、内容流也为空的 StreamResponse。

调用关系:ResponsesClient 发起流式请求时会调用它。它把“发出去的信封”留下给各个测试检查,同时返回一个空流,让客户端认为请求已经成功建立。

调用图:调用 1 个内部函数(record);外部调用 4 个(pin, new, new, iter)。

NoAuth::add_auth_headers102–102 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是一个什么认证头都不加的假认证器。测试用它来排除认证影响,只看路径、请求体、重试等其他行为。

数据流:进去的是可修改的请求头集合 → 它不读取、不插入任何头 → 出来的请求头保持原样。

调用关系:很多测试把 NoAuth 传给 ResponsesClient。这样客户端仍会走完整认证接口,但不会多出 Authorization 之类的头,测试更容易聚焦其他点。

StaticAuth::new112–117 ↗
fn new(token: &str, account_id: &str) -> Self

作用:创建一个固定返回同一组认证信息的假认证器。它用于检查客户端是否真的把 token 和账号 id 放进请求头。

数据流:进去的是 token 字符串和 account_id 字符串 → 它把两者复制保存到 StaticAuth 里 → 出来的是一个可复用的认证器。

调用关系:streaming_client_adds_auth_headers 会调用它创建认证器,然后交给 ResponsesClient。随后 StaticAuth::add_auth_headers 会在请求发送前写入对应头。

调用图:被 1 处调用(streaming_client_adds_auth_headers)。

StaticAuth::add_auth_headers121–129 ↗
fn add_auth_headers(&self, headers: &mut HeaderMap)

作用:把固定的认证信息写进 HTTP 请求头。这里的 HTTP 请求头可以理解成信封上的附加标签,服务端靠它识别谁在请求。

数据流:进去的是可修改的 HeaderMap → 它把 token 拼成 Bearer token 形式,写入 Authorization;再把账号 id 写入 ChatGPT-Account-ID → 出来的请求头多了这两个认证相关字段;如果某个值无法变成合法请求头,就跳过那个字段。

调用关系:ResponsesClient 在准备请求时会通过 AuthProvider 调用它。streaming_client_adds_auth_headers 之后检查 RecordingTransport 记录到的请求,确认这些头确实存在。

调用图:外部调用 3 个(insert, from_str, format!)。

provider132–147 ↗
fn provider(name: &str) -> Provider

作用:快速造一个测试用的 Provider 配置。Provider 可以理解成“模型服务供应商的地址和规则卡片”。

数据流:进去的是供应商名字 → 它生成 base_url、空查询参数、空额外头、很短的超时时间和默认重试配置 → 出来的是一个 Provider,供 ResponsesClient 组装请求使用。

调用关系:几乎所有测试都会调用它。个别测试会在它返回后改重试次数或供应商名字,比如 openai、azure,用来触发不同客户端行为。

调用图:被 7 处调用(azure_default_store_attaches_ids_and_headers, responses_client_stream_request_preserves_exact_json_body, responses_client_uses_responses_path, streaming_client_adds_auth_headers, streaming_client_does_not_retry_auth_build_error, streaming_client_retries_on_transient_auth_error, streaming_client_retries_on_transport_error);外部调用 2 个(from_millis, new)。

FlakyTransport::default161–163 ↗
fn default() -> Self

作用:提供 FlakyTransport 的默认创建方式。FlakyTransport 是一个第一次故意网络失败、第二次成功的假网络层。

数据流:没有额外输入 → 它直接调用 FlakyTransport::new → 出来的是一个带空状态的新 FlakyTransport。

调用关系:这是 Rust 默认构造约定的一部分。测试主要直接使用 FlakyTransport::new,但有了 default 后也能用通用方式创建它。

调用图:外部调用 1 个(new)。

FlakyTransport::new167–171 ↗
fn new() -> Self

作用:创建一个会“先摔一跤再成功”的假传输器,用来测试重试机制。它会记录每次尝试的请求体、请求头和压缩设置。

数据流:没有业务输入 → 它创建一个共享且受锁保护的 FlakyTransportState,里面初始尝试次数为 0、请求记录为空 → 出来的是 FlakyTransport。

调用关系:streaming_client_retries_on_transport_error 用它模拟第一次网络失败。ResponsesClient 调用它的 stream 时,FlakyTransport::stream 会根据尝试次数决定失败还是成功。

调用图:被 1 处调用(streaming_client_retries_on_transport_error);外部调用 3 个(new, new, default)。

FlakyTransport::attempts173–178 ↗
fn attempts(&self) -> i64

作用:读取 FlakyTransport 已经被调用了几次。测试靠它确认客户端到底有没有重试。

数据流:进去的是 FlakyTransport 内部共享状态 → 它加锁读取 attempts 数字 → 出来的是当前尝试次数,不修改状态。

调用关系:streaming_client_retries_on_transport_error 在请求结束后调用它,期望看到 2,表示第一次失败后客户端又试了一次。

FlakyTransport::requests180–186 ↗
fn requests(&self) -> Vec<(RequestBody, HeaderMap, codex_client::RequestCompression)>

作用:取出 FlakyTransport 记录过的请求信息副本。测试用它比较两次重试时,请求体、请求头和压缩设置是否一致。

数据流:进去的是内部共享状态 → 它加锁读取 requests 列表并克隆一份 → 出来的是每次尝试保存的请求体、头和压缩选项,不清空原记录。

调用关系:streaming_client_retries_on_transport_error 用它检查两次尝试的请求是否相同,尤其确认同一份编码后的 JSON 可以被重试复用。

FailsOnceAuth::transient196–203 ↗
fn transient() -> Self

作用:创建一个第一次认证会报临时错误、之后会成功的假认证器。临时错误指类似“服务暂时不可用”,通常值得重试。

数据流:没有业务输入 → 它把尝试次数设为 0,并保存一个 Transient 认证错误 → 出来的是 FailsOnceAuth。

调用关系:streaming_client_retries_on_transient_auth_error 用它验证 ResponsesClient 遇到临时认证失败时,会按照重试规则再试一次。

调用图:被 1 处调用(streaming_client_retries_on_transient_auth_error);外部调用 3 个(new, new, Transient)。

FailsOnceAuth::build205–210 ↗
fn build() -> Self

作用:创建一个第一次认证会报配置错误的假认证器。配置错误代表设置本身有问题,重试通常没有意义。

数据流:没有业务输入 → 它把尝试次数设为 0,并保存一个 Build 认证错误,内容是认证配置无效 → 出来的是 FailsOnceAuth。

调用关系:streaming_client_does_not_retry_auth_build_error 用它验证 ResponsesClient 遇到认证配置错误时会立刻失败,而不是继续重试。

调用图:被 1 处调用(streaming_client_does_not_retry_auth_build_error);外部调用 3 个(new, new, Build)。

FailsOnceAuth::attempts212–217 ↗
fn attempts(&self) -> i64

作用:读取这个假认证器已经尝试认证了几次。测试靠它判断客户端是否按预期重试认证。

数据流:进去的是 FailsOnceAuth 内部的尝试次数 → 它加锁读取数字 → 出来的是次数,不改变内部状态。

调用关系:两个认证重试相关测试都会在请求后调用它:临时错误场景期望是 2,配置错误场景期望是 1。

FailsOnceAuth::add_auth_headers238–238 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是 FailsOnceAuth 的同步加头接口,但这里故意什么都不加。这个假认证器关注的是异步认证失败和重试,不关注具体头内容。

数据流:进去的是请求头集合 → 它不做任何修改 → 出来的请求头保持原样。

调用关系:ResponsesClient 可能会通过 AuthProvider 的统一接口看到它,但真正产生失败行为的是 FailsOnceAuth::apply_auth。

FailsOnceAuth::apply_auth240–242 ↗
fn apply_auth(&self, request: Request) -> codex_api::AuthProviderFuture<'_>

作用:模拟一次需要异步执行的认证过程:第一次按预设报错,第二次开始放行请求。它用来测试客户端对认证失败的重试判断。

数据流:进去的是一个 Request 和内部尝试次数、预设错误 → 它把尝试次数加 1;如果这是第一次,就返回 Build 或 Transient 认证错误;如果不是第一次,就原样返回 Request → 它会改变尝试次数。

调用关系:ResponsesClient 准备发送请求时会调用这个认证入口。streaming_client_retries_on_transient_auth_error 依靠它第一次返回临时错误;streaming_client_does_not_retry_auth_build_error 依靠它第一次返回配置错误。

调用图:外部调用 3 个(pin, Build, Transient)。

FlakyTransport::execute246–248 ↗
async fn execute(&self, _req: Request) -> Result<Response, TransportError>

作用:这是 FlakyTransport 的非流式请求占位实现,在这些测试中不应该被走到。若走到这里,说明客户端没有使用流式通道。

数据流:进去的是一个 Request,但它不发送、不记录 → 直接返回 Build 类型错误,说明 execute 不该运行 → 没有正常响应。

调用关系:FlakyTransport 实现 HttpTransport 必须提供 execute,但 streaming_client_retries_on_transport_error 测的是 stream。这个函数用于暴露错误路径。

调用图:外部调用 1 个(Build)。

FlakyTransport::stream250–279 ↗
async fn stream(&self, req: Request) -> Result<StreamResponse, TransportError>

作用:模拟流式请求第一次网络失败、第二次成功。它是测试重试逻辑的核心假网络层。

数据流:进去的是一个带请求体的 Request → 它取出请求体,记录请求体、请求头和压缩设置,并把尝试次数加 1 → 第一次返回 Network 网络错误;第二次返回 OK 的 StreamResponse,内容是一段模拟的服务器事件数据,里面有 assistant 回复“hi”。

调用关系:streaming_client_retries_on_transport_error 让 ResponsesClient 调用它。第一次失败促使客户端按 Provider 的重试配置再发一次;第二次成功后,测试再检查记录到的两次请求是否一致。

调用图:外部调用 6 个(pin, new, Network, iter, panic!, vec!)。

responses_client_uses_responses_path283–301 ↗
async fn responses_client_uses_responses_path() -> Result<()>

作用:测试 ResponsesClient 发流式请求时,最终 URL 是否落在 /responses 接口上。这个路径错了,真实服务就可能完全不认请求。

数据流:它创建记录型传输器、无认证器和 openai Provider → 调用 client.stream 发送一个简单 JSON → 取出记录的请求并检查 URL 结尾 → 成功时返回 Ok,失败时测试报错。

调用关系:它使用 RecordingTransport::new、provider 和 assert_path_ends_with。RecordingTransport 捕获请求,assert_path_ends_with 做最终判断。

调用图:调用 4 个内部函数(new, new, assert_path_ends_with, provider);外部调用 4 个(new, new, default, json!)。

responses_client_stream_request_preserves_exact_json_body304–347 ↗
async fn responses_client_stream_request_preserves_exact_json_body() -> Result<()>

作用:测试 stream_request 会不会原样保留结构化请求序列化后的 JSON。它防止客户端在准备请求时偷偷改字段、漏字段或换了内容类型。

数据流:它构造一个 ResponsesApiRequest,并先用 serde_json 算出期望字节 → 通过 ResponsesClient 发送 → 从记录的 Request 里准备出实际要发送的 body 和 header → 比较实际字节是否等于期望字节,并确认 Content-Type 是 application/json。

调用关系:它通过 RecordingTransport 捕获请求,通过 provider 创建服务配置。这个测试重点连着 ResponsesClient::stream_request 的请求体编码行为。

调用图:调用 3 个内部函数(new, new, provider);外部调用 7 个(new, new, assert_eq!, default, default, to_vec, vec!)。

streaming_client_adds_auth_headers350–388 ↗
async fn streaming_client_adds_auth_headers() -> Result<()>

作用:测试流式客户端是否把认证头和流式接收头加到请求里。没有这些头,服务端可能拒绝请求,或者不会按流式格式返回。

数据流:它创建 StaticAuth,里面有固定 token 和账号 id → 发起一个简单流式请求 → 从 RecordingTransport 取出请求 → 检查 Authorization、ChatGPT-Account-ID 和 Accept: text/event-stream 是否都存在且值正确。

调用关系:它调用 StaticAuth::new、provider 和 RecordingTransport::new。请求准备过程中 StaticAuth::add_auth_headers 应该被 ResponsesClient 使用,最后由测试检查结果。

调用图:调用 4 个内部函数(new, new, new, provider);外部调用 6 个(new, new, assert!, assert_eq!, default, json!)。

streaming_client_retries_on_transport_error391–444 ↗
async fn streaming_client_retries_on_transport_error() -> Result<()>

作用:测试网络层第一次失败时,ResponsesClient 是否会按配置重试,并且第二次重发的内容和第一次一致。这样可以避免重试时请求体丢失或压缩状态乱掉。

数据流:它创建 FlakyTransport,并把 Provider 的最大尝试次数设为 2 → 构造一个流式 ResponsesApiRequest,启用 Zstd 压缩选项 → 调用 stream_request → FlakyTransport 第一次返回网络错误、第二次返回成功 → 最后检查尝试次数是 2、两次请求相同、JSON 字节底层指针相同、Content-Encoding 是 zstd,同时传输层压缩标记为 None。

调用关系:它依赖 FlakyTransport::new、FlakyTransport::stream、FlakyTransport::attempts 和 FlakyTransport::requests。这个测试覆盖 ResponsesClient 的网络错误重试路径。

调用图:调用 3 个内部函数(new, new, provider);外部调用 5 个(new, default, new, assert_eq!, panic!)。

streaming_client_retries_on_transient_auth_error447–469 ↗
async fn streaming_client_retries_on_transient_auth_error() -> Result<()>

作用:测试认证系统报临时错误时,客户端会不会重试。临时错误像“认证服务暂时忙”,再试一次可能就好了。

数据流:它创建 RecordingTransport 和第一次失败的 FailsOnceAuth::transient → 把最大尝试次数设为 2 → 发起流式请求 → 第一次认证失败不发网络请求,第二次认证成功后才发出请求 → 最后检查认证尝试 2 次、真正的流式请求只发送 1 次。

调用关系:它把 FailsOnceAuth 交给 ResponsesClient。ResponsesClient 调用 FailsOnceAuth::apply_auth 后,根据 Transient 错误决定重试,最终才进入 RecordingTransport::stream。

调用图:调用 4 个内部函数(new, transient, new, provider);外部调用 5 个(new, new, assert_eq!, default, json!)。

streaming_client_does_not_retry_auth_build_error472–502 ↗
async fn streaming_client_does_not_retry_auth_build_error() -> Result<()>

作用:测试认证配置错误不会被重试。因为配置错了,重复再试通常只会浪费时间,还可能掩盖真正问题。

数据流:它创建 RecordingTransport 和 FailsOnceAuth::build → 把最大尝试次数设为 2 → 发起流式请求 → 第一次认证就返回配置错误 → 客户端把它转成 Transport Build 错误返回,不再发送网络请求 → 最后检查认证只试了 1 次,请求记录为 0。

调用关系:它依赖 FailsOnceAuth::build 和 FailsOnceAuth::apply_auth 制造错误。与临时认证错误测试相对照,说明 ResponsesClient 会区分“可重试”和“不可重试”的认证失败。

调用图:调用 4 个内部函数(new, build, new, provider);外部调用 6 个(new, new, assert!, assert_eq!, default, json!)。

azure_default_store_attaches_ids_and_headers505–589 ↗
async fn azure_default_store_attaches_ids_and_headers() -> Result<()>

作用:测试 Azure 场景下,开启 store 时客户端是否带上会话、线程、子代理等专用头,并保留输入消息 id。少了这些信息,服务端可能无法把一次对话正确归档或追踪。

数据流:它创建 azure Provider、记录型传输器和一个 store=true 的 ResponsesApiRequest → 在 ResponsesOptions 里放入 session_id、thread_id、session_source、额外请求头和无压缩设置 → 发起 stream_request → 取出请求后检查 session-id、thread-id、x-client-request-id、x-openai-subagent、额外头都正确 → 再读取请求体 JSON,确认 input[0].id 仍是 msg_1。

调用关系:它使用 RecordingTransport::new 捕获最终请求,用 request_body_bytes 读取请求体。这个测试覆盖 ResponsesClient 针对 Azure Provider 的特殊头部和存储相关行为。

调用图:调用 4 个内部函数(new, new, provider, request_body_bytes);外部调用 9 个(new, new, from_static, new, SubAgent, assert_eq!, default, from_slice, vec!)。

codex-api/tests/models_integration.rs源码 ↗
test测试运行时

这个测试文件像一次小型彩排:它不去访问真实服务,而是启动一个本地假服务器,让假服务器在 /api/codex/models 收到 GET 请求时返回一份写好的模型列表。然后测试创建真正的 ModelsClient,让它像平时一样发请求、解析 JSON(一种常见的数据交换文本格式),最后检查两件事:第一,客户端拿到的模型确实是 gpt-test;第二,假服务器确实只收到了一次 GET 请求,而且路径完全正确。文件里还有一个 DummyAuth,意思是“假认证器”,它什么认证头都不加,避免测试被登录、密钥这些无关因素干扰。这样测试的重点就很清楚:只验证模型列表接口这条通路有没有接对。

函数细节3
DummyAuth::add_auth_headers28–28 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是测试用的“空认证”实现。真实客户端可能要往请求里加登录信息,但这个测试不关心认证,所以它故意什么都不做。

数据流:进去的是一组准备发送的 HTTP 请求头,也就是请求附带的小纸条;函数没有读取额外信息,也没有往里面添加任何内容;出来时请求头保持原样。

调用关系:ModelsClient 创建时需要一个 AuthProvider,也就是“能加认证信息的东西”。这个测试把 DummyAuth 交给客户端,让客户端流程能正常走完,同时不让认证逻辑影响这次对模型接口的检查。

provider31–46 ↗
fn provider(base_url: &str) -> Provider

作用:这个函数做出一个测试专用的 Provider 配置。Provider 可以理解成“告诉客户端去哪里访问服务、遇到失败怎么重试的一张说明卡”。

数据流:进去的是假服务器的基础地址;函数把它填进 Provider,并设置测试用的名字、空请求头、没有额外查询参数、很短的重试等待时间和超时时间;出来的是一个可以交给 ModelsClient 使用的服务配置。

调用关系:models_client_hits_models_endpoint 在启动假服务器后调用它,用假服务器地址拼出客户端配置。它内部用到创建空 HeaderMap、毫秒和秒级时间这些基础工具,目的是让测试快速、可控,不依赖真实线上配置。

调用图:被 1 处调用(models_client_hits_models_endpoint);外部调用 3 个(new, from_millis, from_secs)。

models_client_hits_models_endpoint49–137 ↗
async fn models_client_hits_models_endpoint()

作用:这是主测试:确认 ModelsClient 请求模型列表时,会访问 /api/codex/models,使用 GET 方法,并能把返回的模型数据读出来。

数据流:开始时它启动一个本地假服务器,准备一份包含 gpt-test 的模型列表响应,并规定只有 GET /api/codex/models 才返回这份 JSON。接着它创建网络传输工具、测试 Provider 和空认证器,再调用 list_models。调用结束后,它检查返回的模型数量和名字,也检查假服务器实际收到的请求方法和路径;如果任何一步不符合预期,测试就失败。

调用关系:这是整个文件的测试入口,由 Tokio 测试框架在运行测试时调用。它先搭好假服务器和假响应,再调用 provider 生成配置,然后把这些交给 ModelsClient;ModelsClient 发出的请求会被 wiremock 假服务器接住,最后测试函数读取假服务器记录来确认客户端行为没有跑偏。

调用图:调用 4 个内部函数(new, new, provider, new);外部调用 10 个(new, new, given, start, new, assert_eq!, format!, vec!, method, path)。

codex-api/tests/sse_end_to_end.rs源码 ↗
testtest run

这个测试文件像是在给客户端搭了一个“假服务器”。真实服务会用 SSE(Server-Sent Events,服务器一边生成一边往客户端推送的小消息)把回答分段发回来;这里不用真的连网,而是用 FixtureSseTransport 把事先写好的 SSE 文本直接喂给客户端。NoAuth 是一个空的认证提供者,意思是不加任何登录头,避免测试被账号或密钥影响。provider 函数准备一个很简单的服务配置,build_responses_body 把几段 JSON 事件拼成 SSE 格式。最后的测试会启动 ResponsesClient 的 stream 流程,读完整个流,确认它能依次得到两个“输出消息完成”事件和一个“响应完成”事件。这个文件重要在于它测的是整条解析链路,而不是只测某个小函数,能更接近真实使用时会发生的情况。

函数细节7
FixtureSseTransport::new30–32 ↗
fn new(body: String) -> Self

作用:创建一个假的 SSE 传输器,把测试用的响应正文保存起来。有人用它,是为了让客户端以为自己在读网络流,其实读的是测试提前准备好的字符串。

数据流:进去的是一整段 SSE 文本字符串 → 它把这段文本放进 FixtureSseTransport 的 body 字段里 → 出来的是一个可复制的假传输器,后面 stream 会把这段文本作为服务器返回内容吐出去。

调用关系:主测试 responses_stream_parses_items_and_completed_end_to_end 先用 build_responses_body 拼好 SSE 文本,然后调用 FixtureSseTransport::new 包装它。这个假传输器再被交给 ResponsesClient,让后面的流式读取流程不用真的访问网络。

调用图:被 1 处调用(responses_stream_parses_items_and_completed_end_to_end)。

FixtureSseTransport::execute36–38 ↗
async fn execute(&self, _req: Request) -> Result<Response, TransportError>

作用:这是普通非流式请求的占位实现,测试里不应该走到它。它故意返回错误,用来提醒:如果代码意外调用了普通 execute,就说明测试路径跑偏了。

数据流:进去的是一个请求,但这个函数完全不使用它 → 它直接构造一个 TransportError::Build 错误,错误信息说明 execute 不该运行 → 出来的是失败结果,不会产生正常响应。

调用关系:它属于 HttpTransport 接口要求必须实现的函数,但本测试关注的是 stream 流式请求。真正被测试流程使用的是 FixtureSseTransport::stream;如果 ResponsesClient 错走到 execute,这里会立刻报错,帮助定位问题。

调用图:外部调用 1 个(Build)。

FixtureSseTransport::stream40–49 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:把保存好的测试正文伪装成一次成功的流式 HTTP 响应。简单说,它就是假服务器的“出水口”,把一整段 SSE 数据送给客户端解析。

数据流:进去的是一个请求,但这里不关心请求内容 → 它把 self.body 复制成 Bytes,再放进一个只产生一次数据块的异步流里,同时设置状态码为 200 OK、响应头为空 → 出来的是 StreamResponse,客户端会像读真实网络响应一样读取它。

调用关系:ResponsesClient 在执行 stream 时会调用这个函数取得数据流。它把测试准备好的 SSE 文本交给 codex-api 的解析逻辑,后续解析出的 ResponseEvent 会被主测试逐个收集和检查。

调用图:外部调用 4 个(pin, new, iter, vec!)。

NoAuth::add_auth_headers56–56 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是一个“不做任何认证”的实现。它存在是为了满足客户端需要 AuthProvider 的接口,但测试不想掺入真实 API key 或登录信息。

数据流:进去的是一份可以被修改的 HTTP 头集合 → 它什么也不加、什么也不改 → 出来的头集合保持原样。

调用关系:主测试创建 ResponsesClient 时把 NoAuth 传进去。这样客户端照常走完整的请求构建流程,但认证这一步是空操作,测试重点就能集中在 SSE 流解析上。

provider59–74 ↗
fn provider(name: &str) -> Provider

作用:生成一个最小可用的服务提供方配置,比如名字、基础地址、重试规则和流超时时间。测试用它来让 ResponsesClient 像面对真实服务一样运行,但配置尽量简单可控。

数据流:进去的是服务提供方名称字符串 → 它填好 Provider 结构里的 base_url、空查询参数、空请求头、很短的重试延迟、关闭部分重试、设置流空闲超时 → 出来的是一个 Provider 配置对象。

调用关系:主测试 responses_stream_parses_items_and_completed_end_to_end 调用 provider("openai") 得到配置,然后把它交给 ResponsesClient::new。这个配置不是测试的主角,但没有它客户端无法按正常方式创建。

调用图:被 1 处调用(responses_stream_parses_items_and_completed_end_to_end);外部调用 2 个(from_millis, new)。

build_responses_body76–90 ↗
fn build_responses_body(events: Vec<Value>) -> String

作用:把一组 JSON 事件拼成 SSE 文本格式。它相当于把“几张事件卡片”排成服务器真实会发出的格式,方便后面假传输器直接播放。

数据流:进去的是多个 JSON 值,每个值代表一个事件 → 它读取每个事件里的 type 字段,写成 event: 某类型;如果事件只有 type,就只写事件名,否则还会加 data: 加完整 JSON → 出来的是一整段带空行分隔的 SSE 字符串。

调用关系:主测试先准备两个输出事件和一个完成事件,再调用 build_responses_body 把它们变成 SSE 正文。FixtureSseTransport::new 随后接收这段正文,stream 再把它交给客户端解析。

调用图:被 1 处调用(responses_stream_parses_items_and_completed_end_to_end);外部调用 2 个(new, format!)。

responses_stream_parses_items_and_completed_end_to_end93–170 ↗
async fn responses_stream_parses_items_and_completed_end_to_end() -> Result<()>

作用:这是核心测试:确认 ResponsesClient 能从模拟的 SSE 流里读出两个助手消息事件和一个完成事件。它验证的是从“收到流式字节”到“得到结构化响应事件”的完整路径。

数据流:开始时它构造三个 JSON 事件:两个 response.output_item.done 和一个 response.completed → 用 build_responses_body 拼成 SSE 正文,再用 FixtureSseTransport、provider 和 NoAuth 创建客户端 → 调用 client.stream 发起流式读取,循环收集所有事件,过滤掉测试不关心的速率限制事件 → 最后断言事件数量是 3,并检查前两个事件的角色是 assistant,最后一个完成事件的 response_id 是 resp1,且没有 token_usage 和 end_turn。

调用关系:这是整个文件的入口测试函数,由 tokio 测试运行器异步执行。它串起本文件里的辅助零件:build_responses_body 负责造假数据,FixtureSseTransport::new 和 FixtureSseTransport::stream 负责假装网络返回,provider 提供客户端配置,NoAuth 保持认证为空,最后把真正的解析结果拿来和预期比较。

调用图:调用 4 个内部函数(new, new, build_responses_body, provider);外部调用 8 个(new, new, new, assert!, assert_eq!, panic!, json!, vec!)。

codex-api/tests/realtime_websocket_e2e.rs源码 ↗
testtest run

这个文件解决的是“实时语音 WebSocket 客户端到底能不能和服务器按约定说话”的问题。WebSocket 可以理解成一根长期打开的电话线,客户端和服务器能不断互相发消息。测试里先在本机临时开一个假的 WebSocket 服务器,让它检查客户端发来的第一条 session.update 是否正确,再按不同场景回消息。每个测试都像一次小演练:建连接、发配置、发音频、等事件、最后关闭。它还专门测了一些容易出 bug 的地方,比如服务器晚一点才上线时能不能重试、一个任务正在等事件时另一个任务能不能同时发送音频、服务器关闭后会不会重复报奇怪的断线事件、遇到未知消息会不会卡住。最后一个测试覆盖 RealtimeV2 解析器,确认转交后台代理这类复杂事件能被拼成正确结果。

函数细节8
spawn_realtime_ws_server31–58 ↗
async fn spawn_realtime_ws_server(
    handler: Handler,
) -> (String, tokio::task::JoinHandle<()>)

作用:启动一个只服务本次测试的本地 WebSocket 假服务器。测试用它来假装真实后端,这样不用连外网,也能稳定检查客户端发了什么、收到什么。

数据流:输入是一段 handler,也就是“服务器收到连接后要怎么反应”的异步代码。函数先在 127.0.0.1 的随机空闲端口上监听,然后开一个后台任务等待客户端连进来,完成 WebSocket 握手后把连接交给 handler。输出是服务器地址字符串和后台任务句柄,测试随后用这个地址创建客户端,并在最后等待任务结束。

调用关系:多个端到端测试都会先调用它布置假服务器,包括正常建会话、并发发送、断线、忽略未知事件和 RealtimeV2 转交事件等场景。它内部把底层的监听、后台任务和 WebSocket 握手这些杂活做好,让具体测试只需要写“服务器该收什么、发什么”。

调用图:被 5 处调用(realtime_ws_e2e_disconnected_emitted_once, realtime_ws_e2e_ignores_unknown_text_events, realtime_ws_e2e_realtime_v2_parser_emits_handoff_requested, realtime_ws_e2e_send_while_next_event_waits, realtime_ws_e2e_session_create_and_event_flow);外部调用 3 个(bind, spawn, accept_async)。

test_provider60–75 ↗
fn test_provider(base_url: String) -> Provider

作用:生成一个专门给测试用的 Provider 配置。Provider 可以理解成“告诉客户端去哪里连、带什么默认设置”的地址簿。

数据流:输入是本地假服务器的 base_url。函数把它装进 Provider,并填上空查询参数、空请求头、很短的重试延迟、5 秒空闲超时等测试友好的默认值。输出是一个 Provider,后续会交给 RealtimeWebsocketClient::new 来创建客户端。

调用关系:所有这些测试都靠它快速做出一致的客户端配置。个别测试会在它返回后再改重试参数,比如“服务器晚启动还能连上”的测试会调整等待时间。

调用图:被 6 处调用(realtime_ws_connect_webrtc_sideband_retries_join_until_server_is_available, realtime_ws_e2e_disconnected_emitted_once, realtime_ws_e2e_ignores_unknown_text_events, realtime_ws_e2e_realtime_v2_parser_emits_handoff_requested, realtime_ws_e2e_send_while_next_event_waits, realtime_ws_e2e_session_create_and_event_flow);外部调用 4 个(from_millis, from_secs, new, new)。

realtime_ws_e2e_session_create_and_event_flow78–202 ↗
async fn realtime_ws_e2e_session_create_and_event_flow()

作用:测试最基本也最重要的一条链路:客户端能建实时会话,能把音频发给服务器,也能把服务器返回的音频事件解析出来。

数据流:测试先启动假服务器。服务器检查客户端第一条消息是不是 session.update,并确认会话类型、提示词、音频格式和采样率都正确;然后服务器回 session.updated。客户端读取后应得到 SessionUpdated 事件。接着客户端发送一帧音频,服务器确认收到 input_audio_buffer.append,再回一条音频 delta。客户端最后应把它变成 AudioOut 事件,并关闭连接。

调用关系:这个测试使用 spawn_realtime_ws_server 搭假服务器,用 test_provider 做连接配置,再通过 RealtimeWebsocketClient::new 创建客户端。它覆盖的是实时 WebSocket 功能的主干流程,其他测试则围绕这个主干补充异常和边界情况。

调用图:调用 3 个内部函数(new, spawn_realtime_ws_server, test_provider);外部调用 3 个(new, assert_eq!, format!)。

realtime_ws_connect_webrtc_sideband_retries_join_until_server_is_available205–280 ↗
async fn realtime_ws_connect_webrtc_sideband_retries_join_until_server_is_available()

作用:测试 WebRTC sideband 连接在服务器刚开始还没准备好时,会不会耐心重试直到连上。sideband 可以理解成主通信通道旁边的一条辅助控制线。

数据流:测试先占用一个本地端口拿到地址,然后释放它,让客户端尝试连接时服务器暂时还不存在。后台任务等 20 毫秒后才真正启动服务器。客户端用 connect_webrtc_sideband 去连这个地址;如果第一次失败,它应该按设置等待后再试。连上后服务器检查 session.update,并回 session.updated。客户端最后应收到 SessionUpdated,说明重试成功。

调用关系:它调用 test_provider 做基础配置,但会修改 retry 设置,让测试能观察重试行为。它不使用通用的 spawn_realtime_ws_server,因为这里必须精确控制“服务器晚启动”这个时间点。

调用图:调用 2 个内部函数(new, test_provider);外部调用 11 个(from_millis, new, bind, assert_eq!, format!, json!, from_str, spawn, sleep, accept_async (+1 more))。

realtime_ws_e2e_send_while_next_event_waits283–367 ↗
async fn realtime_ws_e2e_send_while_next_event_waits()

作用:测试客户端在一个地方正等待服务器事件时,另一个地方仍然可以发送音频,不会互相卡死。

数据流:假服务器先接收客户端的 session.update,然后继续等音频消息。客户端建好连接后,同时做两件事:一个任务发送音频帧,另一个任务调用 next_event 等服务器事件。服务器收到音频后才发 session.updated。测试要求发送动作在 200 毫秒内完成,并且等待事件的一边最终拿到 SessionUpdated。

调用关系:它用 spawn_realtime_ws_server 和 test_provider 准备环境,并用 tokio::join! 同时跑发送和接收。这个测试的位置很关键,因为实时语音场景里“边听边说”很常见,如果发送和接收共用锁用错了,就可能在这里暴露死锁。

调用图:调用 3 个内部函数(new, spawn_realtime_ws_server, test_provider);外部调用 4 个(new, assert_eq!, format!, join!)。

realtime_ws_e2e_disconnected_emitted_once370–411 ↗
async fn realtime_ws_e2e_disconnected_emitted_once()

作用:测试服务器主动关闭连接后,客户端能平稳结束读取,不会反复吐出错误或重复的断线事件。

数据流:假服务器收到客户端第一条 session.update 后,立刻发送 WebSocket 关闭消息。客户端调用 next_event,第一次应得到 None,表示没有更多事件;第二次再调用仍然是 None,表示连接已经干净地结束,不会制造新的假事件。

调用关系:它复用 spawn_realtime_ws_server 搭一个会主动关闭的服务器,也用 test_provider 建客户端。它专门验证连接收尾阶段的行为,补上正常收发测试覆盖不到的断线路径。

调用图:调用 3 个内部函数(new, spawn_realtime_ws_server, test_provider);外部调用 3 个(new, assert_eq!, format!)。

realtime_ws_e2e_ignores_unknown_text_events414–483 ↗
async fn realtime_ws_e2e_ignores_unknown_text_events()

作用:测试客户端遇到自己暂时不认识的文本事件时,会跳过它,而不是报错、断开或卡住。

数据流:假服务器先确认客户端发来了 session.update,然后发一条类型为 response.created 的未知事件,接着再发一条正常的 session.updated。客户端调用 next_event 时应该忽略前一条未知消息,继续读到后一条,并返回 SessionUpdated。

调用关系:它通过 spawn_realtime_ws_server 控制服务器发消息的顺序,通过 test_provider 创建客户端。这个测试保护的是兼容性:真实服务端以后可能新增事件类型,旧客户端不应该因此完全不能用。

调用图:调用 3 个内部函数(new, spawn_realtime_ws_server, test_provider);外部调用 3 个(new, assert_eq!, format!)。

realtime_ws_e2e_realtime_v2_parser_emits_handoff_requested486–632 ↗
async fn realtime_ws_e2e_realtime_v2_parser_emits_handoff_requested()

作用:测试 RealtimeV2 事件解析器能把一串语音转文字、助手回复片段和函数调用消息,整理成“请求转交后台代理”的高层事件。

数据流:假服务器先收到 session.update,然后依次发送:用户输入转写完成、助手输出转写增量、一个控制用的 conversation item、以及一个 background_agent 函数调用。客户端使用 RealtimeV2 解析器读取这些消息。前几次 next_event 分别得到输入转写、输出转写和会话条目;最后一次应得到 HandoffRequested,其中包含转交编号、条目编号、用户输入文本,以及由用户和助手文本组成的当前对话记录。

调用关系:它复用 spawn_realtime_ws_server 和 test_provider,但会把 event_parser 设置成 RealtimeV2。它验证的是更高层的事件拼装能力:底层消息不是孤立返回,而是被解析器汇总成后续系统可以直接使用的“请把任务交给后台代理”信号。

调用图:调用 3 个内部函数(new, spawn_realtime_ws_server, test_provider);外部调用 4 个(new, assert!, assert_eq!, format!)。

core/src/client_tests.rs源码 ↗
testtest run

这个文件不像正式功能代码,而是给 ModelClient 做“体检”的测试文件。ModelClient 会把本地会话、用户身份、模型请求、实时流式返回、追踪日志等东西串起来,任何一个小字段错了,后面排查问题都会很痛苦。这里先造出假的客户端、假的模型信息、假的遥测上下文,再模拟不同场景:子代理请求要带什么标签,WebSocket 连接要带什么元数据,空记忆总结要不要真的发请求,模型返回流被用户打断时追踪文件要不要记成“已取消”,以及 ChatGPT Codex 场景下是否生成安全证明头。它还用一个小的日志收集层抓取反馈标签,用一个自定义流模拟“下游来不及接收”的背压情况。整体作用就像给一辆车做刹车、灯光、安全带的专项检查,确保平时不显眼但出事很关键的部件都可靠。

函数细节22
test_model_client67–81 ↗
fn test_model_client(session_source: SessionSource) -> ModelClient

作用:快速造一个测试用的 ModelClient。测试不想真的连真实服务,所以这里用 example.com 和开源模型服务配置搭一个“假客户端”。

数据流:输入一个会话来源,比如命令行、子代理或内部任务 → 它创建一个测试用模型提供方和新的线程编号 → 输出一个配置好的 ModelClient,后续测试可以直接拿来检查请求头和元数据。

调用关系:这是多个测试的共同入口。子代理请求头测试、WebSocket 元数据测试、空记忆总结测试都会先调用它,避免每个测试重复写一大段客户端初始化代码。

调用图:调用 2 个内部函数(new, new);被 4 处调用(build_subagent_headers_sets_internal_memory_consolidation_label, build_subagent_headers_sets_other_subagent_label, build_ws_client_metadata_includes_window_lineage_and_turn_metadata, summarize_memories_returns_empty_for_empty_input);外部调用 1 个(create_oss_provider_with_base_url)。

test_responses_metadata_for_client83–101 ↗
fn test_responses_metadata_for_client(
    client: &ModelClient,
    turn_id: Option<&str>,
    window_id: String,
    parent_thread_id: Option<ThreadId>,
    request_kind: TestCodexResponsesRequestKi

作用:根据某个测试客户端生成一份 Codex Responses 请求元数据。简单说,就是把安装编号、线程编号、窗口编号、父线程等信息打包成测试里可检查的数据。

数据流:输入客户端、可选的 turn_id、窗口编号、可选父线程编号和请求类型 → 它从客户端状态里读出线程编号和会话来源 → 输出一份 CodexResponsesMetadata,供构造请求头或客户端元数据使用。

调用关系:WebSocket 元数据测试和安全证明握手测试会调用它。它把测试客户端的内部状态转换成真实代码会使用的请求元数据,然后交给 ModelClient 的构建方法继续处理。

调用图:调用 1 个内部函数(responses_metadata);被 2 处调用(build_ws_client_metadata_includes_window_lineage_and_turn_metadata, websocket_handshake_includes_attestation_for_chatgpt_codex_responses)。

test_model_info103–131 ↗
fn test_model_info() -> ModelInfo

作用:造一份测试用模型说明。模型说明就是告诉系统这个模型叫什么、支持什么能力、上下文窗口多大等。

数据流:没有外部输入 → 它用一段 JSON 写死一个名为 gpt-test 的模型配置,再反序列化成 ModelInfo → 输出可被 summarize_memories 等逻辑使用的模型信息。

调用关系:空记忆总结测试会调用它。它不参与真实网络请求,只是给被测函数一份格式正确、字段齐全的模型资料。

调用图:被 1 处调用(summarize_memories_returns_empty_for_empty_input);外部调用 2 个(json!, from_value)。

test_session_telemetry133–146 ↗
fn test_session_telemetry() -> SessionTelemetry

作用:造一个测试用的会话遥测对象。遥测可以理解成“这次会话的记录标签”,方便系统知道请求来自哪里、用的什么模型。

数据流:没有外部输入 → 它生成新的线程编号,并填入测试模型名、来源、终端名等固定信息 → 输出 SessionTelemetry,供流式响应映射和记忆总结测试使用。

调用关系:多个流式响应测试和空记忆总结测试都会用它。它给被测流程提供一致的上下文,让测试关注流处理和记录行为本身。

调用图:调用 2 个内部函数(new, new);被 4 处调用(dropped_backpressured_response_stream_traces_cancelled_partial_output, dropped_response_stream_traces_cancelled_partial_output, response_stream_records_last_model_feedback_ids, summarize_memories_returns_empty_for_empty_input)。

TagCollectorVisitor::record_str154–157 ↗
fn record_str(&mut self, field: &tracing::field::Field, value: &str)

作用:在测试里收集日志事件里的字符串字段。它像一个临时记事员,把日志标签的名字和值记下来。

数据流:输入一个日志字段和它的字符串值 → 它读取字段名,把字段名和值存进 tags 这张有序表 → 输出没有返回值,但会改变 visitor 里的 tags。

调用关系:TagCollectorLayer::on_event 在读取 feedback_tags 日志事件时会让事件把字段写进这个 visitor。它负责接住字符串形式的字段。

调用图:外部调用 1 个(name)。

TagCollectorVisitor::record_debug159–162 ↗
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug)

作用:在测试里收集不是普通字符串、但可以用调试格式打印的日志字段。这样数字、选项值等也能被记下来。

数据流:输入一个日志字段和一个可调试打印的值 → 它把值格式化成文本,再按字段名存进 tags → 输出没有返回值,但会更新 visitor 的标签表。

调用关系:它和 record_str 一起服务于 TagCollectorLayer::on_event。日志系统遇到非字符串字段时会走这里,保证测试能检查到模型请求 ID 和响应 ID。

调用图:外部调用 2 个(name, format!)。

TagCollectorLayer::on_event174–181 ↗
fn on_event(&self, event: &Event<'_>, _ctx: LayerContext<'_, S>)

作用:监听测试期间的日志事件,只挑出目标名为 feedback_tags 的事件,并把里面的标签收集起来。

数据流:输入一个 tracing 日志事件和上下文 → 它先看事件目标是不是 feedback_tags,不是就忽略;是的话就用 TagCollectorVisitor 读取字段 → 最后把收集到的标签合并到共享的 tags 表里。

调用关系:response_stream_records_last_model_feedback_ids 测试会安装这个层。它像一个旁听器,等待 map_response_events 发出反馈标签后,把标签保存起来给断言检查。

调用图:外部调用 3 个(default, metadata, record)。

started_inference_attempt184–218 ↗
fn started_inference_attempt(temp: &TempDir) -> anyhow::Result<InferenceTraceAttempt>

作用:创建一个已经开始的推理追踪尝试。推理追踪就是把一次模型调用的开始、结束、取消等过程写到文件里,方便之后回放和排查。

数据流:输入一个临时目录 → 它创建 TraceWriter,写入线程开始和回合开始事件,再启动一次模型推理尝试并记录请求内容 → 输出 InferenceTraceAttempt,后续流测试可以用它记录取消结果。

调用关系:两个“流被丢弃后要记录取消”的测试会调用它。它先把追踪环境搭好,再把 attempt 交给 map_response_events,让被测流处理逻辑在中断时写入正确状态。

调用图:调用 2 个内部函数(enabled, create);被 2 处调用(dropped_backpressured_response_stream_traces_cancelled_partial_output, dropped_response_stream_traces_cancelled_partial_output);外部调用 3 个(new, path, json!)。

output_message220–230 ↗
fn output_message(id: &str, text: &str) -> ResponseItem

作用:造一个模型输出消息。测试里用它模拟模型已经返回了一条助手回答。

数据流:输入消息 ID 和文本 → 它把文本包装成 ResponseItem::Message,角色设为 assistant → 输出一个可放进 ResponseEvent::OutputItemDone 的响应项。

调用关系:两个取消追踪测试会调用它。它提供“模型已经产出了一部分答案”的材料,用来检查中断后追踪里是否保留这部分结果。

调用图:被 2 处调用(dropped_backpressured_response_stream_traces_cancelled_partial_output, dropped_response_stream_traces_cancelled_partial_output);外部调用 1 个(vec!)。

replay_until_cancelled232–247 ↗
async fn replay_until_cancelled(temp: &TempDir) -> anyhow::Result<RolloutTrace>

作用:反复读取追踪文件,直到看到模型调用被标记为“已取消”。这是因为取消记录是异步写入的,测试需要等一小会儿。

数据流:输入临时目录 → 它最多循环 50 次读取 replay_bundle,每次检查推理状态;没看到取消就睡 10 毫秒再读 → 输出最终读到的 RolloutTrace。

调用关系:两个流取消测试在丢弃 stream 后调用它。它负责等待后台 mapper 任务把取消事件写完,然后把回放结果交给测试断言。

调用图:被 2 处调用(dropped_backpressured_response_stream_traces_cancelled_partial_output, dropped_response_stream_traces_cancelled_partial_output);外部调用 4 个(from_millis, path, replay_bundle, sleep)。

NotifyAfterEventStream::poll_next259–268 ↗
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>>

作用:实现一个测试用的异步事件流。它每次吐出一个模型事件,并在吐到指定数量时通知测试代码。

数据流:输入来自异步运行时的轮询请求 → 它从队列前面取一个 ResponseEvent;如果没有事件就保持等待;如果取到指定第几个事件,就发送通知 → 输出一个成功的事件,或者表示暂时没有新事件。

调用关系:背压测试使用这个流来精准控制时机:先把通道塞满,再确认关键输出项已经被 mapper 看见,随后丢弃消费者,测试 send 失败那条取消路径。

调用图:外部调用 2 个(Ready, pop_front)。

build_subagent_headers_sets_other_subagent_label272–281 ↗
fn build_subagent_headers_sets_other_subagent_label()

作用:检查普通自定义子代理会不会把自己的标签写进请求头。请求头可以理解成随请求附带的小纸条,服务端靠它识别请求来源。

数据流:输入没有外部参数 → 它创建一个来源为 Other("memory_consolidation") 的测试客户端,调用 build_subagent_headers → 检查 X_OPENAI_SUBAGENT_HEADER 的值是否正好是 memory_consolidation。

调用关系:它调用 test_model_client 搭测试对象,然后直接验证 ModelClient 的请求头生成行为,确保自定义子代理名称不会丢失。

调用图:调用 1 个内部函数(test_model_client);外部调用 3 个(SubAgent, assert_eq!, Other)。

build_subagent_headers_sets_internal_memory_consolidation_label284–293 ↗
fn build_subagent_headers_sets_internal_memory_consolidation_label()

作用:检查内部的“记忆整理”任务也会被标成 memory_consolidation。这样服务端看到请求时知道它不是普通聊天,而是后台整理记忆。

数据流:输入没有外部参数 → 它创建一个内部记忆整理来源的测试客户端,生成子代理请求头 → 输出是测试断言:请求头里必须包含 memory_consolidation。

调用关系:它同样通过 test_model_client 建客户端,然后验证内部会话来源到请求头标签的映射是否正确。

调用图:调用 1 个内部函数(test_model_client);外部调用 2 个(Internal, assert_eq!)。

build_ws_client_metadata_includes_window_lineage_and_turn_metadata296–356 ↗
fn build_ws_client_metadata_includes_window_lineage_and_turn_metadata()

作用:检查 WebSocket 客户端元数据里是否包含完整的会话血缘信息,比如当前线程、父线程、窗口编号和回合编号。

数据流:输入没有外部参数 → 它创建一个由父线程派生出来的子代理客户端,生成响应元数据,再调用 build_ws_client_metadata → 最后检查普通字段和 JSON 形式的 turn metadata 都包含预期值,并检查子代理标签是 collab_spawn。

调用关系:它调用 test_model_client 和 test_responses_metadata_for_client 准备材料,再测试 ModelClient 的 WebSocket 元数据构造。这个测试保护的是多线程/多窗口协作场景下的身份链路。

调用图:调用 3 个内部函数(test_model_client, test_responses_metadata_for_client, new);外部调用 4 个(SubAgent, assert_eq!, format!, from_str)。

summarize_memories_returns_empty_for_empty_input359–374 ↗
async fn summarize_memories_returns_empty_for_empty_input()

作用:检查没有记忆要总结时,函数会直接返回空结果,而不是多余地发起模型请求或制造错误。

数据流:输入没有外部参数 → 它创建测试客户端、模型信息和会话遥测,然后用空列表调用 summarize_memories → 输出应为空列表,测试断言长度为 0。

调用关系:它用 test_model_client、test_model_info 和 test_session_telemetry 搭好上下文,专门验证 summarize_memories 的一个边界情况:空输入应该安静成功。

调用图:调用 3 个内部函数(test_model_client, test_model_info, test_session_telemetry);外部调用 2 个(new, assert_eq!)。

dropped_response_stream_traces_cancelled_partial_output377–420 ↗
async fn dropped_response_stream_traces_cancelled_partial_output() -> anyhow::Result<()>

作用:检查模型流式返回已经给出一部分答案、但消费者中途停止读取时,追踪记录会标成“已取消”,并保留已经看到的输出。

数据流:输入没有外部参数 → 它建立临时追踪、模拟一个先输出消息再永远等待的模型流,交给 map_response_events;读到第一条输出后丢弃 stream → 最后回放追踪文件,确认状态是 Cancelled,且保留了一个响应项。

调用关系:它调用 started_inference_attempt、output_message、test_session_telemetry 和 replay_until_cancelled。它验证 map_response_events 在消费者被丢弃时的清理和追踪写入行为。

调用图:调用 4 个内部函数(output_message, replay_until_cancelled, started_inference_attempt, test_session_telemetry);外部调用 7 个(new, assert!, assert_eq!, OutputItemDone, iter, pending, map_response_events)。

response_stream_records_last_model_feedback_ids423–455 ↗
async fn response_stream_records_last_model_feedback_ids()

作用:检查流式响应结束后,系统会把最后一次模型请求 ID 和响应 ID 写进反馈标签。这样用户反馈或日志能关联到具体模型调用。

数据流:输入没有外部参数 → 它安装 TagCollectorLayer 收集 feedback_tags,模拟 Created 和 Completed 两个响应事件,并传给 map_response_events → 流读完后,从收集到的标签里检查 request_id 和 response_id 是否正确。

调用关系:它使用 test_session_telemetry 提供上下文,并依赖 TagCollectorLayer::on_event 收集日志。被测核心是 map_response_events 是否在完成事件后发出正确的反馈标签。

调用图:调用 2 个内部函数(test_session_telemetry, disabled);外部调用 7 个(new, new, new, assert_eq!, iter, map_response_events, registry)。

dropped_backpressured_response_stream_traces_cancelled_partial_output458–504 ↗
async fn dropped_backpressured_response_stream_traces_cancelled_partial_output() -> anyhow::Result<()>

作用:检查在“背压”情况下丢弃流时也能正确记录取消。背压就是下游接收太慢,像水管出水口堵住,发送方卡在半路。

数据流:输入没有外部参数 → 它先准备一堆 Created 事件把通道填满,再放入一个输出消息;NotifyAfterEventStream 在关键消息被吐出时通知测试 → 测试随后丢弃 stream,并回放追踪确认状态为 Cancelled 且保留了一个输出项。

调用关系:它调用 started_inference_attempt、output_message、test_session_telemetry 和 replay_until_cancelled,还使用 NotifyAfterEventStream 精准制造阻塞。它覆盖的是 map_response_events 发送给下游失败时的取消记录路径。

调用图:调用 4 个内部函数(output_message, replay_until_cancelled, started_inference_attempt, test_session_telemetry);外部调用 8 个(clone, new, new, new, new, assert_eq!, OutputItemDone, map_response_events)。

auth_request_telemetry_context_tracks_attached_auth_and_retry_phase507–523 ↗
fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase()

作用:检查鉴权请求的遥测上下文会记住是否带了认证头,以及 401 未授权后的重试恢复阶段。401 可以理解成服务端说“你没权限或登录过期”。

数据流:输入没有外部参数 → 它创建一个带 access token 的测试鉴权提供者,并构造一次 refresh_token 阶段的未授权恢复信息 → 输出是若干断言:认证模式、认证头名称、是否重试、恢复模式和阶段都必须正确。

调用关系:它直接测试 AuthRequestTelemetryContext::new 和 PendingUnauthorizedRetry::from_recovery 的组合效果,保证登录刷新和请求遥测能被正确标记。

调用图:调用 3 个内部函数(new, from_recovery, for_test);外部调用 2 个(assert!, assert_eq!)。

model_client_with_counting_attestation525–574 ↗
fn model_client_with_counting_attestation(
    include_attestation: bool,
) -> (ModelClient, Arc<AtomicUsize>)

作用:创建一个带“计数安全证明提供者”的测试客户端。安全证明可以理解成客户端给服务端的一张可信凭证;计数器用来确认到底有没有生成过。

数据流:输入一个布尔值 include_attestation → 如果为真,就创建 ChatGPT Codex 风格的客户端和测试登录;如果为假,就创建普通开源端点客户端;两种情况下都挂上 CountingAttestationProvider → 输出 ModelClient 和一个原子计数器,计数器记录安全证明生成次数。

调用关系:两个安全证明测试会调用它。它把复杂的鉴权、提供方选择和假 attestation provider 封装起来,让测试只关心“该不该生成证明”。

调用图:调用 5 个内部函数(new, from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, create_openai_provider, new);被 2 处调用(non_chatgpt_codex_endpoints_omit_attestation_generation, websocket_handshake_includes_attestation_for_chatgpt_codex_responses);外部调用 3 个(new, new, create_oss_provider_with_base_url)。

websocket_handshake_includes_attestation_for_chatgpt_codex_responses577–599 ↗
async fn websocket_handshake_includes_attestation_for_chatgpt_codex_responses()

作用:检查 ChatGPT Codex Responses 的 WebSocket 握手请求会带上安全证明头。握手就是建立 WebSocket 长连接前的第一轮请求。

数据流:输入没有外部参数 → 它创建开启安全证明的测试客户端,生成 WebSocket 连接用元数据,再异步构造握手头 → 输出是断言:请求头里有 v1.header-1,且安全证明生成器只被调用一次。

调用关系:它调用 model_client_with_counting_attestation 和 test_responses_metadata_for_client。它验证 build_websocket_headers 会在正确的 ChatGPT Codex 场景下向 attestation provider 要凭证。

调用图:调用 2 个内部函数(model_client_with_counting_attestation, test_responses_metadata_for_client);外部调用 2 个(assert_eq!, format!)。

non_chatgpt_codex_endpoints_omit_attestation_generation602–632 ↗
async fn non_chatgpt_codex_endpoints_omit_attestation_generation()

作用:检查普通非 ChatGPT Codex 端点不会生成安全证明。这样可以避免给不需要的服务多做无意义工作,也避免发出不该发的头。

数据流:输入没有外部参数 → 它创建不开启 ChatGPT Codex 安全证明条件的客户端,连续尝试为响应、压缩、实时连接生成 attestation header → 输出是断言:所有头都没有安全证明,计数器仍为 0。

调用关系:它调用 model_client_with_counting_attestation 创建普通端点客户端,然后直接测试 generate_attestation_header_for 的跳过逻辑,和 WebSocket 正向测试形成一正一反的保护。

调用图:调用 1 个内部函数(model_client_with_counting_attestation);外部调用 2 个(assert_eq!, new)。

cloud-tasks-mock-client/src/mock.rs源码 ↗
test本地开发、测试或演示时,在程序需要云任务后端能力的各个调用点活跃

真实的云任务服务可能要联网、要登录、还可能不稳定。这个文件就像一个电影道具柜:外表和真的 CloudBackend(云任务后端接口,也就是程序期待调用的一套能力)一样,但里面放的是固定的假数据。MockClient 会返回几条预设任务,不同环境名会看到不同列表;每个任务还能拿到一段假的代码改动 diff(代码差异文本),并统计加了几行、删了几行。它还会假装“应用任务成功”、假装“预检查通过”、假装创建了一个新任务。这样上层界面或命令行不用关心背后是真是假,都能照常跑完整流程。重要的是,它不会真的改云端数据,也不会真的联系服务器,所以很适合测试流程是否顺、页面是否显示正常。

函数细节11
MockClient::list_tasks164–171 ↗
fn list_tasks(
        &'a self,
        env: Option<&'a str>,
        limit: Option<i64>,
        cursor: Option<&'a str>,
    ) -> CloudBackendFuture<'a, TaskListPage>

作用:返回一页假的任务列表。别人用它来模拟“从云端拉取任务”,但实际数据是文件里写死的。

数据流:进去的是可选的环境名、数量限制和翻页游标;它主要看环境名,如果是 env-A 或 env-B 就返回对应的假任务,否则返回默认任务。然后它给每个任务补上标题、状态、更新时间、环境显示名,并通过 mock_diff_for 拿到假 diff,再用 count_from_unified 统计增加和删除的行数;出来的是一个 TaskListPage,里面有任务数组,游标固定为空。

调用关系:这是 mock 后端里最基础的入口之一。上层想显示任务列表时会走到它;MockClient::get_task_summary 也会先调用它,再从列表里挑出某一个任务。它把生成差异文本的活交给 mock_diff_for,把统计行数的活交给 count_from_unified。

调用图:调用 2 个内部函数(count_from_unified, mock_diff_for);被 1 处调用(get_task_summary);外部调用 5 个(pin, now, new, new, vec!)。

MockClient::get_task_summary173–175 ↗
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>

作用:根据任务编号找到某个假的任务摘要。它让上层可以像查真实任务详情一样查 mock 数据。

数据流:进去的是一个 TaskId,也就是任务编号;它先调用 MockClient::list_tasks 拿默认环境下的假任务列表,再逐个比较编号。找到了就返回那条 TaskSummary;找不到就返回一个错误,说明这个 mock 任务不存在。

调用关系:它站在“按编号查任务”的位置,但自己不重新造数据,而是复用 MockClient::list_tasks。这样任务摘要和列表里看到的数据保持一致。

调用图:调用 1 个内部函数(list_tasks);外部调用 1 个(pin)。

MockClient::get_task_diff177–179 ↗
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>

作用:返回某个假任务对应的代码差异文本。上层用它来模拟查看任务会改哪些文件。

数据流:进去的是任务编号;它把编号交给 mock_diff_for,拿回一段预设的 unified diff(常见的代码改动文本格式),再包装成 Some 返回。它不检查任务是否真的存在,也不访问磁盘或网络。

调用关系:当界面或命令想展示某个任务的改动内容时会用到它。真正生成假 diff 的细节在 mock_diff_for 里,这个函数只是把后端接口需要的结果形式准备好。

调用图:调用 1 个内部函数(mock_diff_for);外部调用 1 个(pin)。

MockClient::get_task_messages181–183 ↗
fn get_task_messages(&self, id: TaskId) -> CloudBackendFuture<'_, Vec<String>>

作用:返回某个任务的假对话消息。它用来模拟“助手曾经对这个任务说了什么”。

数据流:进去的是任务编号,但这里不会根据编号变化;它直接返回一个字符串列表,里面只有一条固定的 mock 助手输出。它不改任何状态。

调用关系:上层查看任务消息流时会调用它。它是 CloudBackend 这套接口的一部分,用固定内容填补真实服务里的聊天记录能力。

调用图:外部调用 2 个(pin, vec!)。

MockClient::get_task_text185–187 ↗
fn get_task_text(&self, id: TaskId) -> CloudBackendFuture<'_, TaskText>

作用:返回任务的完整文字信息,包括提示词、消息、轮次编号和尝试状态。它让需要“完整任务上下文”的功能也能在 mock 模式下运行。

数据流:进去的是任务编号,但内容同样是固定的;它构造一个 TaskText,里面有一条提示词、一条助手消息、一个假的 turn_id、空的兄弟轮次列表、尝试位置 0,以及 Completed(已完成)状态;出来的是这个 TaskText。

调用关系:当上层不只要消息,而是要任务文本、轮次和尝试状态这些更完整信息时会调用它。它不再调用其他本地函数,只是直接拼好一份假资料。

调用图:外部调用 3 个(pin, new, vec!)。

MockClient::apply_task189–195 ↗
fn apply_task(
        &self,
        id: TaskId,
        diff_override: Option<String>,
    ) -> CloudBackendFuture<'_, ApplyOutcome>

作用:假装把某个任务的改动应用到了本地。它用于测试“应用任务”这条流程,不会真的处理冲突或改云端。

数据流:进去的是任务编号和一个可选的 diff 覆盖内容;这里忽略覆盖内容,直接构造 ApplyOutcome。出来的结果表示 applied 为 true、状态为成功、消息里写着这个任务已在本地应用,跳过路径和冲突路径都是空列表。

调用关系:上层点击或执行“应用任务”时会走到它。在 mock 模式下,它扮演真实应用器的替身,给调用方一个成功结果,让后续界面或流程可以继续测试。

调用图:外部调用 3 个(pin, new, format!)。

MockClient::apply_task_preflight197–203 ↗
fn apply_task_preflight(
        &self,
        id: TaskId,
        diff_override: Option<String>,
    ) -> CloudBackendFuture<'_, ApplyOutcome>

作用:假装先检查任务能不能应用,但不真正应用。preflight 可以理解成“起飞前检查”:只看会不会出问题,不动手改东西。

数据流:进去的是任务编号和可选 diff 覆盖内容;它忽略覆盖内容,返回一个 ApplyOutcome。结果里 applied 为 false,表示没有真的应用;状态是成功,消息说明预检查通过,跳过和冲突路径为空。

调用关系:在真正应用任务前,上层可能先调用它确认没有明显问题。它和 MockClient::apply_task 很像,但特意把 applied 设为 false,用来表达“只是检查”。

调用图:外部调用 3 个(pin, new, format!)。

MockClient::list_sibling_attempts205–211 ↗
fn list_sibling_attempts(
        &self,
        task: TaskId,
        turn_id: String,
    ) -> CloudBackendFuture<'_, Vec<TurnAttempt>>

作用:列出同一个任务的其他尝试版本。它用来模拟“这个任务曾经生成过不止一个答案”。

数据流:进去的是任务编号和当前轮次编号;如果任务是 T-1000,它返回一个假的备用尝试,里面有创建时间、完成状态、对应 diff 和一条消息。其他任务则返回空列表。

调用关系:当上层想展示同一任务的其他候选尝试时会调用它。它在 T-1000 上故意返回数据,是为了让测试能覆盖“有兄弟尝试”的界面和流程。

调用图:外部调用 3 个(pin, new, vec!)。

MockClient::create_task213–224 ↗
fn create_task(
        &'a self,
        env_id: &'a str,
        prompt: &'a str,
        git_ref: &'a str,
        qa_mode: bool,
        best_of_n: usize,
    ) -> CloudBackendFuture<'a, CreatedTa

作用:假装创建一个新云任务,并返回一个新任务编号。它让创建任务的流程不用真实后端也能跑通。

数据流:进去的是环境编号、用户提示词、git 引用、是否 QA 模式、以及 best_of_n 数量;这些参数会被接收但不真正使用。它用当前时间毫秒数拼出一个 task_local_ 开头的编号,然后返回 CreatedTask。

调用关系:上层提交新任务时会调用它。在 mock 模式下,它只负责给一个看起来唯一的本地编号,不会保存任务内容,也不会通知任何服务器。

调用图:外部调用 3 个(pin, new, format!)。

mock_diff_for227–239 ↗
fn mock_diff_for(id: &TaskId) -> String

作用:根据任务编号生成一段固定的假代码改动文本。它是 mock 任务“看起来真的有改动”的来源。

数据流:进去的是任务编号;如果是 T-1000,就返回 README 的改动;如果是 T-1001,就返回 core/src/lib.rs 的改动;其他编号返回新增 CONTRIBUTING.md 的改动。出来的是一整段 diff 字符串。

调用关系:MockClient::list_tasks 用它给每个任务配上可统计的改动内容,MockClient::get_task_diff 用它直接展示某个任务的改动。它是这个 mock 客户端里假 diff 数据的集中仓库。

调用图:被 2 处调用(get_task_diff, list_tasks)。

count_from_unified241–267 ↗
fn count_from_unified(diff: &str) -> (usize, usize)

作用:数一段 diff 里新增了几行、删除了几行。这样任务列表能显示“改动规模”,而不是只有标题。

数据流:进去的是 diff 字符串;它先尝试用 diffy::Patch::from_str 解析成结构化补丁,如果成功,就逐行统计 Insert(新增)和 Delete(删除)。如果解析失败,它就退一步用简单规则扫描每一行,跳过文件头和块头,只数以 + 或 - 开头的内容行;出来的是两个数字:新增行数和删除行数。

调用关系:MockClient::list_tasks 在生成每个任务摘要时调用它,用统计结果填充 DiffSummary。它不关心任务编号,只关心 diff 文本本身,所以是一个小工具函数。

调用图:被 1 处调用(list_tasks);外部调用 1 个(from_str)。

codex-client/tests/ca_env.rs源码 ↗
testtest run

CA 证书可以理解成“信任名单”:客户端要连 HTTPS 网站时,会看服务器证书是不是由名单里的机构签发。这个测试文件就是防止自定义信任名单出错。它不会直接在当前测试进程里调用内部函数,而是启动一个叫 custom_ca_probe 的小程序,像真实用户运行程序一样测试环境变量。为了避免开发者电脑或 CI 环境里的代理、证书变量偷偷影响结果,它每次都会先清掉相关环境变量。测试内容包括:CODEX_CA_CERTIFICATE 优先于 SSL_CERT_FILE、证书包里有多个证书也能读、空 PEM 或坏 PEM 会给用户看得懂的提示、OpenSSL 风格的可信证书和夹杂 CRL 的文件也能接受。更进一步,它还现场生成测试 CA 和服务器证书,启动 TLS 1.3 服务,甚至启动一个会拦截 HTTPS 的 CONNECT 代理,确认客户端真的能用自定义 CA 完成 POST 请求。

函数细节26
write_cert_file84–88 ↗
fn write_cert_file(temp_dir: &TempDir, name: &str, contents: &str) -> PathBuf

作用:把一段证书文字写进临时目录里的文件,方便后面的测试把这个文件当成 CA 证书来用。它相当于给测试现场临时准备一张“信任名单纸”。

数据流:输入是临时目录、文件名和证书内容 → 它拼出完整路径并把内容写到磁盘 → 输出这个证书文件的路径,供启动探测程序时放进环境变量。

调用关系:许多测试都会先调用它造出不同的证书文件,比如正常证书、空文件、坏格式文件、证书包。随后这些路径会交给 run_probe 或专门的 POST 探测函数使用。

调用图:被 10 处调用(accepts_bundle_with_crl, accepts_openssl_trusted_certificate, falls_back_to_ssl_cert_file, handles_multi_certificate_bundle, posts_to_tls13_server_using_custom_ca_bundle, posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle, prefers_codex_ca_cert_over_ssl_cert_file, rejects_empty_pem_file_with_hint, rejects_malformed_pem_with_hint, uses_codex_ca_cert_env);外部调用 2 个(path, write)。

probe_command90–105 ↗
fn probe_command() -> Command

作用:准备一个干净的 custom_ca_probe 子进程命令。它最重要的事是清掉会影响证书和代理选择的环境变量,避免测试被机器本身的配置污染。

数据流:它没有外部输入 → 找到 custom_ca_probe 这个测试用二进制程序,并从命令环境里移除 CA、URL、代理等变量 → 输出一个还没运行的 Command,后续函数可以继续往里面加指定环境变量。

调用关系:run_probe、run_probe_posting_to_tls13_server 和 run_probe_posting_through_tls_intercepting_proxy 都从这里拿到基础命令。它是所有子进程测试的共同起点,保证每个测试都从同一张“干净桌面”开始。

调用图:被 3 处调用(run_probe, run_probe_posting_through_tls_intercepting_proxy, run_probe_posting_to_tls13_server);外部调用 2 个(new, cargo_bin)。

run_probe107–113 ↗
fn run_probe(envs: &[(&str, &Path)]) -> std::process::Output

作用:用指定的环境变量运行 custom_ca_probe,检查普通的自定义 CA 加载行为。它适合那些只关心证书文件能不能被识别的测试。

数据流:输入是一组环境变量名和路径 → 它先创建干净命令,再把这些变量加进去并运行子进程 → 输出子进程的运行结果,包括退出状态、标准输出和错误输出。

调用关系:大部分基础测试都会调用它,比如测试 CODEX_CA_CERTIFICATE、SSL_CERT_FILE、坏 PEM 提示等。它把真正启动进程的细节藏起来,让测试只关心结果。

调用图:调用 1 个内部函数(probe_command);被 8 处调用(accepts_bundle_with_crl, accepts_openssl_trusted_certificate, falls_back_to_ssl_cert_file, handles_multi_certificate_bundle, prefers_codex_ca_cert_over_ssl_cert_file, rejects_empty_pem_file_with_hint, rejects_malformed_pem_with_hint, uses_codex_ca_cert_env)。

run_probe_posting_to_tls13_server115–123 ↗
fn run_probe_posting_to_tls13_server(envs: &[(&str, &Path)], url: &str) -> std::process::Output

作用:运行 custom_ca_probe,并要求它向一个本地 TLS 1.3 HTTPS 服务发 POST 请求。TLS 1.3 是一种较新的 HTTPS 加密握手版本,这里用来确认自定义 CA 在真实加密连接里有效。

数据流:输入是证书环境变量和目标 URL → 它准备干净命令,设置证书变量,再打开 TLS 1.3 探测开关并设置请求地址 → 输出子进程运行结果。

调用关系:posts_to_tls13_server_using_custom_ca_bundle 会用它去连接 spawn_tls13_test_server 启动的本地 HTTPS 服务。它负责客户端这边的启动,服务器那边由测试辅助函数负责。

调用图:调用 1 个内部函数(probe_command);被 1 处调用(posts_to_tls13_server_using_custom_ca_bundle)。

run_probe_posting_through_tls_intercepting_proxy125–138 ↗
fn run_probe_posting_through_tls_intercepting_proxy(
    envs: &[(&str, &Path)],
    url: &str,
    proxy_url: &str,
) -> std::process::Output

作用:运行 custom_ca_probe,并让它通过一个会拦截 TLS 的代理去发 POST 请求。这个场景模拟公司代理或安全网关重新签发网站证书的情况。

数据流:输入是证书环境变量、目标 URL 和代理 URL → 它准备干净命令,设置证书、代理、TLS 1.3 开关和请求地址 → 输出子进程运行结果。

调用关系:posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle 调用它。它把请求交给本地代理,代理再转发给本地源站,用来验证自定义 CA 能信任代理伪装出来的 HTTPS 证书。

调用图:调用 1 个内部函数(probe_command);被 1 处调用(posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle)。

spawn_tls13_test_server140–169 ↗
fn spawn_tls13_test_server() -> Tls13TestServer

作用:启动一个本地 HTTPS 测试服务器,只使用 TLS 1.3,并返回它的地址和用于信任它的 CA 证书。它让测试可以在自己电脑上完成真实 HTTPS 请求,而不依赖外网。

数据流:它生成一套 CA 和服务器证书 → 绑定本机随机端口,配置 rustls TLS 服务,并开一个后台线程等待请求 → 输出服务器 URL、CA 证书文本,以及一个接收请求内容的通道。

调用关系:posts_to_tls13_server_using_custom_ca_bundle 会先调用它搭好服务器,再把返回的 CA 写入文件,最后让探测程序访问这个 URL。后台线程内部会把接收到的请求交给 accept_tls13_request 处理。

调用图:调用 1 个内部函数(generate_tls13_material);被 1 处调用(posts_to_tls13_server_using_custom_ca_bundle);外部调用 8 个(new, bind, ensure_rustls_crypto_provider, format!, channel, builder_with_protocol_versions, spawn, vec!)。

spawn_plain_http_origin171–191 ↗
fn spawn_plain_http_origin() -> PlainHttpOrigin

作用:启动一个普通的本地 HTTP 源站,用来接收代理转发过来的请求。这里的“源站”就是最终真正处理请求的服务器。

数据流:它绑定本机随机端口,创建一个后台线程等待请求 → 后台线程读取 HTTP 请求并回复 ok → 输出看起来是 https 的目标 URL 和一个接收请求内容的通道。

调用关系:代理测试会调用它作为最终目的地。spawn_tls_intercepting_proxy 拦截并解密客户端请求后,会把请求转发到这个源站,测试再从通道里确认源站确实收到了正确 POST。

调用图:被 1 处调用(posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle);外部调用 4 个(bind, format!, channel, spawn)。

spawn_tls_intercepting_proxy193–222 ↗
fn spawn_tls_intercepting_proxy() -> TlsInterceptingProxy

作用:启动一个本地 CONNECT 代理,并在客户端以为自己连到目标网站时,接管 TLS 加密连接。它模拟会检查 HTTPS 流量的企业代理。

数据流:它生成测试 CA 和代理用服务器证书 → 绑定本机随机端口,配置 TLS 1.3 服务,并启动后台线程 → 输出代理 URL、代理 CA 证书文本,以及用于拿到被代理请求的通道。

调用关系:代理场景测试会先启动它,再把它的 CA 证书交给 custom_ca_probe。后台线程会调用 accept_tls_intercepting_proxy_request,完成 CONNECT、TLS 握手、转发请求和回传响应。

调用图:调用 1 个内部函数(generate_tls13_material);被 1 处调用(posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle);外部调用 8 个(new, bind, ensure_rustls_crypto_provider, format!, channel, builder_with_protocol_versions, spawn, vec!)。

generate_tls13_material224–255 ↗
fn generate_tls13_material() -> Tls13Material

作用:现场生成一套测试用证书材料:一个 CA 证书、一张由它签发的服务器证书和服务器私钥。这样测试不需要依赖外部证书文件。

数据流:它先创建一个能签发证书的 CA → 再创建包含 localhost 和 127.0.0.1 的服务器证书,并用这个 CA 签名 → 输出 CA 的 PEM 文本、服务器证书和私钥。

调用关系:spawn_tls13_test_server 和 spawn_tls_intercepting_proxy 都调用它。前者用来让本地 HTTPS 服务有证书,后者用来让拦截代理能伪装成目标 HTTPS 服务。

调用图:被 2 处调用(spawn_tls13_test_server, spawn_tls_intercepting_proxy);外部调用 8 个(default, new, self_signed, new, Ca, generate_for, from, vec!)。

accept_plain_http_origin_request257–267 ↗
fn accept_plain_http_origin_request(listener: TcpListener) -> io::Result<String>

作用:在普通 HTTP 源站里接收一次请求,读完整个 HTTP 消息,然后返回一个简单成功响应。它是代理测试里最终服务器的接待员。

数据流:输入是已经监听的 TCP 端口 → 它等客户端连接,设置读写超时,读完整 HTTP 请求,再写回 200 OK → 输出收到的请求文本。

调用关系:spawn_plain_http_origin 的后台线程会使用它。它依赖 accept_with_timeout 等连接,依赖 read_http_message 读请求;测试最后通过通道拿到它返回的请求。

调用图:调用 2 个内部函数(accept_with_timeout, read_http_message);外部调用 1 个(from_secs)。

accept_tls13_request269–284 ↗
fn accept_tls13_request(
    listener: TcpListener,
    config: Arc<rustls::ServerConfig>,
) -> io::Result<String>

作用:在 TLS 1.3 HTTPS 测试服务器里接收一次加密请求,并返回简单成功响应。它用来验证客户端真的完成了 HTTPS 握手和 POST。

数据流:输入是监听端口和 TLS 服务配置 → 它等待连接,把普通 TCP 流包装成 TLS 加密流,读 HTTP 请求,再通过 TLS 写回 200 OK → 输出收到的请求文本。

调用关系:spawn_tls13_test_server 的后台线程会调用它。它先用 accept_with_timeout 拿连接,再用 read_http_message 读请求;主测试随后检查这段请求是不是 token exchange。

调用图:调用 2 个内部函数(accept_with_timeout, read_http_message);外部调用 3 个(from_secs, new, new)。

accept_tls_intercepting_proxy_request286–314 ↗
fn accept_tls_intercepting_proxy_request(
    listener: TcpListener,
    config: Arc<rustls::ServerConfig>,
) -> io::Result<String>

作用:处理一次通过拦截代理发来的 HTTPS 请求。它先接 CONNECT,再建立 TLS,再把解密后的请求转发到真实源站,最后把源站响应送回客户端。

数据流:输入是代理监听端口和 TLS 配置 → 它读 CONNECT 请求,取出目标地址,告诉客户端连接已建立,然后进行 TLS 握手,读取真正的 HTTP 请求,转发给源站并读取响应 → 输出代理看到的请求文本。

调用关系:spawn_tls_intercepting_proxy 的后台线程会调用它。它把解析目标地址的工作交给 connect_authority_from_request,把读消息交给 read_http_message,把等连接交给 accept_with_timeout。

调用图:调用 3 个内部函数(accept_with_timeout, connect_authority_from_request, read_http_message);外部调用 4 个(from_secs, connect, new, new)。

connect_authority_from_request316–329 ↗
fn connect_authority_from_request(request: &str) -> io::Result<String>

作用:从 HTTP CONNECT 请求的第一行里取出目标主机和端口。CONNECT 可以理解成客户端对代理说:“请帮我打通到这个地址的隧道”。

数据流:输入是一整段 CONNECT 请求文本 → 它读取第一行并按空格拆开,确认格式是 CONNECT 目标地址 HTTP版本 → 成功时输出目标地址,失败时输出格式错误。

调用关系:accept_tls_intercepting_proxy_request 在决定把请求转发到哪里之前会调用它。它只做解析,不碰网络连接。

调用图:被 1 处调用(accept_tls_intercepting_proxy_request);外部调用 2 个(new, format!)。

accept_with_timeout331–348 ↗
fn accept_with_timeout(listener: TcpListener, timeout: Duration) -> io::Result<TcpStream>

作用:等待一个 TCP 客户端连进来,但不会无限等下去。它防止测试卡死,像给门口接待员设了一个下班时间。

数据流:输入是监听器和最长等待时间 → 它反复尝试 accept,如果暂时没人连接就短暂睡眠,直到成功、超时或出现其他错误 → 输出连接进来的 TCP 流,或者返回超时错误。

调用关系:普通源站、TLS 服务器和拦截代理接收请求时都会用它。它是这些本地测试服务器的共同保护措施,避免客户端失败时整个测试一直挂住。

调用图:被 3 处调用(accept_plain_http_origin_request, accept_tls13_request, accept_tls_intercepting_proxy_request);外部调用 5 个(from_millis, now, accept, new, sleep)。

read_http_message350–377 ↗
fn read_http_message(stream: &mut impl Read) -> io::Result<String>

作用:从网络流里读出一整条 HTTP 消息,包括请求头和按 Content-Length 指定长度的正文。HTTP 消息可以理解成一封有信封信息和正文的网络信件。

数据流:输入是一个可读取的数据流,可能是普通 TCP,也可能是 TLS 加密流 → 它分块读取字节,找到 HTTP 头结束位置,再根据 Content-Length 判断正文是否读完 → 输出完整消息文本。

调用关系:accept_plain_http_origin_request、accept_tls13_request 和 accept_tls_intercepting_proxy_request 都靠它读请求或响应。它让不同服务器处理函数不用重复写 HTTP 读取逻辑。

调用图:被 3 处调用(accept_plain_http_origin_request, accept_tls13_request, accept_tls_intercepting_proxy_request);外部调用 3 个(read, from_utf8_lossy, new)。

assert_token_exchange_request379–388 ↗
fn assert_token_exchange_request(request: &str)

作用:检查收到的请求是不是测试期望的 token 交换请求。也就是确认客户端确实向 /oauth/token 发了 POST,并带上指定表单内容。

数据流:输入是一段 HTTP 请求文本 → 它检查开头是否是 POST /oauth/token HTTP/1.1,并检查正文里是否有 grant_type=authorization_code&code=test → 没问题就通过,有问题就让测试失败并打印请求。

调用关系:真实 POST 场景的两个测试都会调用它。TLS 服务器测试用它检查服务器收到的请求,代理测试用它同时检查代理看到的请求和源站收到的请求。

调用图:被 2 处调用(posts_to_tls13_server_using_custom_ca_bundle, posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle);外部调用 1 个(assert!)。

uses_codex_ca_cert_env391–398 ↗
fn uses_codex_ca_cert_env()

作用:测试 CODEX_CA_CERTIFICATE 这个专用环境变量是否会被用作 CA 证书来源。它确认项目自己的设置入口是有效的。

数据流:它创建临时目录,写入一份正常 CA 证书 → 用 CODEX_CA_CERTIFICATE 指向这个文件运行探测程序 → 期望子进程成功退出。

调用关系:它通过 write_cert_file 准备证书,通过 run_probe 启动干净子进程。这个测试覆盖最主要的自定义 CA 使用方式。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 2 个(new, assert!)。

falls_back_to_ssl_cert_file401–408 ↗
fn falls_back_to_ssl_cert_file()

作用:测试没有专用变量时,程序会退回使用 SSL_CERT_FILE。SSL_CERT_FILE 是很多工具通用的证书文件环境变量。

数据流:它创建临时证书文件 → 只设置 SSL_CERT_FILE 后运行探测程序 → 期望探测程序成功,说明备用入口可用。

调用关系:它和 uses_codex_ca_cert_env 类似,也通过 write_cert_file 和 run_probe 完成。它验证的是兼容通用环境变量的路径。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 2 个(new, assert!)。

prefers_codex_ca_cert_over_ssl_cert_file411–422 ↗
fn prefers_codex_ca_cert_over_ssl_cert_file()

作用:测试当两个证书环境变量同时存在时,程序优先使用 CODEX_CA_CERTIFICATE。这样用户明确给项目设置的证书不会被系统通用变量抢走。

数据流:它写一个正常证书文件和一个空的坏文件 → CODEX_CA_CERTIFICATE 指向好文件,SSL_CERT_FILE 指向坏文件 → 运行探测程序并期望成功,说明程序选了好文件。

调用关系:它调用 write_cert_file 两次准备对照,再用 run_probe 启动子进程。这个测试锁定了证书来源的优先级规则。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 2 个(new, assert!)。

handles_multi_certificate_bundle425–433 ↗
fn handles_multi_certificate_bundle()

作用:测试一个 PEM 文件里放多个证书时能不能正常读取。PEM 是常见的文本证书格式,证书包就像一本装了多张证书的册子。

数据流:它把两份测试证书拼成一个文件 → 用 CODEX_CA_CERTIFICATE 指向这个证书包运行探测程序 → 期望成功,说明程序能逐张读出证书。

调用关系:它通过 write_cert_file 造证书包,通过 run_probe 验证加载。这个测试覆盖现实中很常见的 CA bundle 文件。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 3 个(new, assert!, format!)。

posts_to_tls13_server_using_custom_ca_bundle436–455 ↗
fn posts_to_tls13_server_using_custom_ca_bundle()

作用:测试客户端能不能用自定义 CA 信任本地 TLS 1.3 服务器,并完成真实 POST 请求。它证明不只是证书文件能解析,网络连接也真的可用。

数据流:它启动本地 TLS 1.3 服务器,写入服务器对应的 CA 证书 → 运行探测程序访问服务器 URL → 从服务器线程取回请求并检查请求内容,期望子进程成功。

调用关系:它串起 spawn_tls13_test_server、write_cert_file、run_probe_posting_to_tls13_server 和 assert_token_exchange_request。这个测试是自定义 CA 功能的端到端检查。

调用图:调用 4 个内部函数(assert_token_exchange_request, run_probe_posting_to_tls13_server, spawn_tls13_test_server, write_cert_file);外部调用 3 个(from_secs, new, assert!)。

posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle458–486 ↗
fn posts_to_token_origin_through_tls_intercepting_proxy_with_custom_ca_bundle()

作用:测试通过 TLS 拦截代理时,自定义 CA 是否仍然能让请求成功。这个场景贴近公司网络里 HTTPS 被代理检查的情况。

数据流:它启动普通源站和 TLS 拦截代理,把代理的 CA 写入文件 → 让探测程序通过代理访问源站 URL → 分别从代理和源站拿到请求,确认两边都看到正确 POST,且子进程成功。

调用关系:它调用 spawn_plain_http_origin 和 spawn_tls_intercepting_proxy 搭环境,再用 run_probe_posting_through_tls_intercepting_proxy 发请求,最后用 assert_token_exchange_request 检查结果。

调用图:调用 5 个内部函数(assert_token_exchange_request, run_probe_posting_through_tls_intercepting_proxy, spawn_plain_http_origin, spawn_tls_intercepting_proxy, write_cert_file);外部调用 3 个(from_secs, new, assert!)。

rejects_empty_pem_file_with_hint489–500 ↗
fn rejects_empty_pem_file_with_hint()

作用:测试空的 PEM 文件会被拒绝,并且错误信息会提示用户该检查哪些环境变量。它保证失败时不是只给一串难懂错误。

数据流:它写一个空证书文件 → 用 CODEX_CA_CERTIFICATE 指向它运行探测程序 → 期望失败,并检查 stderr 里包含“没有证书”和两个相关环境变量名。

调用关系:它通过 write_cert_file 制造坏输入,通过 run_probe 获取子进程错误输出。这个测试关注用户看到的报错质量。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 3 个(from_utf8_lossy, new, assert!)。

rejects_malformed_pem_with_hint503–518 ↗
fn rejects_malformed_pem_with_hint()

作用:测试格式坏掉的 PEM 文件会被拒绝,并给出带提示的错误。它防止用户拿到一个模糊的解析失败信息后不知道该改哪里。

数据流:它写入一段开头像证书但内容不完整的 PEM → 运行探测程序 → 期望失败,并检查错误输出包含解析失败说明和相关环境变量名。

调用关系:它和空文件测试一样走 write_cert_file 与 run_probe,但输入是“坏格式”而不是“没内容”。两者一起覆盖常见证书文件错误。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 3 个(from_utf8_lossy, new, assert!)。

accepts_openssl_trusted_certificate521–528 ↗
fn accepts_openssl_trusted_certificate()

作用:测试程序能接受 OpenSSL 生成的 trusted certificate 格式。OpenSSL 是常见加密工具,它的某些证书文件会带额外信任信息。

数据流:它把测试用 trusted certificate 写进临时文件 → 用 CODEX_CA_CERTIFICATE 指向它运行探测程序 → 期望成功。

调用关系:它用 write_cert_file 准备 fixture,再交给 run_probe。这个测试保证程序兼容现实世界里 OpenSSL 可能产出的证书格式。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 2 个(new, assert!)。

accepts_bundle_with_crl531–540 ↗
fn accepts_bundle_with_crl()

作用:测试证书包里夹着 CRL 时仍然能读取证书。CRL 是“证书吊销列表”,表示哪些证书已作废;这里重点是它不该干扰 CA 证书读取。

数据流:它把正常 CA 证书和一段 CRL 文本拼到同一个文件 → 用 CODEX_CA_CERTIFICATE 指向这个文件运行探测程序 → 期望成功,说明程序会忽略或跳过不需要的 CRL 内容。

调用关系:它用 write_cert_file 制作混合证书包,再用 run_probe 检查结果。这个测试覆盖企业证书包里可能混有其他 PEM 区块的情况。

调用图:调用 2 个内部函数(run_probe, write_cert_file);外部调用 3 个(new, assert!, format!)。

提示词与工具渲染测试

这些测试固定在准备面向模型的输入和输出时使用的提示词/模板输出,以及工具侧规范化辅助工具。

prompts/src/goals_tests.rs源码 ↗
testtest

这是一组测试,像给提示词模板做“质检”。系统会把一个线程目标包装成提示词,交给模型继续工作。这里检查几件关键事:普通继续时,提示词要清楚告诉模型目标是什么、预算是多少、什么时候可以标记完成,什么时候才算真正卡住;预算超限时,提示词要催模型尽快收尾,但不能让它进入不存在或不该用的“暂停”状态;用户改了目标时,新目标必须覆盖旧目标,并提醒模型不要随便更新状态。最后还有一个安全测试:如果目标文字里故意塞进像 XML 标签一样的内容,提示词必须把它转义,意思是把危险符号变成普通文字,避免模型把用户内容误当成系统指令。简单说,这个文件防止“给模型的说明书”写错、漏写,或被用户输入污染。

函数细节4
continuation_prompt_allows_complete_and_strict_blocked_updates6–30 ↗
fn continuation_prompt_allows_complete_and_strict_blocked_updates()

作用:这个测试检查普通“继续完成目标”的提示词是否足够明确。它要确认模型既能被允许在完成时更新为 complete,也只有在严格条件下才能说 blocked。

数据流:进去的是一个模拟的线程目标:目标是“finish the stack”,状态是 Active,还有 token 预算、已用 token、用时等数字。测试把这些信息交给 continuation_prompt 生成提示词,再把换行统一成一种格式。出来的是一串提示词文本;测试逐项确认里面包含目标、预算、完成规则、严格 blocked 规则,同时确认里面没有不该出现的 budgetLimited 和 paused 说法。

调用关系:它在测试阶段直接构造 ThreadGoal,并用 ThreadId::new 造一个新线程编号,然后通过断言检查提示词内容。它是在保护 continuation_prompt 这个生成提示词的入口,防止以后有人改模板时,把完成或卡住的规则写松、写错或混进错误状态。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert!)。

budget_limit_prompt_steers_model_to_wrap_up_without_pausing33–52 ↗
fn budget_limit_prompt_steers_model_to_wrap_up_without_pausing()

作用:这个测试检查当 token 预算已经超了时,提示词是否会引导模型尽快收尾。它同时确保提示词不会叫模型进入 paused 这种不该出现的状态。

数据流:进去的是一个状态为 BudgetLimited 的线程目标:预算是一万 token,已经用了 10100。测试用这些数据生成预算限制提示词,再统一换行。出来的是提示词文本;测试确认它包含目标、预算、已用数量,并且用类似“尽快结束这一轮”的表达催促模型收尾,同时确认没有 status "paused"。

调用关系:它在预算限制场景下给 budget_limit_prompt 做把关。它同样用 ThreadId::new 生成测试用线程编号,再用断言验证文本。这个测试的作用是防止预算超限提示变成“暂停任务”,因为系统真正想要的是让模型收束当前回答,而不是进入错误状态。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert!)。

objective_updated_prompt_supersedes_previous_goal_context55–78 ↗
fn objective_updated_prompt_supersedes_previous_goal_context()

作用:这个测试检查用户修改目标后,新的提示词是否明确告诉模型:新目标已经替代旧目标。它还检查模型不会因为目标被改了就随便调用 update_goal。

数据流:进去的是一个新的线程目标,文字是“finish the revised stack”,状态仍是 Active,并带有预算和已用 token。测试生成“目标已更新”的提示词,然后检查输出文本。出来的提示词必须说明这是用户编辑过的目标、会覆盖之前目标,把新目标放进 untrusted_objective 标签里,并正确显示预算和剩余 token,还要提醒“除非真的完成,否则不要调用 update_goal”。

调用关系:它守住 objective_updated_prompt 的行为边界。这个提示词通常用在用户中途改任务时,目的是让模型切换到新方向,同时不要误以为旧目标还有效。测试通过 ThreadId::new 和断言完成验证,确保这个关键转场说明不会被模板修改破坏。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert!)。

goal_prompts_escape_objective_delimiters81–120 ↗
fn goal_prompts_escape_objective_delimiters()

作用:这个测试检查目标文字里的特殊标签和符号会不会被安全处理。它防止用户把像 </objective> 或 <developer> 这样的内容塞进目标里,骗提示词结构“破口”。

数据流:进去的是一段故意带有危险外观的目标文字:里面像是要关闭 objective 标签、插入 developer 指令,还带有 & 符号。测试先用 escape_xml_text 算出安全转义后的版本,然后分别生成继续提示词、预算限制提示词、目标更新提示词。出来的是三段提示词;测试确认每段都包含转义后的安全文字,并且不再包含原始危险文字。

调用关系:它一次覆盖三个提示词生成函数,确保它们都遵守同一条安全规则:用户目标只能当普通内容,不能当提示词结构或系统指令。它用 ThreadId::new 构造测试目标,用断言逐个检查结果,是整个目标提示词安全性的兜底测试。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert!)。

prompts/src/review_request_tests.rs源码 ↗
testtest

这个文件专门检查代码评审用的提示词。可以把提示词想成一张给 AI 审代码的“任务单”:要审哪个分支、哪个提交、要怎么比较代码,都得写清楚。这里的测试会把固定的输入塞进模板里,比如分支名 main、提交号 deadbeef、提交标题 Fix bug,然后把生成出的整段文字和预先写好的标准答案逐字比较。只要多一个字、少一个字,测试就会失败。这样做很重要,因为这些提示词会直接影响 AI 怎么理解评审任务;如果模板被改歪了,AI 可能看错范围,或者漏掉该审的代码。文件里还覆盖了几种常见情况:有上游分支时怎么提示、已知 merge base(共同祖先提交)时怎么提示、只审某个提交时怎么提示,以及提交带标题时怎么把标题放进去。

函数细节4
review_prompt_template_renders_base_branch_backup_variant5–10 ↗
fn review_prompt_template_renders_base_branch_backup_variant()

作用:这个测试确认“备用的基准分支评审模板”能把分支名正确填进去,并生成完整、准确的评审说明。它防止模板里关于如何找到 merge diff 的命令被意外改错。

数据流:进去的是一个模板和分支名 main。测试把 main 填进模板,得到一整段提示词;然后用断言把它和写死的标准文字逐字比较。出来的结果不是业务数据,而是测试通过或失败:一样就通过,不一样就说明模板渲染有问题。

调用关系:这是测试运行时由测试框架自动执行的小检查。它把主要工作交给模板渲染逻辑生成文字,最后通过 assert_eq! 做“实际结果”和“标准答案”的对比。

调用图:外部调用 1 个(assert_eq!)。

review_prompt_template_renders_base_branch_variant13–21 ↗
fn review_prompt_template_renders_base_branch_variant()

作用:这个测试确认普通的“基准分支评审模板”能正确写出基准分支和 merge base 提交号。merge base 可以理解成两条代码分支分开之前的共同起点。

数据流:进去的是模板、base_branch=main、merge_base_sha=abc123。测试生成一段告诉用户运行 git diff abc123 的评审提示词,然后和标准字符串比较。之后没有保存新数据,只用比较结果决定测试是否通过。

调用关系:它属于提示词模板的回归测试,也就是防止以后改代码时把旧行为弄坏。流程上,它先让模板渲染逻辑产出文字,再用 assert_eq! 检查这段文字是不是完全符合预期。

调用图:外部调用 1 个(assert_eq!)。

review_prompt_template_renders_commit_variant24–36 ↗
fn review_prompt_template_renders_commit_variant()

作用:这个测试确认当评审目标是某个提交、而且没有提交标题时,生成的提示词只包含提交号,并且措辞正确。

数据流:进去的是一个 ReviewTarget::Commit,里面有提交号 deadbeef,标题为空;另外还传入当前目录路径。测试调用提示词生成函数得到文字,再和标准答案比较。结果是测试通过或失败;它不会改动项目文件。

调用关系:这是对更高一层提示词生成入口的测试,不只是测单个模板替换。它在测试框架运行时被调用,生成结果后交给 assert_eq! 判断是否和预期一致。

调用图:外部调用 1 个(assert_eq!)。

review_prompt_template_renders_commit_variant_with_title39–51 ↗
fn review_prompt_template_renders_commit_variant_with_title()

作用:这个测试确认当评审某个提交且提交有标题时,生成的提示词会把标题也放进去。这样 AI 看到的不只是提交号,还能看到一点人类可读的上下文。

数据流:进去的是提交号 deadbeef、提交标题 Fix bug,以及当前目录路径。提示词生成函数把这些信息拼成一句评审说明,测试再把它和包含标题的标准文字逐字比较。输出体现为测试结果:完全相同就通过,否则失败。

调用关系:它覆盖的是“提交评审提示词带标题”这个分支场景。测试运行时,它先让提示词生成逻辑产出最终文本,再通过 assert_eq! 做精确校验,确保标题格式也没有被改坏。

调用图:外部调用 1 个(assert_eq!)。

prompts/src/review_exit_tests.rs源码 ↗
testtest

这个文件不参与正式运行,而是在跑测试时帮项目“验货”。它关心的是一段给用户看的评审结果文本:程序要把评审模型输出的内容放进固定模板里,像把信纸内容塞进信封一样,位置和格式都不能错。第一个测试检查 render_review_exit_success 会不会把“Finding A、Finding B”正确放到 <results> 区域里。第二个测试检查 normalize_review_template_line_endings 会不会把 Windows 常见的回车换行,也就是 \r\n,统一改成普通换行 \n。这很重要,因为提示词通常对格式很敏感,少一个换行、多一个回车,都可能让后续处理或比对结果出问题。

函数细节2
render_review_exit_success_replaces_results_placeholder5–10 ↗
fn render_review_exit_success_replaces_results_placeholder()

作用:这个测试确认:当评审结束时,程序生成的用户动作文本里,评审结果会被放到正确的位置。有人改模板或改生成函数时,它能及时发现格式被弄坏了。

数据流:进去的是一段模拟评审结果文本:Finding A\nFinding B。测试调用 render_review_exit_success 生成完整文本,然后用 assert_eq!,也就是“断言两边必须完全一样”的检查工具,把实际结果和预期模板逐字比较。出来的不是业务数据,而是测试通过或失败;如果有任何空格、换行或标签不一样,测试就会失败。

调用关系:它在测试流程中单独运行,直接检查 render_review_exit_success 这个生成模板的函数。它把最终判断交给外部的 assert_eq! 宏来做精确比较,目的就是防止模板输出在后续修改中悄悄变样。

调用图:外部调用 1 个(assert_eq!)。

normalize_review_template_line_endings_rewrites_crlf13–18 ↗
fn normalize_review_template_line_endings_rewrites_crlf()

作用:这个测试确认:模板里的 Windows 风格换行 \r\n 会被统一改成 \n。这样不同操作系统上生成或比较文本时,不会因为换行符不同而出错。

数据流:进去的是一段带有 \r\n 的模板片段。测试调用 normalize_review_template_line_endings 做换行清理,然后用 assert_eq! 检查结果是否变成只含 \n 的版本。出来的是测试结果:如果函数没有正确替换回车换行,测试就会失败。

调用关系:它在测试阶段验证 normalize_review_template_line_endings 这个格式清理函数。它同样把比较工作交给 assert_eq!,确保换行标准化这件小但关键的事不会被后续改动破坏。

调用图:外部调用 1 个(assert_eq!)。

memories/write/src/prompts_tests.rs源码 ↗
testtest

这是一组自动测试。它不直接给用户提供功能,而是像质检员一样检查别的代码是否按预期工作。这里重点测两件事:第一,第一阶段提示词里要塞入很长的 rollout 内容时,必须按模型能接受的上下文长度裁短。上下文长度可以理解成模型一次能“看见”的文字容量,太长就会超出限制,所以要安全截断。第二,如果模型没有告诉我们具体容量,就要用默认上限,不能乱放完整大文本。第三,合并阶段的提示词必须清楚指向工作区差异文件和扩展目录,让后续处理知道该看哪里。测试里会造很长的字符串、临时目录和预期的截断结果,然后检查生成出来的提示词是否包含这些关键内容。

函数细节3
build_stage_one_input_message_truncates_rollout_using_model_context_window6–32 ↗
fn build_stage_one_input_message_truncates_rollout_using_model_context_window()

作用:这个测试确认:当模型提供了可用的上下文窗口大小时,第一阶段提示词会按这个大小计算安全额度,并把超长 rollout 文本裁短。这样可以避免把太多文字塞进模型,导致请求失败或提示词不可用。

数据流:它先造出一个非常长的输入字符串,中间夹着少量文字,两头是大量字符;再读取一个模型信息,并手动设置它的上下文窗口为 123000。接着它按同一套规则算出应该允许多少 token,也就是模型处理文字时用的粗略单位,然后生成预期的截断文本。最后它调用真正的提示词构建函数,检查生成的消息里确实包含这个被截断后的文本,并且截断内容保留了开头和结尾,还带有“tokens truncated”这类说明。

调用关系:这个测试会先通过 model_info_from_slug 拿到模型资料,再用 Tokens 这种截断策略计算预期结果,最后调用被测的 build_stage_one_input_message。它自己不生产正式提示词,只是在测试运行时充当裁判,确认被测函数没有忽略模型容量限制。

调用图:调用 1 个内部函数(model_info_from_slug);外部调用 5 个(new, assert!, format!, Tokens, try_from)。

build_stage_one_input_message_uses_default_limit_when_model_context_window_missing35–53 ↗
fn build_stage_one_input_message_uses_default_limit_when_model_context_window_missing()

作用:这个测试确认:如果模型信息里没有写明上下文窗口,第一阶段提示词也不会放飞自我,而是使用系统设定的默认 rollout 长度上限。这样即使模型资料不完整,程序也能稳妥地生成提示词。

数据流:它先创建一个很长的输入文本;然后拿到模型信息,并故意把 context_window 和 max_context_window 都清空,模拟“模型没有告诉我们容量”的情况。之后它用 DEFAULT_ROLLOUT_TOKEN_LIMIT 生成预期的截断文本,再调用 build_stage_one_input_message。最后它检查生成出来的提示词确实包含按默认上限裁短后的内容。

调用关系:这个测试同样从 model_info_from_slug 获取基础模型资料,但会把关键容量字段清掉。它把测试重点放在 build_stage_one_input_message 的兜底行为上:当外部资料缺失时,被测函数应该转向默认限制,而不是依赖不存在的数据。

调用图:调用 1 个内部函数(model_info_from_slug);外部调用 4 个(new, assert!, format!, Tokens)。

build_consolidation_prompt_points_to_workspace_diff_and_extension_tree56–71 ↗
fn build_consolidation_prompt_points_to_workspace_diff_and_extension_tree()

作用:这个测试确认:合并阶段的提示词会告诉模型去看工作区差异文件,也会指出记忆扩展目录的位置。这样模型在整理记忆时不会漏看新增、删除或变化的资源。

数据流:它先创建一个临时目录,里面搭出 memories/extensions 这样的目录结构;然后把 memories 作为记忆根目录传给 build_consolidation_prompt,生成合并阶段提示词。最后它检查提示词里是否提到“Memory workspace diff:”、phase2_workspace_diff.md、扩展目录的完整路径,以及关于删除扩展资源文件的说明。

调用关系:这个测试使用 tempdir 建一个用完即丢的测试场地,再用 create_dir_all 建出需要的目录。随后它调用被测的 build_consolidation_prompt,并用断言检查提示词是否把后续合并工作需要看的地方说清楚。

调用图:外部调用 3 个(assert!, create_dir_all, tempdir)。

tools/src/image_detail_tests.rs源码 ↗
testtest

这个文件关心的是:当工具把图片交给模型时,图片可以要求不同的清晰程度,比如自动、低清、高清,或者“原图”。但不是每个模型都支持“原图”。如果不提前处理,就像把一张超规格的票塞给不认这类票的闸机,后面可能会失败。这里先造出一个假的测试模型信息,然后分别验证几种情况:模型支持原图时,原图请求应该保留;模型不支持时,原图请求应该被去掉;普通的清晰度设置不能被误伤;如果一批输出内容里混有文字和图片,不支持原图时,只把原图图片改成默认细节,其他内容保持不变。它不是线上功能本身,而是给相关逻辑兜底的安全网。

函数细节5
model_info9–42 ↗
fn model_info() -> ModelInfo

作用:这个函数造出一个固定的“假模型资料”,给后面的测试反复使用。它让测试不用依赖真实服务器或真实配置,也能稳定检查逻辑。

数据流:进去没有外部参数 → 它用一段 JSON 测试数据拼出模型的各种能力说明,其中包括“支持原图细节”这个开关 → 出来一个 ModelInfo 对象,也就是测试用的模型信息;如果这段测试数据不能转成模型信息,测试会直接失败。

调用关系:它是几个测试的共同准备步骤。explicit_original_is_allowed_when_model_supports_it、explicit_original_is_dropped_without_model_support 和 explicit_non_original_detail_is_preserved 都先调用它拿到同一份基础模型资料,然后再按各自需要检查图片细节逻辑。它内部借助 json! 生成测试数据,再用 from_value 把数据变成模型结构。

调用图:被 3 处调用(explicit_non_original_detail_is_preserved, explicit_original_is_allowed_when_model_supports_it, explicit_original_is_dropped_without_model_support);外部调用 2 个(json!, from_value)。

explicit_original_is_allowed_when_model_supports_it45–57 ↗
fn explicit_original_is_allowed_when_model_supports_it()

作用:这个测试确认:如果模型明确支持“原图”图片细节,那么用户要求原图时,程序不能擅自删掉这个要求。它保护的是“支持就应该允许”的行为。

数据流:进去没有参数 → 它先通过 model_info 拿到一个支持原图的测试模型 → 然后检查程序判断结果确实是“可以请求原图”,并检查把 ImageDetail::Original 交给规范化函数后仍然得到 Original;如果没有传任何细节设置,则结果也保持为空 → 出来没有业务返回值,只有测试通过或失败。

调用关系:这是测试套件里验证正向场景的一关。它先用 model_info 准备数据,再用断言 assert! 和 assert_eq! 对结果做核对;如果生产代码以后误把支持原图的请求丢掉,这个测试会立刻报错。

调用图:调用 1 个内部函数(model_info);外部调用 2 个(assert!, assert_eq!)。

explicit_original_is_dropped_without_model_support60–67 ↗
fn explicit_original_is_dropped_without_model_support()

作用:这个测试确认:如果模型不支持“原图”,程序应该把用户的原图请求去掉。它避免把模型处理不了的请求继续往后传。

数据流:进去没有参数 → 它先用 model_info 做出测试模型,再手动把 supports_image_detail_original 改成 false,表示这个模型不支持原图 → 接着把 Original 交给规范化函数检查 → 出来的期望结果是 None,也就是不再请求原图。

调用关系:它和 explicit_original_is_allowed_when_model_supports_it 是一正一反的配对测试。前者证明支持时保留,这个证明不支持时删除;它同样依赖 model_info 准备基础数据,并用 assert_eq! 判断结果是否符合预期。

调用图:调用 1 个内部函数(model_info);外部调用 1 个(assert_eq!)。

explicit_non_original_detail_is_preserved70–85 ↗
fn explicit_non_original_detail_is_preserved()

作用:这个测试确认:不是“原图”的图片细节设置,比如自动、低清、高清,都应该原样保留。它防止修正原图问题时误伤正常设置。

数据流:进去没有参数 → 它用 model_info 取得测试模型 → 分别把 Auto、Low、High 这些非原图选项交给规范化函数 → 出来的结果都应该和输入一样,说明这些普通选项没有被改掉。

调用关系:它补上了边界检查:图片细节逻辑不能只会删 Original,还要保证其他合法选项不受影响。它由测试框架运行,先调用 model_info,再用多次 assert_eq! 对每个选项做核对。

调用图:调用 1 个内部函数(model_info);外部调用 1 个(assert_eq!)。

sanitize_original_falls_back_to_high_without_support88–121 ↗
fn sanitize_original_falls_back_to_high_without_support()

作用:这个测试检查一批输出内容里的图片细节清洗:当不能请求原图时,原图图片要被改成默认细节,而文字和其他图片细节不能被乱改。

数据流:进去没有参数 → 它先造出一个列表,里面有一段文字、一张要求 Original 的图片、一张要求 Low 的图片 → 然后调用清洗函数,并告诉它当前不能请求原图 → 之后列表被原地修改:Original 那张图片的 detail 变成 DEFAULT_IMAGE_DETAIL,文字和 Low 图片保持原样 → 最后用 assert_eq! 对整个列表做完整比较。

调用关系:这是更接近真实输出内容的测试,不只是测单个细节值,而是测一组内容会怎样被改。它自己用 vec! 准备测试数据,再把可变列表交给清洗函数;清洗完成后,assert_eq! 负责确认只有该改的地方被改了。

调用图:外部调用 2 个(assert_eq!, vec!)。

tools/src/json_schema_tests.rs源码 ↗
testtest run

工具调用时会带一份 JSON Schema,也就是“这次输入应该长什么样”的说明。但现实里的 schema 可能写得不完整、很大、带引用,甚至有些格式系统不能直接表示。这个测试文件用很多具体例子,检查解析函数 parse_tool_input_schema 是否会把这些 schema 修正到 Responses API 能接受的形状。比如:缺 type 但有 properties 时要当成对象;数组没写 items 时要补默认项;const 要变成只有一个值的 enum;过大的描述文字要被删掉;$ref 引用到的定义要保留,没用到的定义要剪掉。这里没有业务运行逻辑,主要作用是“守门”:只要解析规则被改坏,测试就会立刻失败,提醒开发者。

函数细节45
parse_tool_input_schema_coerces_boolean_schemas14–25 ↗
fn parse_tool_input_schema_coerces_boolean_schemas()

作用:检查布尔形式的 JSON Schema,比如直接写 true,会不会被转成系统能表示的普通字符串 schema。这样可以避免遇到系统模型不支持的 schema 写法时直接崩掉。

数据流:进去的是一个 JSON 值 true → 测试把它交给 parse_tool_input_schema 解析 → 出来应该是 JsonSchema::string,也就是一个宽松的字符串输入说明;测试用断言确认结果完全一致。

调用关系:这是解析器兼容性测试的一部分。它直接调用 parse_tool_input_schema,然后把结果交给 assert_eq! 比较,确保布尔 schema 的兜底规则没有被改坏。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

json_schema_serializes_encrypted_marker28–39 ↗
fn json_schema_serializes_encrypted_marker()

作用:检查带有 encrypted 标记的字符串 schema 序列化后是否真的包含 encrypted: true。这个标记表示字段是加密值,不能在传输或展示时当普通文本看待。

数据流:进去的是描述文字“Secret value” → 测试先创建字符串 schema,再加上 encrypted 标记,并转成 JSON → 出来应该包含 type、description 和 encrypted 三个字段。

调用关系:它不走解析器,而是直接验证 JsonSchema 自身的输出格式。这个测试保护的是 schema 写出到 JSON 时的加密标记行为。

调用图:调用 1 个内部函数(string);外部调用 1 个(assert_eq!)。

parse_tool_input_schema_infers_object_shape_and_defaults_properties42–69 ↗
fn parse_tool_input_schema_infers_object_shape_and_defaults_properties()

作用:检查 schema 没写 type、但写了 properties 时,解析器会不会聪明地认出它是对象。这样用户少写一个 type 也不会导致工具输入说明失效。

数据流:进去的是一个只有 properties 的 JSON 对象 → parse_tool_input_schema 推断它是 object,并递归整理里面的 query 字段 → 出来是一个对象 schema,query 字段因为缺少有效类型提示而变成空的宽松 schema。

调用关系:这是对象形状推断测试。它调用 parse_tool_input_schema,再用 assert_eq! 确认属性表和默认子 schema 都符合预期。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_coerces_unrecognized_object_schema_to_empty_schema72–89 ↗
fn parse_tool_input_schema_coerces_unrecognized_object_schema_to_empty_schema()

作用:检查只有 title、description 这类说明文字、却没有真正类型线索的 schema,会不会被当成空的宽松 schema。这样坏输入不会被误认成某种具体结构。

数据流:进去的是一个只有描述和标题的 JSON 对象 → 解析器发现没有 type、properties、enum 等可用线索 → 出来是 JsonSchema::default,表示不过度限制输入。

调用关系:它验证解析器的兜底策略。测试直接调用 parse_tool_input_schema,并用断言确认没有把无效对象强行解释成 object。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_integer_and_defaults_array_items92–134 ↗
fn parse_tool_input_schema_preserves_integer_and_defaults_array_items()

作用:检查 integer 会不会和 number 区分开,同时检查数组没写 items 时会不会补一个默认的字符串项。这样数字类型和数组结构都不会丢失基本含义。

数据流:进去的是一个 object,里面有 integer 字段 page 和缺 items 的 array 字段 tags → 解析器保留 page 的整数类型,并给 tags 补上 string items → 出来是整理好的对象 schema。

调用关系:它覆盖对象属性里的基础类型整理。parse_tool_input_schema 做实际转换,assert_eq! 检查整数和数组默认值都正确。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_sanitizes_additional_properties_schema137–189 ↗
fn parse_tool_input_schema_sanitizes_additional_properties_schema()

作用:检查 additionalProperties 里如果又是一份 schema,解析器会不会继续深入整理。additionalProperties 可以理解为“除了已列字段外,额外字段该长什么样”。

数据流:进去的是一个 object,它的 additionalProperties 指向带 required、properties、anyOf 的嵌套 schema → 解析器递归清理这份嵌套 schema → 出来保留额外字段规则,并把 value 字段整理成 string 或 number 的 anyOf。

调用关系:它验证递归处理。测试调用 parse_tool_input_schema,重点看解析器有没有把活儿继续交给子 schema 的整理逻辑。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only192–210 ↗
fn parse_tool_input_schema_infers_object_shape_from_boolean_additional_properties_only()

作用:检查只有 additionalProperties: false 时,解析器会不会推断这是一个对象 schema。false 表示不允许出现额外字段。

数据流:进去的是只包含 additionalProperties false 的 JSON 对象 → 解析器把它识别成 object,并保留 false 这个限制 → 出来是一个没有显式属性、但禁止额外字段的对象 schema。

调用关系:它属于对象推断规则测试。parse_tool_input_schema 负责推断和保留布尔设置,assert_eq! 验证结果。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_number_from_numeric_keywords213–228 ↗
fn parse_tool_input_schema_infers_number_from_numeric_keywords()

作用:检查只有 minimum 这类数字限制时,解析器会不会推断这是 number 类型。minimum 的意思是“最小值”。

数据流:进去的是 { minimum: 1 } → 解析器看到数字约束关键字,推断类型是 number → 出来是一个 number schema。

调用关系:它验证数字类型的暗示规则。测试把样例交给 parse_tool_input_schema,并检查结果是不是数字 schema。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_number_from_multiple_of231–246 ↗
fn parse_tool_input_schema_infers_number_from_multiple_of()

作用:检查 multipleOf 也会被当成数字类型线索。multipleOf 表示数值必须是某个数的倍数。

数据流:进去的是 { multipleOf: 5 } → 解析器把这个约束看作数字 schema 的提示 → 出来是 number schema。

调用关系:它补充验证数字推断路径。parse_tool_input_schema 做判断,assert_eq! 确认 multipleOf 和 minimum 一样能触发 number。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords249–283 ↗
fn parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords()

作用:检查 enum、const、format 这些关键词在没写 type 时如何被解释。enum 是“只能选这些值”,const 是“只能是这个固定值”,format 是“像日期时间这样的格式提示”。

数据流:进去分别是 enum、const、format 三种 schema → 解析器把 enum 和 const 整理成字符串枚举,把 format 兜底成普通字符串 → 出来分别是 string_enum、单值 string_enum、string。

调用关系:它一次验证三个字符串相关推断。每个样例都调用 parse_tool_input_schema,再用断言检查对应输出。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_empty_schema286–296 ↗
fn parse_tool_input_schema_preserves_empty_schema()

作用:检查空对象 {} 会不会保持为空 schema。空 schema 在 JSON Schema 里表示“基本不限制”,本来就是合法写法。

数据流:进去的是 {} → 解析器识别它已经是合法的宽松 schema,不额外改写成 object → 出来仍是 JsonSchema::default。

调用关系:它保护一个容易被误改的边界情况。parse_tool_input_schema 被调用后,assert_eq! 确认空 schema 没被多管闲事地改变。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_empty_schema299–342 ↗
fn parse_tool_input_schema_preserves_nested_empty_schema()

作用:检查嵌套在对象深处的空 schema 也会被保留。这样用户明确写的“这个字段不限制”不会在递归整理时被改掉。

数据流:进去的是 object,里面 metadata 也有 properties,最里面 extra 是 {} → 解析器一层层递归整理 → 出来 metadata 是对象,extra 仍是空的宽松 schema。

调用关系:它验证递归清理不会破坏空 schema。parse_tool_input_schema 负责处理多层结构,assert_eq! 对最终树形结果做比较。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_array_from_prefix_items345–371 ↗
fn parse_tool_input_schema_infers_array_from_prefix_items()

作用:检查只有 prefixItems 时,解析器会不会推断这是数组。prefixItems 是 JSON Schema 里描述数组前几个位置各自类型的写法。

数据流:进去的是带 prefixItems、没写 type 的 schema → 解析器推断为 array,并把第一个 prefix item 当成普通 items → 出来是 items 为 string 的数组 schema。

调用关系:它测试数组形状推断。parse_tool_input_schema 做转换,assert_eq! 验证结果是否变成系统当前使用的数组表达方式。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object374–410 ↗
fn parse_tool_input_schema_preserves_boolean_additional_properties_on_inferred_object()

作用:检查嵌套字段只有 additionalProperties: true 时,会不会被推断成对象,并保留 true。true 表示允许额外字段。

数据流:进去的是一个 object,metadata 字段只有 additionalProperties true → 解析器把 metadata 推断成对象 → 出来 metadata 是允许额外字段的空对象 schema。

调用关系:它关注嵌套对象推断。顶层调用 parse_tool_input_schema,内部会递归处理 metadata,最后由断言检查。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only413–441 ↗
fn parse_tool_input_schema_infers_object_shape_from_schema_additional_properties_only()

作用:检查 additionalProperties 如果是一份 schema,也能暗示当前结构是对象。比如“所有额外字段都必须是字符串”。

数据流:进去的是只有 additionalProperties: { type: string } 的 schema → 解析器推断外层是 object,并整理内部的字符串 schema → 出来是一个额外字段类型为 string 的对象 schema。

调用关系:它验证 additionalProperties 的另一种写法。parse_tool_input_schema 处理外层推断和内层 schema,assert_eq! 检查两者都保留。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_rewrites_const_to_single_value_enum444–462 ↗
fn parse_tool_input_schema_rewrites_const_to_single_value_enum()

作用:检查 const 会不会被改写成只有一个值的 enum。这样内部只需要一种“可选值列表”的表达方式。

数据流:进去的是 { const: 'tagged' } → 解析器移除 const,并生成 enum: ['tagged'] 的字符串枚举 schema → 出来是单值 string_enum。

调用关系:它专门锁定 const 转 enum 的路径。parse_tool_input_schema 做改写,assert_eq! 验证等价结果。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_rejects_singleton_null_type465–476 ↗
fn parse_tool_input_schema_rejects_singleton_null_type()

作用:检查顶层 schema 如果只允许 null,会不会被拒绝。工具输入不能整个就是 null,否则无法表达有意义的参数结构。

数据流:进去的是 { type: 'null' } → 解析器返回错误,而不是返回 schema → 测试检查错误文字里包含“不能是单独 null 类型”的提示。

调用关系:这是少数期望失败的测试。它调用 parse_tool_input_schema,然后用 assert! 检查错误内容,确保拒绝规则存在。

调用图:外部调用 3 个(assert!, json!, parse_tool_input_schema)。

parse_tool_input_schema_fills_default_properties_for_nullable_object_union479–504 ↗
fn parse_tool_input_schema_fills_default_properties_for_nullable_object_union()

作用:检查 type 是 ['object', 'null'] 时,对象分支仍会补默认 properties。这个联合类型表示“可以是对象,也可以是空值 null”。

数据流:进去的是 nullable object union → 解析器保留 object/null 这个联合类型,同时给 object 形态补上空 properties → 出来是带 Multiple 类型和空属性表的 schema。

调用关系:它验证联合类型不会跳过对象默认值。parse_tool_input_schema 做规范化,assert_eq! 检查联合和默认 properties 都在。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_fills_default_items_for_nullable_array_union507–532 ↗
fn parse_tool_input_schema_fills_default_items_for_nullable_array_union()

作用:检查 type 是 ['array', 'null'] 时,数组分支仍会补默认 items。这样可空数组也有清楚的元素类型说明。

数据流:进去的是 nullable array union → 解析器保留 array/null 联合,并给数组补 string items → 出来是带 Multiple 类型和默认 items 的 schema。

调用关系:它和可空对象测试配套。parse_tool_input_schema 做整理,assert_eq! 验证数组默认项没有漏掉。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_nullable_any_of_shape538–628 ↗
fn parse_tool_input_schema_preserves_nested_nullable_any_of_shape()

作用:检查很深的 anyOf 可空结构不会被拍扁或改形。anyOf 表示“满足其中任意一种 schema 就行”。

数据流:进去的是一个对象,open 字段可以是对象数组,也可以是 null,数组元素里还有可空整数 → 解析器递归整理每一层,但保留 anyOf 结构 → 出来是同样嵌套层级的 JsonSchema 树。

调用关系:它保护 Responses API 需要的复杂兼容形状。parse_tool_input_schema 负责整棵树的递归规范化,assert_eq! 检查每层结构都没丢。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_nullable_type_union631–679 ↗
fn parse_tool_input_schema_preserves_nested_nullable_type_union()

作用:检查嵌套字段的 ['string', 'null'] 类型联合会被原样保留,同时对象的 required 和 additionalProperties 也不丢。

数据流:进去的是带 nickname 字段的对象 schema → 解析器保留 nickname 的 string/null 联合和描述文字,也保留 required 与禁止额外字段设置 → 出来是整理后的对象 schema。

调用关系:它验证嵌套联合类型和对象级限制能同时存在。parse_tool_input_schema 处理全部字段,assert_eq! 确认没有互相覆盖。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_any_of_property682–729 ↗
fn parse_tool_input_schema_preserves_nested_any_of_property()

作用:检查属性里的 anyOf 会被保留,不会被粗暴降级成某一个普通类型。这样“字符串或数字都可以”的规则仍然准确。

数据流:进去的是 query 字段含 anyOf string/number 的对象 → 解析器整理两个分支 → 出来 query 仍是 anyOf,里面分别是 string 和 number schema。

调用关系:它测试组合 schema 的基本保真。parse_tool_input_schema 递归处理 anyOf 子项,assert_eq! 校验结构。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_one_of_property732–782 ↗
fn parse_tool_input_schema_preserves_nested_one_of_property()

作用:检查属性里的 oneOf 会被保留,并且里面的 const 仍会被整理。oneOf 表示“只能匹配其中一种”。

数据流:进去的是 query 字段含 oneOf,分支之一是 const 'exact',另一个是 number → 解析器保留 oneOf,并把 const 分支改成单值枚举 → 出来是 oneOf 的规范化结果。

调用关系:它同时验证组合结构保留和子分支清理。parse_tool_input_schema 负责递归,assert_eq! 对最终结构验收。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_all_of_property785–832 ↗
fn parse_tool_input_schema_preserves_nested_all_of_property()

作用:检查 allOf 组合结构会被保留。allOf 表示“必须同时满足所有子 schema”。

数据流:进去的是 query 字段含 allOf,一个分支是 string,另一个只有 description、没有有效类型线索 → 解析器保留 allOf,并把无效分支转成空 schema → 出来是 allOf 加两个整理后的分支。

调用关系:它覆盖第三种组合关键字。parse_tool_input_schema 清理子 schema,assert_eq! 确认外层 allOf 没被展开或丢掉。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_type_unions_without_rewriting_to_any_of835–862 ↗
fn parse_tool_input_schema_preserves_type_unions_without_rewriting_to_any_of()

作用:检查显式 type 联合不会被改写成 anyOf。这样用户写的 ['string', 'null'] 形式能保持原样。

数据流:进去的是 type 为 string/null 且带 description 的 schema → 解析器保留 Multiple 类型和描述文字 → 出来不是 anyOf,而是原来的类型联合表达。

调用关系:它保护输出格式稳定性。parse_tool_input_schema 做解析,assert_eq! 确认没有换一种结构表达同一件事。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_explicit_enum_type_union865–898 ↗
fn parse_tool_input_schema_preserves_explicit_enum_type_union()

作用:检查 type 联合和 enum 值可以一起保留。比如字段可以是字符串或 null,但字符串只能从几个选项里选。

数据流:进去的是 string/null 联合、enum 三个值、以及描述文字 → 解析器保留联合类型、描述和 enum_values → 出来是包含这些信息的 JsonSchema。

调用关系:它验证枚举约束不会因为可空联合而丢失。测试调用 super::parse_tool_input_schema,然后用 assert_eq! 校验。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

many_string_properties900–909 ↗
fn many_string_properties(count: usize) -> serde_json::Map<String, serde_json::Value>

作用:生成一大批名字类似 field_000、field_001 的字符串属性,用来构造很大的测试 schema。它是测试里的小工具,省得手写几百个字段。

数据流:进去的是数量 count → 函数从 0 到 count-1 生成字段名和 { type: string } 的 JSON 值 → 出来是一张有序属性表。

调用关系:它被大型 schema 压缩相关测试拿来制造体积压力,尤其用于检查定义表太大时会不会被剪掉。它不调用解析器,只准备测试数据。

parse_large_tool_input_schema_compacts_descriptions_only_on_default_path912–966 ↗
fn parse_large_tool_input_schema_compacts_descriptions_only_on_default_path()

作用:检查默认解析路径会压缩过大的描述文字,而“不压缩”路径会完整保留。这样系统既能控制发送给 API 的 schema 大小,也能在需要时保留原信息。

数据流:进去的是带很长 description 和 $defs 的 schema → 默认 parse_tool_input_schema 会去掉描述,parse_tool_input_schema_without_compaction 会保留描述 → 出来分别与两个预期 JSON 比较。

调用关系:它同时调用两个解析入口,对比它们的差异。这个测试保护“默认压缩、显式不压缩”这条分界线。

调用图:外部调用 4 个(assert_eq!, json!, parse_tool_input_schema, parse_tool_input_schema_without_compaction)。

parse_large_tool_input_schema_ignores_dropped_metadata_for_budget969–1018 ↗
fn parse_large_tool_input_schema_ignores_dropped_metadata_for_budget()

作用:检查 title、examples 这类会被丢弃的元信息,不应该继续占用大小预算。大小预算可以理解为“schema 最多能有多大”。

数据流:进去的是包含超长 examples 和若干 title 的 schema → 解析器先丢掉这些不必要元信息,再判断大小 → 出来是只保留真正结构信息的 schema。

调用关系:它验证压缩流程的顺序。parse_tool_input_schema 完成清理和预算判断,assert_eq! 确认被丢弃的信息不会影响结果。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_large_tool_input_schema_stops_after_dropping_root_definitions_when_under_budget1021–1078 ↗
fn parse_large_tool_input_schema_stops_after_dropping_root_definitions_when_under_budget()

作用:检查当根部 $defs 太大、删掉后已经低于大小限制时,压缩流程会停下来,不再继续破坏有用结构。

数据流:进去的是有长描述、嵌套事件结构、以及很大的 $defs 定义表的 schema → 解析器删描述和根定义,metadata 引用变成空 schema → 出来保留 event 结构,不再继续剪枝。

调用关系:它用 many_string_properties 造出大定义表,再调用 parse_tool_input_schema。测试关注压缩算法是否“够用就停”。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_large_tool_input_schema_strips_descriptions_without_removing_description_property1081–1174 ↗
fn parse_large_tool_input_schema_strips_descriptions_without_removing_description_property()

作用:检查压缩时会删除 schema 的 description 说明文字,但不会误删名叫 description 的用户字段。字段名和元信息同名时不能混淆。

数据流:进去的是一个大对象,既有顶层和子项 description 元信息,也有 properties.description 这个真实字段 → 解析器删除说明文字,但保留 description 字段本身 → 出来是没有描述元信息、但仍有 description 属性的 schema。

调用关系:它保护一个很容易出错的同名场景。parse_tool_input_schema 做压缩,assert_eq! 确认只删元数据不删用户数据。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_large_tool_input_schema_prunes_compositions_as_last_resort1177–1217 ↗
fn parse_large_tool_input_schema_prunes_compositions_as_last_resort()

作用:检查 schema 太大时,anyOf、oneOf、allOf 这类组合分支会作为最后手段被剪掉。组合分支有时很占空间。

数据流:进去的是循环构造的三种组合 schema,每个组合里都有很长的 enum 字符串 → 解析器先尝试常规压缩,仍太大时把 choice 变成空 schema → 出来每种组合都被剪成 {}。

调用关系:它遍历 COMPOSITION_SCHEMA_KEYS,对每个组合关键字都调用 parse_tool_input_schema。这个测试确保最后兜底剪枝对所有组合类型一致。

调用图:外部调用 6 个(assert_eq!, new, Array, json!, parse_tool_input_schema, vec!)。

parse_large_tool_input_schema_prunes_single_composition_variant_if_still_over_budget1220–1245 ↗
fn parse_large_tool_input_schema_prunes_single_composition_variant_if_still_over_budget()

作用:检查即使组合里只有一个分支,只要它仍然太大,也会被剪掉。不能因为只有一个选项就跳过预算限制。

数据流:进去的是 choice.anyOf,里面只有一个带超长 enum 的字符串分支 → 解析器发现压缩后仍超预算 → 出来 choice 变成空 schema。

调用关系:它补充上一条组合剪枝测试。parse_tool_input_schema 执行预算控制,assert_eq! 验证单分支组合也适用。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_large_tool_input_schema_preserves_object_enum_literal_descriptions1248–1290 ↗
fn parse_large_tool_input_schema_preserves_object_enum_literal_descriptions()

作用:检查 enum 里的对象字面量如果含有 description 字段,不会被当成 schema 描述删掉。这里的 description 是枚举值的一部分,不是元信息。

数据流:进去的是一个对象字段 choice,它的 enum 值本身是含 description 和 id 的对象 → 解析器删掉外层过长描述,但保留 enum 值里的 description → 出来是 string enum,并保留两个对象字面量。

调用关系:它保护“数据里的 description”和“schema 的 description”之间的区别。parse_tool_input_schema 做压缩和枚举整理,assert_eq! 检查枚举值没被污染。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

collapse_deep_schema_objects_traverses_schema_children1293–1416 ↗
fn collapse_deep_schema_objects_traverses_schema_children()

作用:检查折叠过深对象的函数会走遍各种子 schema 位置,包括 properties、items、additionalProperties 和 anyOf。折叠的意思是把太深的对象变成空 schema,减少复杂度。

数据流:进去的是一棵很深的 JSON schema 树 → 测试直接调用 collapse_deep_schema_objects,从深度 0 开始遍历 → 出来是深层 nested 对象被替换成 {},浅层和标量字段保持不变。

调用关系:这是直接测试内部辅助函数,而不是完整解析入口。它确保深度压缩逻辑能进入数组、映射和组合分支这些不同通道。

调用图:外部调用 3 个(assert_eq!, json!, collapse_deep_schema_objects)。

parse_tool_input_schema_preserves_string_enum_constraints1419–1486 ↗
fn parse_tool_input_schema_preserves_string_enum_constraints()

作用:检查老式写法 type: 'enum' 和 type: 'const' 会被规范成现在的字符串枚举表达。这样旧客户端发来的 schema 还能继续用。

数据流:进去的是包含 response_length、kind、scope 三个字段的对象,其中用了 enum 和 const 旧写法 → 解析器把它们都改成 string_enum → 出来是三个枚举字段的对象 schema。

调用关系:它测试向后兼容。parse_tool_input_schema 负责把旧格式翻译成当前格式,assert_eq! 检查每个字段的枚举值。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_refs_and_prunes_unreachable_defs1489–1546 ↗
fn parse_tool_input_schema_preserves_refs_and_prunes_unreachable_defs()

作用:检查本地 $ref 引用会被保留,同时 $defs 里没被引用的定义会被删除。$ref 像“见附录某一项”的指针,$defs 就是附录表。

数据流:进去的是 user 字段引用 #/$defs/User,$defs 里还有 Unused → 解析器保留 user 的引用,整理 User 定义,删除 Unused → 出来只带可达的 User 定义。

调用关系:它验证引用可达性裁剪。parse_tool_input_schema 解析主 schema,并沿 $ref 找到需要保留的定义。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_refs_from_properties_named_def_tables1549–1594 ↗
fn parse_tool_input_schema_preserves_refs_from_properties_named_def_tables()

作用:检查如果用户字段刚好叫 $defs,也不能把它误认为定义表。字段名只是字段名,仍要从里面的 $ref 收集引用。

数据流:进去的是 properties 里有一个名叫 $defs 的字段,它引用 User;根部 $defs 还有 User 和 Unused → 解析器把 properties.$defs 当普通字段处理,保留 User,删除 Unused → 出来字段名和引用都正确。

调用关系:它保护关键字同名的边界情况。parse_tool_input_schema 的遍历逻辑必须区分“schema 关键字”和“用户属性名”。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_collects_refs_from_schema_child_keywords1597–1681 ↗
fn parse_tool_input_schema_collects_refs_from_schema_child_keywords()

作用:检查 $ref 不只在 properties 里能被发现,也能从 items、additionalProperties、anyOf、oneOf、allOf 这些子位置收集。否则一些被用到的定义会被误删。

数据流:进去的是包含数组项、额外字段、组合分支等多种引用位置的 schema → 解析器从所有这些子 schema 中收集可达定义 → 出来保留 Combined、Choice、ExclusiveChoice、Extra、Item,删除 Unused。

调用关系:它验证引用扫描的覆盖面。parse_tool_input_schema 负责递归走所有 schema 子节点,assert_eq! 通过序列化结果检查保留集合。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_handles_cyclic_local_refs1684–1745 ↗
fn parse_tool_input_schema_handles_cyclic_local_refs()

作用:检查循环引用不会让解析器无限转圈。比如链表节点 Node 的 next 又引用 Node 自己。

数据流:进去的是 node 字段引用 Node,Node 内部 next 又引用 Node → 解析器保留递归引用,并且每个本地目标只访问一次 → 出来是带自引用定义的 schema。

调用关系:它保护引用遍历的安全性。parse_tool_input_schema 必须记住已访问定义,避免递归引用导致卡死。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_legacy_definitions1748–1827 ↗
fn parse_tool_input_schema_preserves_legacy_definitions()

作用:检查旧版 definitions 定义表也能被保留和裁剪。definitions 是 $defs 之前常见的老写法。

数据流:进去的是 user 引用 #/definitions/User,User 又引用 Profile,definitions 里还有 Unused → 解析器沿引用保留 User 和 Profile,删除 Unused → 出来是整理后的 legacy definitions 表。

调用关系:它验证新旧定义表都受支持。parse_tool_input_schema 的引用可达性逻辑要能跟踪 #/definitions/... 这种老路径。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_unresolved_and_external_refs1830–1880 ↗
fn parse_tool_input_schema_preserves_unresolved_and_external_refs()

作用:检查找不到的本地引用和外部网址引用会被保留下来,而不是被报错或删除。下游系统可能仍能处理这些引用。

数据流:进去的是 missing 引用不存在的本地定义,remote 引用外部 URL,$defs 里只有 Unused → 解析器保留两个 $ref,删除未使用的 Unused → 出来没有定义表,但引用字段还在。

调用关系:它测试宽容处理策略。parse_tool_input_schema 不负责解析外部资源,只负责不要破坏原始引用。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_nested_defs_ref_parent1883–1942 ↗
fn parse_tool_input_schema_preserves_nested_defs_ref_parent()

作用:检查 $ref 指向定义内部更深路径时,会保留它所属的顶层定义。否则引用会变成“指向不存在的附录”。

数据流:进去的是 name 字段引用 #/$defs/User/properties/name,$defs 里还有 name 和 Unused → 解析器识别这个引用属于 User 定义,保留 User,删除无关定义 → 出来引用原样保留,User 定义存在。

调用关系:它验证复杂 JSON Pointer 引用的可达性。parse_tool_input_schema 要从深层引用中找出根定义名。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_preserves_percent_encoded_definition_refs1945–2012 ↗
fn parse_tool_input_schema_preserves_percent_encoded_definition_refs()

作用:检查带百分号编码的 $ref 能正确找到定义。比如空格写成 %20,美元符号写成 %24,波浪号还有 JSON Pointer 的转义规则。

数据流:进去的是引用 User%20Name 和 %24defs/Profile%7E0Name 的 schema,定义表里是真实名字 User Name 和 Profile~Name → 解析器解码后识别目标,但输出仍保留原始 $ref 字符串 → 出来只保留这两个被引用的定义。

调用关系:它保护符合标准的引用解析。parse_tool_input_schema 的可达性扫描需要先做 URI 片段解码,再按 JSON Pointer 规则处理。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

parse_tool_input_schema_drops_malformed_definition_tables2015–2049 ↗
fn parse_tool_input_schema_drops_malformed_definition_tables()

作用:检查 $defs 如果不是对象表,而是坏格式数组,解析器会丢掉它而不是整体失败。这样坏的定义表不会拖垮整个工具 schema。

数据流:进去的是 user 引用 #/$defs/User,但根部 $defs 是数组 ['not', 'an', 'object'] → 解析器删除这个畸形定义表,同时保留 user 的 unresolved $ref → 出来是对象 schema 和原始引用。

调用关系:它验证错误容忍策略。parse_tool_input_schema 对坏定义表选择降级处理,assert_eq! 确认主 schema 仍可用。

调用图:外部调用 3 个(assert_eq!, json!, parse_tool_input_schema)。

协议与执行语义测试

这些文件涵盖协议错误/输出编码,以及代码模式会话和服务契约中围绕执行生命周期与失败传播的行为。

protocol/src/error_tests.rs源码 ↗
testtest

这个文件像一张“错误提示质检表”。它不负责真正处理错误,而是造出各种典型坏情况:用量超限、服务器过载、沙盒里命令失败、读服务器响应失败、接口返回异常状态等,然后检查系统最后生成的提示文字和错误分类是否符合预期。这里特别关注细节,比如不同套餐该提示升级到哪里、团队或企业用户该不该找管理员、重试时间怎么显示、Cloudflare 拦截页面要不要简化、很长的错误内容要不要截断。文件里有两个小帮手:rate_limit_snapshot 用来快速造一份假的限流信息,with_now_override 用来把“当前时间”固定住,避免测试因为真实时间变化而忽好忽坏。没有这些测试,改错误文案或协议映射时很容易悄悄破坏用户体验。

函数细节31
rate_limit_snapshot15–42 ↗
fn rate_limit_snapshot() -> RateLimitSnapshot

作用:造一份固定的“用量限制快照”,给多个测试重复使用。这样每个测试不用从头写一大堆限流数据,也能保证测试条件一致。

数据流:进去没有参数 → 它写死两个重置时间和两段限流窗口,比如主限制用了 50%、副限制用了 30% → 出来一份 RateLimitSnapshot,供用量超限相关测试放进错误对象里。

调用关系:它是测试里的小道具,被多个 usage_limit_reached 开头的测试调用。那些测试关心的是错误文案,不想被造限流数据的细节分散注意力,所以把这部分交给它。

调用图:被 9 处调用(usage_limit_reached_error_formats_business_plan_without_reset, usage_limit_reached_error_formats_default_for_other_plans, usage_limit_reached_error_formats_default_when_none, usage_limit_reached_error_formats_enterprise_cbp_usage_based_plan, usage_limit_reached_error_formats_free_plan, usage_limit_reached_error_formats_go_plan, usage_limit_reached_error_formats_plus_plan, usage_limit_reached_error_formats_rate_limit_reached_types, usage_limit_reached_error_formats_self_serve_business_usage_based_plan)。

with_now_override44–51 ↗
fn with_now_override(now: DateTime<Utc>, f: impl FnOnce() -> T) -> T

作用:临时把系统眼里的“现在”改成指定时间,用来稳定测试带有“多久后再试”的提示。测试结束后它会把这个假时间清掉。

数据流:进去一个指定的当前时间和一段要执行的测试代码 → 它先把 NOW_OVERRIDE 设成这个时间,再运行传入的代码,最后恢复为空 → 出来的是那段代码的结果,同时避免真实时间影响测试。

调用关系:凡是要检查重试时间格式的测试都会先调用它。它把时间固定好后,测试再去调用 format_retry_timestamp 和错误格式化逻辑,这样结果不会因为运行当天不同而变化。

调用图:被 8 处调用(usage_limit_reached_error_formats_pro_plan_with_reset, usage_limit_reached_error_formats_team_plan, usage_limit_reached_error_hides_upsell_for_non_codex_limit_name, usage_limit_reached_includes_days_hours_minutes, usage_limit_reached_includes_hours_and_minutes, usage_limit_reached_includes_minutes_when_available, usage_limit_reached_less_than_minute, usage_limit_reached_with_promo_message)。

usage_limit_reached_error_formats_plus_plan54–66 ↗
fn usage_limit_reached_error_formats_plus_plan()

作用:检查 Plus 套餐用户用量超限时,提示语是否引导升级到 Pro、购买更多 credits 或稍后再试。credits 可以理解成可用额度。

数据流:进去没有外部输入 → 它用 rate_limit_snapshot 造限流信息,再构造一个 Plus 用户的 UsageLimitReachedError → 出来通过断言确认 err.to_string() 生成的整句提示完全符合预期。

调用关系:这是用量超限文案测试中的一个套餐分支。它依赖 rate_limit_snapshot 准备背景数据,最后把实际文案交给 assert_eq! 和预期文案比较。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_rate_limit_reached_types69–104 ↗
fn usage_limit_reached_error_formats_rate_limit_reached_types()

作用:检查不同“超限原因”会不会显示不同的人话提示。比如个人额度用完、工作区额度用完、花费上限触顶,都应该说清楚该找谁或做什么。

数据流:进去没有外部输入 → 它列出多种 RateLimitReachedType,每一种都构造一个 UsageLimitReachedError → 每轮输出一条实际提示,并和对应预期提示比较。

调用关系:它覆盖了比套餐更具体的错误原因。每个案例都借用 rate_limit_snapshot,但真正要验证的是 rate_limit_reached_type 会覆盖或改变最终文案。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

server_overloaded_maps_to_protocol107–113 ↗
fn server_overloaded_maps_to_protocol()

作用:检查“服务器太忙”这个内部错误,能不能正确变成对外协议里的 ServerOverloaded 错误信息。

数据流:进去没有外部输入 → 它创建 CodexErr::ServerOverloaded,再调用 to_codex_protocol_error → 出来应是 CodexErrorInfo::ServerOverloaded,并用断言确认。

调用关系:这个测试站在内部错误和外部协议之间。它不关心文字提示,而是保证错误分类传到客户端时没有变错。

调用图:外部调用 1 个(assert_eq!)。

sandbox_denied_uses_aggregated_output_when_stderr_empty116–130 ↗
fn sandbox_denied_uses_aggregated_output_when_stderr_empty()

作用:检查沙盒拒绝执行时,如果标准错误没有内容,界面会不会退而使用汇总输出。沙盒可以理解成隔离的小房间,命令只能在里面安全运行。

数据流:进去没有外部输入 → 它造一个命令输出,stdout 和 stderr 都空,但 aggregated_output 有内容 → 包成 Sandbox Denied 错误后,get_error_message_ui 应输出汇总内容。

调用关系:这是沙盒错误提示的一种兜底场景。它把造好的 ExecToolCallOutput 交给 CodexErr::Sandbox,再由 get_error_message_ui 决定用户看到什么。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, from_millis, new, assert_eq!, Sandbox)。

sandbox_denied_reports_both_streams_when_available133–147 ↗
fn sandbox_denied_reports_both_streams_when_available()

作用:检查沙盒命令失败时,如果标准错误和标准输出都有内容,界面是否两个都显示,避免漏掉有用线索。

数据流:进去没有外部输入 → 它造出同时带 stderr 和 stdout 的命令输出 → 包成沙盒拒绝错误后,结果应先显示 stderr,再换行显示 stdout。

调用关系:它验证 get_error_message_ui 对命令两条输出流的组合规则。这个规则很重要,因为 stderr 通常是错误原因,stdout 也可能有补充信息。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, from_millis, new, assert_eq!, Sandbox)。

sandbox_denied_reports_stdout_when_no_stderr150–164 ↗
fn sandbox_denied_reports_stdout_when_no_stderr()

作用:检查沙盒命令失败时,如果没有标准错误但有标准输出,界面是否会显示标准输出。

数据流:进去没有外部输入 → 它造一个 stderr 为空、stdout 有文字的命令输出 → 包成 Sandbox Denied 错误 → 出来应把 stdout 的内容作为用户可见错误信息。

调用关系:它补齐了沙盒错误输出选择的另一个分支。和显示双流、显示汇总输出、显示退出码的测试一起,形成完整保护网。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, from_millis, new, assert_eq!, Sandbox)。

to_error_event_handles_response_stream_failed167–191 ↗
fn to_error_event_handles_response_stream_failed()

作用:检查读取服务器响应中途失败时,系统生成的错误事件是否包含清楚的文字、请求编号和 HTTP 状态码。HTTP 状态码就是服务器返回的数字结果,比如 429 表示请求太多。

数据流:进去没有外部输入 → 它先造一个 429 的 HTTP 响应,再把它转成 reqwest 错误,包进 ResponseStreamFailed → 调用 to_error_event 后,检查事件里的 message 和 codex_error_info。

调用关系:它测试从底层网络响应错误到上层错误事件的转换。这里会用到 HTTP 响应构造、URL 解析和 ResponseStreamFailed 包装,最终确认客户端能收到正确分类。

调用图:外部调用 5 个(builder, parse, assert_eq!, from, ResponseStreamFailed)。

sandbox_denied_reports_exit_code_when_no_output_available194–211 ↗
fn sandbox_denied_reports_exit_code_when_no_output_available()

作用:检查沙盒命令失败但完全没有输出时,界面至少会显示退出码。退出码是命令结束时留下的数字,通常非 0 表示失败。

数据流:进去没有外部输入 → 它造一个 stdout、stderr、汇总输出都为空但 exit_code 为 13 的命令结果 → 包成沙盒拒绝错误 → 出来应是一句包含退出码的兜底提示。

调用关系:它是沙盒错误提示的最后兜底测试。当前面那些输出都没有时,get_error_message_ui 仍然不能让用户看到空白错误。

调用图:调用 1 个内部函数(new);外部调用 5 个(new, from_millis, new, assert_eq!, Sandbox)。

usage_limit_reached_error_formats_free_plan214–226 ↗
fn usage_limit_reached_error_formats_free_plan()

作用:检查 Free 免费套餐用量超限时,提示是否引导升级到 Plus 或稍后再试。

数据流:进去没有外部输入 → 它用固定限流快照构造 Free 用户的超限错误 → 把错误转成字符串后,和预期升级提示比较。

调用关系:它是套餐差异测试的一部分。它调用 rate_limit_snapshot 准备共同背景,再让 UsageLimitReachedError 的格式化逻辑产出文案。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_go_plan229–241 ↗
fn usage_limit_reached_error_formats_go_plan()

作用:检查 Go 套餐用户超限时,提示是否和预期一样引导升级到 Plus 或稍后再试。

数据流:进去没有外部输入 → 它构造 Go 套餐的 UsageLimitReachedError,并带上固定限流信息 → 出来比较错误字符串是否等于预期文案。

调用关系:它和 Free 套餐测试类似,用来防止 Go 套餐的分支文案被改坏。共同的限流数据仍由 rate_limit_snapshot 提供。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_default_when_none244–256 ↗
fn usage_limit_reached_error_formats_default_when_none()

作用:检查不知道用户套餐时,用量超限提示是否保持通用:告诉用户超限并稍后再试。

数据流:进去没有外部输入 → 它构造一个 plan_type 为空的超限错误 → 转成字符串后,应得到不带升级建议的默认提示。

调用关系:它保护“信息不足时不要乱推荐”的行为。它同样使用 rate_limit_snapshot,但重点是 plan_type 为 None 的默认分支。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 2 个(new, assert_eq!)。

usage_limit_reached_error_formats_team_plan259–276 ↗
fn usage_limit_reached_error_formats_team_plan()

作用:检查 Team 团队套餐超限且知道重置时间时,提示是否告诉用户可以找管理员,或者在指定时间再试。

数据流:进去没有外部输入 → 它先固定当前时间,再算出一小时后的重置时间 → 构造 Team 超限错误,最后比较生成文案里的重试时间是否正确。

调用关系:它通过 with_now_override 固定时间,避免测试受真实时钟影响。这个测试验证团队类套餐和 format_retry_timestamp 配合后的最终文字。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(hours)。

usage_limit_reached_error_formats_business_plan_without_reset279–291 ↗
fn usage_limit_reached_error_formats_business_plan_without_reset()

作用:检查 Business 套餐超限但没有重置时间时,提示是否让用户找管理员或稍后再试。

数据流:进去没有外部输入 → 它构造 Business 套餐、resets_at 为空的超限错误 → 输出字符串应是不带具体时间、但带管理员建议的提示。

调用关系:它覆盖企业/团队类套餐没有时间信息的分支。rate_limit_snapshot 只是补充限流背景,断言关注最终文案。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_self_serve_business_usage_based_plan294–306 ↗
fn usage_limit_reached_error_formats_self_serve_business_usage_based_plan()

作用:检查自助商业按量套餐超限时,文案是否和管理员协助的预期一致。

数据流:进去没有外部输入 → 它构造 SelfServeBusinessUsageBased 计划的超限错误 → 转成字符串后,确认提示用户找管理员或稍后再试。

调用关系:它和 Business、EnterpriseCbpUsageBased 的测试一起,保证相近商业套餐走同一类文案规则。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_enterprise_cbp_usage_based_plan309–321 ↗
fn usage_limit_reached_error_formats_enterprise_cbp_usage_based_plan()

作用:检查 EnterpriseCbpUsageBased 企业按量套餐超限时,提示是否仍然是找管理员或稍后再试。

数据流:进去没有外部输入 → 它构造这个企业套餐类型的 UsageLimitReachedError → 输出错误字符串,并和预期管理员提示比较。

调用关系:它保护企业按量套餐这个具体枚举值不会掉到错误的默认文案里。限流快照由 rate_limit_snapshot 提供。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_default_for_other_plans324–336 ↗
fn usage_limit_reached_error_formats_default_for_other_plans()

作用:检查其他没有特殊规则的套餐超限时,会不会使用最普通的“稍后再试”提示。

数据流:进去没有外部输入 → 它构造 Enterprise 套餐的超限错误 → 输出字符串应为通用提示,不带升级、购买或管理员建议。

调用关系:它测试套餐匹配的兜底分支。这样新增或修改套餐文案时,不会意外影响没有特殊处理的套餐。

调用图:调用 1 个内部函数(rate_limit_snapshot);外部调用 3 个(new, assert_eq!, Known)。

usage_limit_reached_error_formats_pro_plan_with_reset339–356 ↗
fn usage_limit_reached_error_formats_pro_plan_with_reset()

作用:检查 Pro 套餐超限且知道恢复时间时,提示是否让用户购买更多额度,或者在指定时间再试。

数据流:进去没有外部输入 → 它固定当前时间,设置一小时后的 resets_at → 构造 Pro 超限错误 → 输出文案中应包含购买 credits 的链接和格式化后的重试时间。

调用关系:它用 with_now_override 控制时间,验证 Pro 套餐专属文案和重试时间格式能一起正确工作。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(hours)。

usage_limit_reached_error_hides_upsell_for_non_codex_limit_name359–383 ↗
fn usage_limit_reached_error_hides_upsell_for_non_codex_limit_name()

作用:检查当超限项不是标准 Codex 限制名时,系统会隐藏升级或购买推广文案,只提示切换模型或到时间再试。

数据流:进去没有外部输入 → 它固定时间,构造一个 limit_name 为 codex_other 且带 promo_message 的限流快照 → 输出文案应提到这个具体限制名,但不显示购买更多额度的推广语。

调用关系:它验证一个容易忽略的安全文案规则:并不是所有限制都适合推销升级。它借助 with_now_override 固定重试时间,并改写 rate_limit_snapshot 的部分字段。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(hours)。

usage_limit_reached_includes_minutes_when_available386–401 ↗
fn usage_limit_reached_includes_minutes_when_available()

作用:检查离恢复只剩几分钟时,超限提示会包含正确的重试时间。

数据流:进去没有外部输入 → 它固定当前时间,把 resets_at 设为 5 分钟后 → 构造没有套餐信息的超限错误 → 输出应是通用超限提示加上格式化重试时间。

调用关系:它是重试时间格式测试之一。with_now_override 保证“5 分钟后”这个条件稳定,错误格式化逻辑负责把时间写进文案。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(minutes)。

unexpected_status_cloudflare_html_is_simplified404–421 ↗
fn unexpected_status_cloudflare_html_is_simplified()

作用:检查服务器返回 Cloudflare 拦截 HTML 页面时,错误信息会被简化成更好懂的固定提示。Cloudflare 是常见的网络防护服务。

数据流:进去没有外部输入 → 它构造一个 403 状态、正文像 HTML 拦截页、带 cf-ray 编号的 UnexpectedResponseError → 转成字符串后,应显示简化提示、状态、网址和 cf-ray。

调用关系:它测试异常 HTTP 响应的特殊清理规则。这样用户不会看到一大坨 HTML,而是看到更直接的被拦截说明。

调用图:外部调用 1 个(assert_eq!)。

unexpected_status_non_html_is_unchanged424–440 ↗
fn unexpected_status_non_html_is_unchanged()

作用:检查普通文本错误不会被当成 Cloudflare HTML 简化,而是原样放进异常状态提示里。

数据流:进去没有外部输入 → 它构造一个 403、正文为 plain text error 的 UnexpectedResponseError → 输出字符串应包含原始正文和 URL。

调用关系:它和 Cloudflare HTML 测试成对出现,保证简化规则只在该简化时生效,不会误伤普通错误内容。

调用图:外部调用 1 个(assert_eq!)。

unexpected_status_prefers_error_message_when_present443–461 ↗
fn unexpected_status_prefers_error_message_when_present()

作用:检查服务器返回 JSON 错误时,如果里面有 error.message,提示会优先使用这句更有用的话,而不是整段原始 JSON。

数据流:进去没有外部输入 → 它构造一个 401 响应,body 里有 JSON 格式的错误消息和 request id → 输出字符串应提取 Workspace is not authorized in this region. 并带上 URL 和请求编号。

调用关系:它验证 UnexpectedResponseError 的“挑重点说”能力。这样上层展示错误时,用户看到的是真正原因,而不是机器格式的数据。

调用图:外部调用 1 个(assert_eq!)。

unexpected_status_truncates_long_body_with_ellipsis464–483 ↗
fn unexpected_status_truncates_long_body_with_ellipsis()

作用:检查异常响应正文太长时会被截断,并在末尾加省略号,避免错误信息刷屏。

数据流:进去没有外部输入 → 它生成一段超过最大长度的 x 字符串,放进 UnexpectedResponseError → 输出应只保留允许的最大字节数,再加 ...,同时保留 URL 和请求编号。

调用关系:它保护错误展示的长度限制。这里用 format! 拼出预期结果,再用断言确认实际格式化没有超长。

调用图:外部调用 2 个(assert_eq!, format!)。

unexpected_status_includes_cf_ray_and_request_id486–503 ↗
fn unexpected_status_includes_cf_ray_and_request_id()

作用:检查异常状态错误会带上 cf-ray 和 request id,方便排查问题。request id 是一次请求的编号,cf-ray 是 Cloudflare 给请求的追踪编号。

数据流:进去没有外部输入 → 它构造一个带 URL、cf-ray、request id 的 401 UnexpectedResponseError → 输出字符串应把这些排查信息都拼进去。

调用关系:它确保错误文案不只给用户看,也能帮开发或支持人员查日志。它测试的是 UnexpectedResponseError 的附加信息拼接。

调用图:外部调用 1 个(assert_eq!)。

unexpected_status_includes_identity_auth_details506–523 ↗
fn unexpected_status_includes_identity_auth_details()

作用:检查身份认证相关的异常响应,会把认证错误和认证错误码也写进提示,便于判断是不是令牌过期或缺少授权头。

数据流:进去没有外部输入 → 它构造一个 401 错误,带普通正文、URL、cf-ray、request id、auth error 和 auth error code → 输出应包含所有这些诊断字段。

调用关系:它补充测试认证失败的排查信息。和 request id、cf-ray 测试一起,保证服务端返回的关键线索不会丢。

调用图:外部调用 1 个(assert_eq!)。

usage_limit_reached_includes_hours_and_minutes526–543 ↗
fn usage_limit_reached_includes_hours_and_minutes()

作用:检查恢复时间是几小时几分钟后时,Plus 用户超限提示仍能显示正确重试时间。

数据流:进去没有外部输入 → 它固定当前时间,把 resets_at 设为 3 小时 32 分钟后 → 构造 Plus 超限错误 → 输出应包含升级/购买建议和格式化后的重试时间。

调用关系:它是重试时间跨度测试的一种。with_now_override 负责固定起点,hours 和 minutes 构造时间差,错误格式化逻辑负责生成最终句子。

调用图:调用 1 个内部函数(with_now_override);外部调用 2 个(hours, minutes)。

usage_limit_reached_includes_days_hours_minutes546–562 ↗
fn usage_limit_reached_includes_days_hours_minutes()

作用:检查恢复时间跨越多天、小时和分钟时,超限提示仍能把重试时间说清楚。

数据流:进去没有外部输入 → 它固定当前时间,把 resets_at 设为 2 天 3 小时 5 分钟后 → 构造无套餐信息的超限错误 → 输出应是通用提示加正确重试时间。

调用关系:它覆盖更长等待时间的格式。它和分钟、小时分钟、少于一分钟的测试一起,保证时间显示在各种距离下都稳定。

调用图:调用 1 个内部函数(with_now_override);外部调用 3 个(days, hours, minutes)。

usage_limit_reached_less_than_minute565–580 ↗
fn usage_limit_reached_less_than_minute()

作用:检查离恢复不到一分钟时,超限提示也能正常显示重试时间,而不是出错或显示空内容。

数据流:进去没有外部输入 → 它固定当前时间,把 resets_at 设为 30 秒后 → 构造通用超限错误 → 输出应包含由 format_retry_timestamp 得到的重试时间。

调用关系:它测试时间格式化的边界情况。with_now_override 让“30 秒后”可重复,避免真实时间流逝导致断言不稳定。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(seconds)。

usage_limit_reached_with_promo_message583–602 ↗
fn usage_limit_reached_with_promo_message()

作用:检查服务端给了自定义推广提示时,超限文案会把这段话合进去,并仍然保留重试时间。

数据流:进去没有外部输入 → 它固定当前时间,设置 30 秒后的重置时间,并构造带 promo_message 的超限错误 → 输出应先说明超限,再接自定义推广话术,最后提示何时再试。

调用关系:它验证 UsageLimitReachedError 不只会按套餐生成固定文案,也能接纳服务端下发的 promo_message。时间部分仍由 with_now_override 稳定控制。

调用图:调用 1 个内部函数(with_now_override);外部调用 1 个(seconds)。

protocol/src/exec_output_tests.rs源码 ↗
testtest run

这份测试文件是在给一个很实际的问题兜底:程序从 shell(命令行窗口)拿到的输出,本质上先是一串字节,不一定已经是现代常用的 UTF-8 编码。比如 Windows 上的俄文、法文重音字符、弯引号,可能来自 CP1251、CP866、Windows-1252、Latin-1 这些旧编码。如果程序只按 UTF-8 硬读,就会出现乱码。这里的每个测试都像拿一张不同语言、不同旧打印机打出来的小纸条,交给同一个解码入口 decode_shell_output,看它能不能还原成正常文字。测试既确认正常 UTF-8 不会被改坏,也确认旧编码能被“聪明地猜出来”。最后还有一个保护:如果真的猜不出来,也要退回到有替换符的显示方式,至少让用户看到有内容,而不是程序崩掉。

函数细节9
test_utf8_shell_output10–13 ↗
fn test_utf8_shell_output()

作用:这个测试确认最普通、最正确的 UTF-8 输出不会被额外处理坏。也就是说,本来已经是正常文字的内容,经过解码后应该原样回来。

数据流:进去的是俄文“пример”的 UTF-8 字节 → 测试把这些字节交给 decode_shell_output → 出来应该还是同一个俄文字符串;它用 assert_eq!(断言相等,不相等就让测试失败)检查结果。

调用关系:这是整组测试的基准线。测试运行器会单独执行它,它把具体解码工作交给 decode_shell_output,然后用 assert_eq! 判断解码入口有没有把好数据改坏。

调用图:外部调用 1 个(assert_eq!)。

test_cp1251_shell_output16–19 ↗
fn test_cp1251_shell_output()

作用:这个测试确认 Windows 上常见的 CP1251 编码俄文能被正确读出来。CP1251 是一种老式西里尔文字编码,很多俄文 Windows 输出会用它。

数据流:进去的是一串 CP1251 编码的字节 → decode_shell_output 负责把它们当作 shell 输出来解码 → 出来应该是正常的“пример”;测试用 assert_eq! 检查是否完全一致。

调用关系:测试运行器执行它时,它模拟 VSCode 或 Windows shell 送来的俄文字节。它不自己做解码,而是通过 decode_shell_output 走和其他测试一样的入口,最后交给 assert_eq! 判定。

调用图:外部调用 1 个(assert_eq!)。

test_cp866_shell_output22–25 ↗
fn test_cp866_shell_output()

作用:这个测试确认 CP866 编码的俄文也能被识别。CP866 是传统 Windows cmd.exe 里常见的俄文命令行编码。

数据流:进去的是 CP866 格式的字节 → decode_shell_output 把这些原始字节包装成命令输出并解码 → 出来应是“пример”;如果结果不同,assert_eq! 会让测试失败。

调用关系:它覆盖的是另一种 Windows 命令行来源。测试本身只负责准备样例和核对结果,真正被验证的是 decode_shell_output 背后的 StreamOutput 解码行为。

调用图:外部调用 1 个(assert_eq!)。

test_windows_1252_smart_decoding28–34 ↗
fn test_windows_1252_smart_decoding()

作用:这个测试确认 Windows-1252 里的弯引号和短横线能变成正确的 Unicode 字符。Unicode 可以理解为现代系统统一表示文字和符号的大字典。

数据流:进去的是 Windows-1252 字节,其中包含弯双引号和 en dash 这种普通 UTF-8 不能直接读的符号 → decode_shell_output 尝试智能解码 → 出来应该是“”“ test – dash”这样的正常符号文本;assert_eq! 负责核对。

调用关系:它专门验证“智能解码”不是只会处理字母,也能处理常见标点。它在测试流程中调用 decode_shell_output,再由 assert_eq! 对最终字符串做精确比较。

调用图:外部调用 1 个(assert_eq!)。

test_smart_decoding_improves_over_lossy_utf837–49 ↗
fn test_smart_decoding_improves_over_lossy_utf8()

作用:这个测试证明新的智能解码确实比 Rust 自带的宽松 UTF-8 解码更好。宽松解码会把读不懂的字节换成 U+FFFD 这个替换符,而这里希望尽量还原真实字符。

数据流:进去的是一串 Windows-1252 的弯引号和短横线字节 → 先用 String::from_utf8_lossy 证明普通宽松 UTF-8 会产生替换符 → 再用 decode_shell_output 解码 → 出来应该保留正确的弯引号和短横线;assert! 检查旧方式确实会坏,assert_eq! 检查新方式修好了。

调用关系:这是一个回归测试,也就是防止以前修好的问题将来又坏掉。它一边拿标准宽松解码作反面例子,一边把实际工作交给 decode_shell_output,并用 assert!、assert_eq! 两步证明改进有效。

调用图:外部调用 2 个(assert!, assert_eq!)。

test_mixed_ascii_and_legacy_encoding52–55 ↗
fn test_mixed_ascii_and_legacy_encoding()

作用:这个测试确认英文普通字符和旧编码字符混在一起时也能读对。现实里的命令输出常常是英文提示加少量带重音的词,比如 café。

数据流:进去的是“Output: ”这样的 ASCII 字节加上 Latin-1 编码的 é → decode_shell_output 把整段字节作为一次 shell 输出处理 → 出来应该是“Output: café”;assert_eq! 检查混合内容没有乱码。

调用关系:它模拟真实命令输出的常见样子,而不是只测单个外语单词。测试运行时,它调用 decode_shell_output 做统一解码,再用 assert_eq! 验证英文部分和特殊字符都保住了。

调用图:外部调用 1 个(assert_eq!)。

test_pure_latin1_shell_output58–61 ↗
fn test_pure_latin1_shell_output()

作用:这个测试确认纯 Latin-1 编码的文本仍然能正常解码。Latin-1 是一种老式西欧文字编码,常用来表示 é 这类字符。

数据流:进去的是 Latin-1 字节形式的“café” → decode_shell_output 尝试识别并转换 → 出来应该是 Unicode 字符串“café”;assert_eq! 负责比较结果。

调用关系:它是对旧测试场景的保护,防止新加入的智能判断影响原来已经支持的 Latin-1。它通过 decode_shell_output 触发同一条解码路径,再用 assert_eq! 确认没有退步。

调用图:外部调用 1 个(assert_eq!)。

test_invalid_bytes_still_fall_back_to_lossy64–68 ↗
fn test_invalid_bytes_still_fall_back_to_lossy()

作用:这个测试确认遇到实在无法可靠识别的字节时,程序仍然会给用户一个可显示的结果,而不是崩溃或丢空。这里的退路是宽松 UTF-8:读不懂的地方用替换符显示。

数据流:进去的是一组三个很难明确判断编码的字节 → decode_shell_output 尝试解码,无法更好识别时退回到 String::from_utf8_lossy 的结果 → 出来应和宽松 UTF-8 的输出一致;assert_eq! 检查这个兜底行为。

调用关系:它测试的是最后一道安全网。测试运行器执行它时,它调用 decode_shell_output,并把结果和 Rust 的宽松解码结果对照,确保失败时的行为可预期。

调用图:外部调用 1 个(assert_eq!)。

decode_shell_output70–77 ↗
fn decode_shell_output(bytes: &[u8]) -> String

作用:这是测试里的小帮手,用来把一段原始字节伪装成真正的命令输出,然后走项目里的正式解码方法。这样每个测试不用重复写同样的包装代码。

数据流:进去的是字节切片,也就是一段还没确定是什么编码的原始数据 → 它创建一个 StreamOutput,把这些字节放进 text 字段,并表示没有因为行数太多而截断 → 然后调用 from_utf8_lossy 做实际解码 → 出来的是解码后的 text 字符串。

调用关系:上面所有测试都会把样例字节交给它。它本身不做断言,也不判断对错,只负责把测试数据送进 StreamOutput 的正式解码流程,让各个测试用同一条路验证结果。

code-mode-protocol/src/session_tests.rs源码 ↗
testtest

这个测试文件专门检查 StartedCell 这个“已经启动的代码单元”在等待第一次远程响应时,能不能原样保留错误信息。可以把它想成:你派人去远处开一台机器,远处回话说“启动失败”,本地记录员不能把这句话弄丢,也不能改成别的模糊提示。测试里先用 oneshot 通道(一种只能发送一次消息的小管道)模拟远端只回一次启动结果,并故意发送一个错误:"remote runtime failed"。然后把这个接收端交给 StartedCell。最后调用 initial_response 等待结果,并断言拿到的错误和远端发来的一模一样。这个文件本身不实现业务功能,但它守住了一个重要行为:失败原因必须透明传回调用者。

函数细节1
started_cell_preserves_remote_initial_response_errors8–19 ↗
async fn started_cell_preserves_remote_initial_response_errors()

作用:这个测试确认 StartedCell 在收到远端启动失败时,会把原始错误文字原封不动地交还出来。有人改动会话启动流程时,可以靠它及时发现“错误信息被吞掉、被包装错、或变成别的内容”的问题。

数据流:进去的是一个模拟出来的远端响应通道:发送端先放入 Err("remote runtime failed"),接收端交给 StartedCell::from_result_receiver,同时用 CellId::new 创建一个单元编号。函数随后等待 started.initial_response() 的结果。出来的是一次断言:实际拿到的结果必须等于同样的 Err("remote runtime failed");它不修改长期状态,只是在测试中验证这个行为。

调用关系:这个函数由 Tokio 测试框架在跑测试时自动调用。它先调用 channel 建一个一次性通信管道,再调用 new 创建 CellId,接着调用 from_result_receiver 组装 StartedCell,最后用 assert_eq! 检查 initial_response 返回的内容是否正确。它扮演的是守门员:保证会话启动相关代码以后被修改时,远端初始错误仍然能准确传到外面。

调用图:调用 2 个内部函数(new, from_result_receiver);外部调用 2 个(assert_eq!, channel)。

code-mode/src/service_contract_tests.rs源码 ↗
testtest

Code Mode 会执行一段代码,并把它放在一个“单元格”里运行。运行过程中,代码可能先吐出一部分结果再暂停,也可能调用外部工具、发送通知,或者被用户强制终止。这个测试文件专门盯住这些容易出错的边界情况:比如定时暂停和已有输出谁先算数,终止和自然完成同时发生时谁赢,终止前是否先取消还没结束的通知或工具调用。文件里还做了几个假的代理对象,模拟“外部回调一直卡住”或“收到取消信号后才结束”的情况。这样测试就能像人为制造拥堵路口一样,检查服务在并发和竞态下是否仍按规则办事。

函数细节23
HeldNotificationDelegate::new40–49 ↗
fn new() -> (Arc<Self>, mpsc::UnboundedReceiver<DelegateEvent>)

作用:创建一个会故意“卡住通知收尾”的假代理。测试用它来检查:第一次终止还没清理完时,第二次终止会不会被正确拒绝。

数据流:进去没有业务输入 → 它建一个事件通道和一个通知开关 → 出来一个可共享的 HeldNotificationDelegate,以及一个接收事件的管道,测试可以从管道里看到通知何时开始、何时被取消。

调用关系:它被 repeated_termination_is_rejected_while_callback_cleanup_is_pending 使用。后面的 notify 会等 release_notification 放行,所以这个构造函数是在测试里布置“清理被故意拖住”的现场。

调用图:被 1 处调用(repeated_termination_is_rejected_while_callback_cleanup_is_pending);外部调用 3 个(new, new, unbounded_channel)。

HeldNotificationDelegate::release_notification51–53 ↗
fn release_notification(&self)

作用:放行之前故意卡住的通知清理。没有它,第一次终止会一直等通知回调真正结束。

数据流:进去是这个假代理自身 → 它向内部 Notify 发一个信号,相当于按下放行按钮 → 等在 notify 里的异步任务收到信号后继续往下走。

调用关系:它通常在测试确认第二次终止被拒绝之后调用。这样测试先观察“清理中”的状态,再让第一次终止正常收尾。

调用图:外部调用 1 个(notify_one)。

HeldNotificationDelegate::invoke_tool57–66 ↗
fn invoke_tool(
        &'a self,
        _invocation: CodeModeNestedToolCall,
        cancellation_token: CancellationToken,
    ) -> ToolInvocationFuture<'a>

作用:模拟一个工具调用,但这个假代理只等取消信号,然后返回“已取消”。它主要是为了完整实现代理接口。

数据流:进去有一次工具调用和一个取消令牌(像一个可被拉下的停止开关)→ 它等待取消令牌被触发 → 出来一个错误字符串 cancelled。

调用关系:这是 CodeModeSessionDelegate 接口的一部分。运行时代码如果调用外部工具,会走到这里;在这个假代理里,它不做真实工具工作,只配合取消流程。

调用图:外部调用 2 个(pin, cancelled)。

HeldNotificationDelegate::notify68–82 ↗
fn notify(
        &'a self,
        _call_id: String,
        _cell_id: CellId,
        _text: String,
        cancellation_token: CancellationToken,
    ) -> NotificationFuture<'a>

作用:模拟一个通知回调:先报告通知开始,再等取消,然后故意停住,直到测试手动放行。它用来制造“终止正在清理但还没结束”的状态。

数据流:进去有通知编号、单元格编号、文本和取消令牌 → 它先发出 NotificationStarted 事件,随后等取消令牌触发,再发出 NotificationCancelled,最后等 release_notification 的放行信号 → 出来 Ok,表示通知清理完成。

调用关系:服务执行 notify 语句时会调用它。repeated_termination_is_rejected_while_callback_cleanup_is_pending 借它把第一次 terminate 卡在回调清理阶段,然后测试第二次 terminate 是否被拒绝。

调用图:外部调用 4 个(pin, cancelled, notified, send)。

HeldNotificationDelegate::cell_closed84–88 ↗
fn cell_closed(&self, cell_id: &CellId)

作用:记录某个执行单元格已经关闭。测试用它确认清理结束后服务确实通知了代理。

数据流:进去是一个单元格编号 → 它复制这个编号并发送 CellClosed 事件 → 测试端可以从事件管道读到这个关闭消息。

调用关系:当服务结束一个单元格时会调用它。它配合 notify 里的事件,让测试能看清“取消通知”和“关闭单元格”的先后顺序。

调用图:外部调用 3 个(send, clone, CellClosed)。

spawn_cell_control_harness99–144 ↗
fn spawn_cell_control_harness(
    initial_yield_time_ms: Option<u64>,
    delegate: Arc<dyn CodeModeSessionDelegate>,
) -> CellControlHarness

作用:搭一个可手动操控的单元格控制测试台。测试可以直接塞运行时事件、发送终止命令,而不用真的跑完整服务流程。

数据流:进去是初始暂停等待时间和一个代理对象 → 它创建多条消息通道,启动一个假运行时,再启动 run_cell_control 控制任务 → 出来 CellControlHarness,里面有发事件的入口、发控制命令的入口、初始响应接收器和后台任务句柄。

调用关系:yield_timer_preempts_buffered_runtime_output、queued_termination_preempts_unobserved_runtime_completion、observed_natural_completion_wins_over_termination 都用它。它把测试直接接到单元格控制层,方便精确制造竞态。

调用图:调用 2 个内部函数(cell_id, execute_request);被 3 处调用(observed_natural_completion_wins_over_termination, queued_termination_preempts_unobserved_runtime_completion, yield_timer_preempts_buffered_runtime_output);外部调用 10 个(new, new, new, new, new, new, Runtime, unbounded_channel, channel, spawn)。

BlockingDelegate::new147–157 ↗
fn new() -> (Arc<Self>, mpsc::UnboundedReceiver<DelegateEvent>)

作用:创建一个收到取消信号才会结束的假代理。它能记录通知或工具调用是否真的被取消完。

数据流:进去没有业务输入 → 它建事件通道,并把 notification_finished 和 tool_finished 两个标记设为 false → 出来共享代理和事件接收器。

调用关系:多个测试用它确认服务在返回结果或终止结果之前,是否已经把外部回调取消并收拾干净。

调用图:被 3 处调用(natural_completion_cleans_up_callbacks_before_responding, observed_natural_completion_wins_over_termination, termination_cancels_pending_callbacks_before_responding);外部调用 3 个(new, new, unbounded_channel)。

BlockingDelegate::invoke_tool161–173 ↗
fn invoke_tool(
        &'a self,
        _invocation: CodeModeNestedToolCall,
        cancellation_token: CancellationToken,
    ) -> ToolInvocationFuture<'a>

作用:模拟一个会一直等取消的工具调用。它让测试检查:代码自然结束后,尚未完成的工具调用会不会被取消。

数据流:进去有工具调用信息和取消令牌 → 它先发送 ToolStarted,等待取消令牌触发,然后把 tool_finished 标记为 true,再发送 ToolCancelled → 出来错误 cancelled。

调用关系:运行时代码调用 tools.block 时会走到这里。natural_completion_cleans_up_callbacks_before_responding 用它确认服务返回最终结果前已经取消工具回调。

调用图:外部调用 4 个(store, pin, cancelled, send)。

BlockingDelegate::notify175–189 ↗
fn notify(
        &'a self,
        _call_id: String,
        _cell_id: CellId,
        _text: String,
        cancellation_token: CancellationToken,
    ) -> NotificationFuture<'a>

作用:模拟一个会一直等取消的通知回调。它帮助测试确认终止单元格时,通知回调会先被取消再返回终止结果。

数据流:进去有通知编号、单元格编号、通知文本和取消令牌 → 它发送 NotificationStarted,等待取消,设置 notification_finished 为 true,再发送 NotificationCancelled → 出来错误 cancelled。

调用关系:代码调用 notify 时会触发它。termination_cancels_pending_callbacks_before_responding 和 observed_natural_completion_wins_over_termination 都靠它观察通知取消时机。

调用图:外部调用 4 个(store, pin, cancelled, send)。

BlockingDelegate::cell_closed191–195 ↗
fn cell_closed(&self, cell_id: &CellId)

作用:把“单元格关闭了”这件事发给测试。它用来验证关闭通知发生在回调清理之后。

数据流:进去是单元格编号 → 它复制编号并发送 CellClosed 事件 → 测试从事件接收器读到这个事件。

调用关系:服务关闭单元格时调用它。它和 BlockingDelegate 的 notify、invoke_tool 事件一起组成一条可检查的时间线。

调用图:外部调用 3 个(send, clone, CellClosed)。

cell_id198–200 ↗
fn cell_id(value: &str) -> CellId

作用:把普通字符串包装成 CellId。这样测试里写 cell_id(1) 这类含义时更短、更统一。

数据流:进去是字符串片段 → 它复制成 String 并交给 CellId::new → 出来一个正式的单元格编号对象。

调用关系:很多测试和 spawn_cell_control_harness 都用它生成同一个格式的单元格编号,避免每个断言里重复写包装代码。

调用图:调用 1 个内部函数(new);被 5 处调用(queued_termination_preempts_unobserved_runtime_completion, repeated_termination_is_rejected_while_callback_cleanup_is_pending, returns_and_resumes_from_the_pending_frontier, second_observer_is_rejected_without_displacing_the_first, spawn_cell_control_harness)。

execute_request202–210 ↗
fn execute_request(source: &str) -> ExecuteRequest

作用:快速生成一个默认执行请求。测试只需要填要运行的源码,其它字段用固定默认值。

数据流:进去是一段源码文本 → 它放进 ExecuteRequest,并填好 tool_call_id、空工具列表、短暂停时间等默认项 → 出来一个可以交给服务执行的请求。

调用关系:多数组合测试都用它开头。需要特殊工具或特殊参数的测试会在它的基础上再覆盖字段。

调用图:被 6 处调用(natural_completion_cleans_up_callbacks_before_responding, repeated_termination_is_rejected_while_callback_cleanup_is_pending, second_observer_is_rejected_without_displacing_the_first, spawn_cell_control_harness, termination_cancels_pending_callbacks_before_responding, yields_and_resumes);外部调用 1 个(new)。

blocking_tool212–221 ↗
fn blocking_tool() -> ToolDefinition

作用:定义一个名叫 block 的假工具。测试代码调用这个工具时,会落到 BlockingDelegate 的阻塞工具逻辑里。

数据流:进去没有业务输入 → 它填好工具名称、协议里的工具名、工具类型等字段 → 出来一个 ToolDefinition,表示服务允许代码调用这个工具。

调用关系:natural_completion_cleans_up_callbacks_before_responding 使用它,让源码中的 tools.block 调用真的能被服务识别并转给代理。

调用图:调用 1 个内部函数(plain);外部调用 1 个(new)。

next_event223–228 ↗
async fn next_event(events_rx: &mut mpsc::UnboundedReceiver<DelegateEvent>) -> DelegateEvent

作用:从测试事件管道里等下一个代理事件,并设置 2 秒超时。这样测试失败时不会永远卡住。

数据流:进去是事件接收器 → 它最多等 2 秒接收一条 DelegateEvent → 出来那条事件;如果超时或通道关闭,就让测试直接失败并给出明确原因。

调用关系:各个检查回调顺序的测试都会用它。它把异步事件读取包装成一个安全的小工具。

调用图:外部调用 3 个(from_secs, recv, timeout)。

yield_timer_preempts_buffered_runtime_output231–272 ↗
async fn yield_timer_preempts_buffered_runtime_output()

作用:测试暂停计时器到点时,即使运行时已经排队了一些输出,第一次响应也应该先返回“已暂停”且不带那些输出。

数据流:进去是测试自己搭的控制台 → 它发送 Started 和一条排队输出,再等待初始响应 → 初始响应应是空内容的 Yielded;之后发送终止命令,终止响应才带上之前排队的输出。

调用关系:它通过 spawn_cell_control_harness 直接驱动单元格控制层,重点验证暂停计时器和缓冲输出之间的优先级。

调用图:调用 1 个内部函数(spawn_cell_control_harness);外部调用 4 个(new, assert_eq!, ContentItem, channel)。

queued_termination_preempts_unobserved_runtime_completion275–302 ↗
async fn queued_termination_preempts_unobserved_runtime_completion()

作用:测试如果运行时已经完成但这个完成还没被观察到,而终止命令排队进来了,最终应按终止处理。

数据流:进去是一个长暂停时间的控制台 → 它先塞入 Result 完成事件,再发送 Terminate 命令 → 终止响应和初始响应都应变成 Terminated,而不是 Result。

调用关系:它用 spawn_cell_control_harness 精确安排事件顺序,用 cell_id 构造断言里的编号,检查未观察到的自然完成不会抢过已排队的终止。

调用图:调用 2 个内部函数(cell_id, spawn_cell_control_harness);外部调用 5 个(new, new, new, assert_eq!, channel)。

yields_and_resumes305–339 ↗
async fn yields_and_resumes()

作用:测试代码主动让出控制权后,服务能先返回前半段输出,之后等待时继续跑完后半段。

数据流:进去是一段会输出 before、调用 yield_control、再输出 after 的源码 → execute 先返回一个单元格,initial_response 得到 Yielded 和 before → wait 继续观察同一单元格,最后得到 Result 和 after。

调用关系:这是完整服务层面的基础流程测试。它使用 CodeModeService::new 和 execute_request,验证“暂停再继续”的对外契约。

调用图:调用 2 个内部函数(new, execute_request);外部调用 1 个(assert_eq!)。

returns_and_resumes_from_the_pending_frontier342–390 ↗
async fn returns_and_resumes_from_the_pending_frontier()

作用:测试 execute_to_pending 会停在“等待中的边界”,之后 wait_to_pending 能从那里继续,并在定时器触发后完成。

数据流:进去是一段先等很久再输出 after 的源码 → execute_to_pending 先返回 Pending,说明单元格还活着但暂时没结果 → 测试手动向运行时发 TimeoutFired,再 wait_to_pending,最后得到 Completed 和 after。

调用关系:它直接访问服务内部单元格的 runtime_tx 来模拟定时器触发。这个测试确认 pending 模式不会丢失继续运行的位置。

调用图:调用 2 个内部函数(new, cell_id);外部调用 1 个(assert_eq!)。

observed_natural_completion_wins_over_termination393–460 ↗
async fn observed_natural_completion_wins_over_termination()

作用:测试一旦自然完成已经被控制层观察到,后来的终止命令就不能把结果改成“已终止”。

数据流:进去是带 BlockingDelegate 的控制台 → 它先让单元格暂停,再发送输出和 Result,随后触发一个通知并确认通知开始 → 再发送终止命令,返回的仍应是 Result;同时通知被取消,最后单元格关闭。

调用关系:它通过 spawn_cell_control_harness 制造“完成已观察到但通知还没收尾”的场景,验证完成结果优先于后来的终止,同时仍会清理回调。

调用图:调用 2 个内部函数(new, spawn_cell_control_harness);外部调用 5 个(new, assert!, assert_eq!, ContentItem, channel)。

termination_cancels_pending_callbacks_before_responding463–500 ↗
async fn termination_cancels_pending_callbacks_before_responding()

作用:测试终止一个还挂着通知回调的单元格时,服务必须先取消通知,再把终止结果返回给调用者。

数据流:进去是一段先 notify 再永远等待的源码 → 代理报告 NotificationStarted,初始响应是 Yielded → 调用 terminate 后得到 Terminated;同时代理标记通知已结束,并依次发出 NotificationCancelled 和 CellClosed。

调用关系:它使用 BlockingDelegate 和 CodeModeService::with_delegate,从服务外部视角验证 terminate 的清理顺序。

调用图:调用 3 个内部函数(with_delegate, new, execute_request);外部调用 2 个(assert!, assert_eq!)。

repeated_termination_is_rejected_while_callback_cleanup_is_pending503–551 ↗
async fn repeated_termination_is_rejected_while_callback_cleanup_is_pending()

作用:测试第一次终止正在等通知清理时,第二次终止请求会被拒绝,而不是插队或重复清理。

数据流:进去是 HeldNotificationDelegate 和一个会 notify 后挂起的单元格 → 第一次 terminate 在后台启动,并卡在通知清理处 → 第二次 terminate 立即返回错误“已经在终止中”;测试再放行通知,第一次终止正常返回 Terminated。

调用关系:它用 HeldNotificationDelegate::new 制造可控阻塞,用 release_notification 收尾。这个测试保护的是“同一个单元格只能有一个终止流程”的规则。

调用图:调用 4 个内部函数(with_delegate, new, cell_id, execute_request);外部调用 4 个(clone, new, assert_eq!, spawn)。

second_observer_is_rejected_without_displacing_the_first554–598 ↗
async fn second_observer_is_rejected_without_displacing_the_first()

作用:测试同一个正在运行的单元格不能同时有两个等待观察者。第二个等待请求应失败,而且不能影响第一个等待者。

数据流:进去是一个永远等待的单元格 → 第一次 begin_wait 成功占住观察位置 → 第二次 wait 返回错误“已经有活跃观察者” → 终止单元格后,terminate 和第一个观察者都收到同一个 Terminated 结果。

调用关系:它用 CodeModeService::new 走真实服务接口,检查并发观察的排他规则,避免两个调用者同时消费同一份运行结果。

调用图:调用 3 个内部函数(new, cell_id, execute_request);外部调用 2 个(new, assert_eq!)。

natural_completion_cleans_up_callbacks_before_responding601–634 ↗
async fn natural_completion_cleans_up_callbacks_before_responding()

作用:测试代码自然完成时,如果还有工具调用挂着,服务要先取消工具回调,再把最终结果交给外部。

数据流:进去是启用了 block 工具的执行请求,源码先调用 tools.block 再输出 done → 代理先报告 ToolStarted → initial_response 得到 Result 和 done;同时 tool_finished 为 true,随后看到 ToolCancelled 和 CellClosed。

调用关系:它把 blocking_tool 注册进请求,并让 BlockingDelegate 接住工具调用。这个测试确认自然完成路径和终止路径一样,都要先清理未完成回调。

调用图:调用 3 个内部函数(with_delegate, new, execute_request);外部调用 3 个(assert!, assert_eq!, vec!)。

传输、代理与套接字支持测试

这些测试检查较底层的传输辅助工具、重试/认证解析、代理检查规则、套接字工具以及 TLS 提供者初始化。

rmcp-client/src/executor_process_transport_tests.rs源码 ↗
testtest

这个测试文件关心的是一个很常见的问题:程序从外部进程读取数据时,数据可能像水龙头滴水一样分几次到达,而不是整整齐齐一行一行来。LineBuffer 就像一个临时收纳盒,先把收到的字节存起来,看到换行符才拿出一整行。这里的测试会检查三件关键事:第一,之前已经扫过但没找到换行的内容,下次不要傻乎乎从头再扫,避免重复做工;第二,如果一次收到多行,要能一行一行拆出来,并把最后没写完的半行留下;第三,如果已经到文件结尾了,即使最后没有换行符,也要能把剩下的内容取出来。它不是业务功能本身,而是在替底层读数据的可靠性兜底。

函数细节3
searches_only_new_bytes_after_partial_line7–42 ↗
fn searches_only_new_bytes_after_partial_line()

作用:这个测试确认 LineBuffer 遇到“先来半行、后面才补上换行”的情况时,能把完整行取出来,并且不会重复扫描已经检查过的旧内容。

数据流:一开始创建一个空的 LineBuffer。先放入“partial”,因为没有换行,所以取不出完整行,缓冲区记住这些字节已经扫过。再追加“ line”,仍然没有换行,也继续只记录为半行。最后追加“\nnext”,这时它取出“partial line”作为完整一行,并把换行后的“next”留在缓冲区里,等待后续数据。

调用关系:这是测试运行时由测试框架自动调用的用例。它主要直接使用 LineBuffer 的追加和取行能力,再用 assert_eq!(断言相等,用来检查实际结果和预期是否一样)确认每一步状态都对。

调用图:外部调用 2 个(assert_eq!, default)。

splits_multiple_lines_and_retains_partial_tail45–59 ↗
fn splits_multiple_lines_and_retains_partial_tail()

作用:这个测试确认 LineBuffer 一次收到多行内容时,可以连续拆出完整行,同时把最后那段还没换行的残尾保留下来。

数据流:它先创建空缓冲区,然后一次放入“first\nsecond\npartial”。第一次取行得到“first”,第二次取行得到“second”,第三次因为“partial”后面没有换行,所以返回没有完整行。最后检查缓冲区里确实只剩“partial”,并且记录它已经被扫描过。

调用关系:这是测试框架执行的一条独立测试。它模拟外部进程一次吐出好几行再加半行的场景,检查 LineBuffer 的 take_line 是否能被反复调用,并按顺序吐出正确结果。

调用图:外部调用 2 个(assert_eq!, default)。

takes_unterminated_remaining_bytes_at_eof62–72 ↗
fn takes_unterminated_remaining_bytes_at_eof()

作用:这个测试确认到了输入结束时,即使最后一段文字没有换行符,LineBuffer 也能把剩下内容交出来,不会把最后一行弄丢。

数据流:它创建空缓冲区,放入“remaining”。因为没有换行,普通取行拿不到结果。随后调用 take_remaining,表示“已经没有更多数据了,把剩下的都给我”,于是得到“remaining”。最后缓冲区被清空,回到默认状态。

调用关系:这是测试框架自动运行的收尾场景测试。它覆盖的是文件结束或子进程关闭输出时的行为,确保 LineBuffer 除了按换行取数据,也能在结束时把未换行的尾巴交给上层。

调用图:外部调用 2 个(assert_eq!, default)。

rmcp-client/src/http_client_adapter/www_authenticate_tests.rs源码 ↗
testtest

当客户端访问某个接口时,服务器可能会说:你的令牌能登录,但权限范围不够,需要额外的 scope(权限范围,比如 files:read 表示能读文件)。这些信息通常藏在 HTTP 头里的 WWW-Authenticate 字段中,格式还可能有大小写变化、引号、转义字符、多个认证方案混在一起等麻烦情况。这个测试文件就是给解析代码做“体检”:它用一组正常、异常、容易误判的字符串,确认代码只在真正看到 Bearer error="insufficient_scope" 时才认为权限不足;能提取合法 scope;遇到模糊或坏掉的 scope 时不会瞎猜;也不会把普通描述文字里的 scope=admin 当成真实权限要求。最后还测试了多个 WWW-Authenticate 头同时存在时,代码能从后面的 Bearer 提示里挑出正确信息。

函数细节5
extracts_scope_from_bearer_insufficient_scope_challenges10–52 ↗
fn extracts_scope_from_bearer_insufficient_scope_challenges()

作用:这个测试确认解析器能从各种合法的 Bearer 权限不足提示里,准确拿到服务器要求的 scope。它覆盖了大小写不同、参数顺序不同、没有引号、带转义字符、前面混有其他认证方式等常见情况。

数据流:进去的是一批模拟的 WWW-Authenticate 头字符串,以及每个字符串应该解析出的 scope → 测试逐个把字符串交给 parse_bearer_insufficient_scope → 出来应该是 BearerInsufficientScope,并且 required_scope 正好等于预期文字;如果不一致,assert_eq! 会让测试失败并显示是哪条 header 出错。

调用关系:它直接检验 parse_bearer_insufficient_scope 这个解析函数的正向能力,也就是“该认出来时必须认出来”。函数本身不把工作交给项目里的其他测试函数,只用 assert_eq! 做结果对比。

调用图:外部调用 1 个(assert_eq!)。

does_not_treat_other_bearer_errors_as_insufficient_scope55–60 ↗
fn does_not_treat_other_bearer_errors_as_insufficient_scope()

作用:这个测试确认解析器不会把其他 Bearer 错误误认为“权限范围不够”。比如 invalid_token 是令牌无效,不是 scope 不够,两者后续处理方式不同。

数据流:进去的是一个 Bearer error="invalid_token" 的头字符串,里面虽然也带了 scope → 测试把它交给 parse_bearer_insufficient_scope → 出来必须是 None,表示这里没有权限不足提示;assert_eq! 用来检查这个结论。

调用关系:它补上了反向保护:parse_bearer_insufficient_scope 不能只看到 scope 就下结论。这个测试只调用 assert_eq! 来验证结果,重点是防止权限不足逻辑被错误触发。

调用图:外部调用 1 个(assert_eq!)。

rejects_invalid_or_ambiguous_scope_parameters63–84 ↗
fn rejects_invalid_or_ambiguous_scope_parameters()

作用:这个测试确认当 scope 写法坏掉、含糊或有多个冲突值时,解析器不会自作聪明地猜一个答案。这样可以避免客户端拿着不可靠的权限要求去误导用户。

数据流:进去的是一组格式有问题的 Bearer 权限不足头,比如 scope 为空、引号转义异常、空格不规范、未加引号却含特殊字符、重复出现 scope → 测试逐条交给 parse_bearer_insufficient_scope → 出来仍然表示识别到了 insufficient_scope,但 required_scope 是 None,意思是“确实权限不足,但具体缺什么 scope 说不清”。

调用关系:它测试 parse_bearer_insufficient_scope 的保守策略:遇到危险或含糊输入时,不丢掉权限不足这个大信息,但也不编造具体 scope。它用 assert_eq! 检查每个坏例子的返回值。

调用图:外部调用 1 个(assert_eq!)。

ignores_scope_text_outside_a_scope_parameter87–102 ↗
fn ignores_scope_text_outside_a_scope_parameter()

作用:这个测试确认解析器不会把普通文字里出现的 scope= 当成真正的 scope 参数。它防止服务器的说明文字、其他字段名或坏掉的字符串造成误判。

数据流:进去的是几条容易迷惑人的头字符串,比如 error_description 里写了 scope=admin,或者字段名叫 resource_scope → 测试把它们交给 parse_bearer_insufficient_scope → 出来必须是 None,表示没有发现有效的 Bearer insufficient_scope 挑战。

调用关系:它约束 parse_bearer_insufficient_scope 只看真正的认证参数,不看描述文字里的相似片段。这个测试同样通过 assert_eq! 判断结果是否完全符合预期。

调用图:外部调用 1 个(assert_eq!)。

selects_bearer_challenge_from_a_later_www_authenticate_field_value105–124 ↗
fn selects_bearer_challenge_from_a_later_www_authenticate_field_value()

作用:这个测试确认当响应里有多个 WWW-Authenticate 头时,代码能跳过前面的 Basic 提示,并从后面的 Bearer 提示中找出权限不足信息。现实服务器可能一次返回多种认证方式,所以这点很重要。

数据流:进去的是一个 HttpHeader 列表,第一项是 Basic 认证提示,第二项是 WWW-Authenticate 的 Bearer insufficient_scope 提示 → 测试把整个列表交给 insufficient_scope_challenge → 出来应该是 InsufficientScopeChallenge,里面保存了第二个头的原始值,并提取出 files:read 这个 required_scope。

调用关系:它测试更外层的 insufficient_scope_challenge:这个函数不是只解析单个字符串,而是在一组 HTTP 头里寻找合适的认证挑战。测试用 vec! 构造头列表,用 assert_eq! 检查最终挑中的是不是正确那一条。

调用图:外部调用 2 个(assert_eq!, vec!)。

rmcp-client/src/streamable_http_retry_tests.rs源码 ↗
testtest

这个文件是一组测试,检查 RmcpClient 对“可重试错误”的判断是否靠谱。这里的“可重试”可以理解成:像打电话时信号突然断了,可以再拨一次;但如果号码本身错了,再拨也没用。测试覆盖两类情况:一类是客户端初始化时失败,文件确认发送初始化请求、发送 initialized 通知这两个阶段出错可以重试,但已经在接收初始化响应时出错就不算同一种可重试场景。另一类是 streamable HTTP(用 HTTP 长连接或流式响应传数据)里的错误,文件构造了网络请求失败、服务器内部错误、响应流断开、响应序号错乱、HTTP 502、HTTP 400 等情况,确认哪些会被当成临时故障。底部的辅助函数会拼出一个假的初始化错误,方便上面的测试反复使用。

函数细节3
retryable_initialize_error_includes_initialized_notification_context13–26 ↗
fn retryable_initialize_error_includes_initialized_notification_context()

作用:这个测试确认:初始化流程里某些具体步骤失败时,客户端会认为“可以重试”。它特别检查发送 initialized 通知失败也被算进可重试范围,避免这个边界情况被漏掉。

数据流:进去的是三段代表初始化阶段的文字:发送初始化请求、发送 initialized 通知、接收初始化响应。测试把每段文字包装成一个假的初始化错误,再交给 RmcpClient 的判断函数,最后拿结果和预期的 true、true、false 做比较;它不改系统状态,只验证判断结果。

调用关系:这个测试调用本文件里的 retryable_initialize_error 来造假错误,再检查 RmcpClient::is_retryable_client_initialize_error 的行为。最后用 assert_eq! 做断言,也就是把实际答案和标准答案放在一起比对。

调用图:外部调用 1 个(assert_eq!)。

retryable_streamable_http_error_includes_remote_body_stream_failure29–59 ↗
fn retryable_streamable_http_error_includes_remote_body_stream_failure()

作用:这个测试确认:流式 HTTP 通信里,不同错误会被正确分成“值得重试”和“不该重试”。它重点保护远端响应流断开这类临时故障,确保客户端不会过早放弃。

数据流:进去的是一组手工构造的错误对象,包括请求发不出去、服务器返回内部错误、响应流断开、响应序号不对、HTTP 502 和 HTTP 400。测试逐个交给 RmcpClient::is_retryable_streamable_http_error 判断,再得到一串布尔值,最后和预期结果比较;它只做验证,不真正发网络请求。

调用关系:这个测试直接构造 StreamableHttpError、StreamableHttpClientAdapterError 和 ExecServerError 这些错误外壳,模拟真实通信中可能遇到的失败。它把这些错误交给客户端的重试判断函数,再用 assert_eq! 确认规则没有被改坏。

调用图:外部调用 6 个(Client, UnexpectedServerResponse, assert_eq!, HttpRequest, Protocol, HttpRequest)。

retryable_initialize_error61–74 ↗
fn retryable_initialize_error(context: &'static str) -> rmcp::service::ClientInitializeError

作用:这是测试用的小帮手,用来快速造出一个“初始化时因为 HTTP 请求失败而报错”的假错误。这样上面的测试不用重复写一大段错误包装代码。

数据流:进去的是一段 context 文字,表示错误发生在初始化流程的哪个步骤。函数把这段文字放进 ClientInitializeError 里,同时塞入一个 streamable_http 传输错误,里面再包着 HTTP 请求失败的信息;出来的是一个完整的初始化错误对象,供测试函数拿去判断是否可重试。

调用关系:它服务于 retryable_initialize_error_includes_initialized_notification_context。这个辅助函数内部调用 DynamicTransportError::from_parts 来拼装传输层错误,再把更底层的 StreamableHttpError、StreamableHttpClientAdapterError 和 ExecServerError 一层层包进去,模拟真实错误链路。

调用图:外部调用 5 个(new, from_parts, Client, HttpRequest, HttpRequest)。

rmcp-client/tests/streamable_http_oauth_startup.rs源码 ↗
testtest execution

这里测试的是 OAuth(一种用访问令牌代表用户登录的标准办法)在客户端启动时的行为。文件用假的 HTTP 服务器模拟真实 MCP 服务器和 OAuth 发令牌服务器:先准备一个已经过期的访问令牌,再确认客户端启动前会用刷新令牌换到新访问令牌,然后才发送 initialize 初始化请求。它还测试了三种已保存凭证的判断:过期且不能刷新算没登录,没过期算已登录,过期但有刷新令牌也算可用。一个重要细节是:这些测试会启动“当前测试程序的子进程”,让子进程使用单独的 CODEX_HOME 目录。这样做像给每次测试发一个临时抽屉,避免改到主测试进程的真实环境或其他测试的登录文件。

函数细节5
refreshes_expired_persisted_token_before_initialize47–122 ↗
async fn refreshes_expired_persisted_token_before_initialize() -> anyhow::Result<()>

作用:这个测试确认:如果磁盘里保存的 OAuth 访问令牌已经过期,但还有可用的刷新令牌,客户端在初始化 MCP 连接前会先刷新令牌。这样不会把旧令牌直接发给服务器。

数据流:进去的是一个临时假的服务器和临时 CODEX_HOME 目录;测试先给假服务器安排三段回应:提供 OAuth 配置、接受刷新令牌请求、只接受带新访问令牌的 MCP 初始化请求。然后它启动当前测试程序的一个子进程,把服务器地址和临时家目录传进去。出来的结果是子进程必须成功退出,并且假服务器收到的请求次数和内容都符合预期。

调用关系:它是外层验收测试,自己不直接创建客户端,而是启动被忽略的辅助测试 oauth_startup_child。oauth_startup_child 负责写入过期凭证并真正创建客户端;这个外层测试负责搭好假服务器、隔离环境,并在最后检查所有预期请求确实发生。

调用图:外部调用 13 个(given, start, new, new, assert!, new, format!, json!, current_exe, body_string_contains (+3 more))。

reports_auth_status_for_persisted_credentials125–144 ↗
async fn reports_auth_status_for_persisted_credentials() -> anyhow::Result<()>

作用:这个测试确认:客户端查看已保存 OAuth 凭证时,能正确报告“是否已登录”。它主要保证登录状态显示不会误导用户。

数据流:进去的是一个新的临时 CODEX_HOME;测试启动当前测试程序的一个子进程,让子进程在这个临时目录里写入几种不同的令牌。出来的结果是子进程必须成功退出,表示所有登录状态判断都符合预期。

调用关系:它是一个外层包装测试,负责隔离运行环境。真正写入令牌并检查状态的是 persisted_credentials_auth_status_child;这样拆开是因为凭证存储会从进程环境里找 CODEX_HOME,放到子进程里更安全。

调用图:外部调用 4 个(new, assert!, new, current_exe)。

persisted_credentials_auth_status_child148–220 ↗
async fn persisted_credentials_auth_status_child() -> anyhow::Result<()>

作用:这个辅助测试实际检查三种已保存凭证会被判断成什么登录状态:不能刷新的过期令牌、没过期令牌、以及能刷新的过期令牌。

数据流:进去的是子进程里的临时 CODEX_HOME。它先保存一个已过期且没有刷新令牌的凭证,调用 auth_status 后应得到 NotLoggedIn;再保存一个还没过期的凭证,应得到 OAuth;最后保存一个已过期但带刷新令牌的凭证,也应得到 OAuth。过程中它会把测试令牌写入文件式凭证存储,并读取当前时间来制造“还没过期”的情况。

调用关系:它由 reports_auth_status_for_persisted_credentials 启动。它把重复的登录状态查询交给 auth_status,这样测试主体只需要关注“准备哪种凭证”和“应该得到什么状态”。

调用图:调用 2 个内部函数(default, auth_status);外部调用 8 个(new, new, new, now, default, assert_eq!, save_oauth_tokens, new)。

auth_status222–233 ↗
async fn auth_status(server_url: &str) -> anyhow::Result<McpAuthStatus>

作用:这是一个小帮手,用固定的测试服务器名和固定的存储设置去查询某个服务器地址的 OAuth 登录状态。它让测试里少写一大串重复参数。

数据流:进去的是一个 server_url,也就是要检查的 MCP 服务器地址;函数把它连同测试用服务器名、文件存储模式、默认钥匙串后端等参数交给 determine_streamable_http_auth_status。出来的是 McpAuthStatus,表示当前对这个地址来说是未登录、OAuth 已登录等状态。

调用关系:它被 persisted_credentials_auth_status_child 调用。它本身不判断令牌是否过期,而是把这件事交给真正的客户端认证状态判断函数 determine_streamable_http_auth_status。

调用图:调用 1 个内部函数(default);被 1 处调用(persisted_credentials_auth_status_child);外部调用 1 个(determine_streamable_http_auth_status)。

oauth_startup_child237–281 ↗
async fn oauth_startup_child() -> anyhow::Result<()>

作用:这个辅助测试模拟客户端启动:先在临时凭证库里放一个过期访问令牌和一个有效刷新令牌,再创建 HTTP MCP 客户端并初始化连接。它用来证明启动流程会自动刷新令牌。

数据流:进去的是环境变量里的假 MCP 服务器地址,以及子进程的临时 CODEX_HOME。它把过期令牌、刷新令牌、客户端 ID 和过期时间写入文件式凭证存储;接着创建 streamable HTTP 客户端,并且故意不传直接的 bearer token(承载令牌,HTTP 请求里表示身份的字符串),避免绕过已保存的 OAuth 凭证。最后它调用 initialize_client,成功则说明客户端已经用刷新后的令牌完成初始化。

调用关系:它由 refreshes_expired_persisted_token_before_initialize 启动。外层测试负责假服务器和断言请求,oauth_startup_child 负责触发真实客户端启动流程;创建客户端后,它把初始化动作交给测试支持函数 initialize_client。

调用图:调用 4 个内部函数(default, default_for_tests, new_streamable_http_client, initialize_client);外部调用 8 个(new, from_secs, new, new, default, save_oauth_tokens, new, var)。

rmcp-client/tests/streamable_http_recovery.rs源码 ↗
testtest execution

这份测试文件关心的是:客户端通过 HTTP 和服务器说话时,现实世界里会遇到各种坏情况,比如第一次初始化没回应、服务器临时返回 502、会话失效变成 404、权限不足返回 403。如果客户端处理不好,用户就会看到本来能恢复的小故障直接失败,或者在权限错误时反复重试浪费时间。文件里先做了一个特殊的假 HTTP 客户端 FailFirstInitializeHttpClient,它会故意让第一次 initialize 初始化请求失败,用来模拟“刚连上就断了一下”。后面的每个测试都会启动一个测试服务器,安排某个请求故意失败,再调用 echo 工具或列出工具,检查客户端是否按预期恢复。重点不是测服务器,而是测客户端的恢复策略:临时错误要重试,404 会话过期可以重建会话,401 和普通 500 不应触发会话恢复,403 里的权限提示要被正确识别。

函数细节19
FailFirstInitializeHttpClient::new42–48 ↗
fn new(inner: Arc<dyn HttpClient>, failures_remaining: usize) -> Self

作用:创建一个“会故意捣乱一次”的 HTTP 客户端包装器。它包住真正的 HTTP 客户端,并记录还要让几次初始化请求失败。

数据流:进去的是一个真正负责发 HTTP 请求的客户端,以及要模拟失败的次数 → 它把真实客户端、失败计数器、初始化尝试次数计数器放进可共享的容器里 → 出来的是一个新的 FailFirstInitializeHttpClient,后续测试可以用它制造初始化失败并统计尝试次数。

调用关系:它被 streamable_http_initialize_retries_remote_no_response_error 和 streamable_http_session_recovery_retries_initialize_failure 使用,用来搭建特殊测试场景。创建出来的对象之后会交给 create_client_with_http_client,让真实客户端流程在可控故障下运行。

调用图:被 2 处调用(streamable_http_initialize_retries_remote_no_response_error, streamable_http_session_recovery_retries_initialize_failure);外部调用 2 个(new, new)。

FailFirstInitializeHttpClient::initialize_attempts50–52 ↗
fn initialize_attempts(&self) -> usize

作用:读取 initialize 初始化请求一共被尝试了几次。测试用它来确认客户端确实发生了重试,而不是碰巧成功。

数据流:进去的是这个包装客户端自身保存的计数器 → 它用原子读取方式安全地取出数字,原子可以理解成多线程里不会读乱的计数器 → 出来的是初始化尝试次数,不改动任何状态。

调用关系:它通常在测试末尾被调用,用来配合 assert_eq! 检查结果。比如第一次初始化故意失败后,测试期待次数是 2;会话恢复时再次初始化失败一次后,测试期待总次数是 3。

FailFirstInitializeHttpClient::fail_next_initialize54–56 ↗
fn fail_next_initialize(&self)

作用:让下一次 initialize 初始化请求故意失败一次。它用于已经连上后,再模拟“会话恢复时初始化也闪断”的情况。

数据流:进去的是这个包装客户端自身 → 它把失败计数器重新设为 1 → 出来没有返回值,但之后遇到下一次初始化请求时会先报错一次。

调用关系:它在 streamable_http_session_recovery_retries_initialize_failure 里使用。测试先让客户端正常启动,再制造会话 404 过期,然后调用这个函数,让恢复过程里的重新初始化也经历一次失败,从而验证恢复流程内部也会重试。

FailFirstInitializeHttpClient::http_request60–65 ↗
fn http_request(
        &self,
        params: HttpRequestParams,
    ) -> BoxFuture<'_, Result<HttpRequestResponse, ExecServerError>>

作用:普通 HTTP 请求不做特殊处理,直接交给里面真正的 HTTP 客户端。这样测试只干扰流式请求里的初始化,不影响别的路径。

数据流:进去的是 HTTP 请求参数 → 它不检查、不修改,直接转交给 inner 真实客户端 → 出来的是真实客户端返回的 HTTP 响应或错误。

调用关系:这是实现 HttpClient 接口的一部分。当前测试主要通过 http_request_stream 制造 initialize 失败,这个函数负责保持其他普通请求行为原样,避免测试假客户端影响太多东西。

FailFirstInitializeHttpClient::http_request_stream67–89 ↗
fn http_request_stream(
        &self,
        params: HttpRequestParams,
    ) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>>

作用:处理带响应流的 HTTP 请求,并在请求是 initialize 初始化时按计划故意失败。它是这个测试文件模拟“第一次初始化没回应”的核心开关。

数据流:进去的是一次流式 HTTP 请求参数 → 它先调用 is_initialize_post 判断这是不是 initialize 的 POST 请求;如果是,就增加尝试次数,并查看是否还需要失败;需要失败时直接返回一个模拟的服务器错误,否则把请求交给真正的 HTTP 客户端 → 出来的是真实响应流,或者一次被故意制造的错误。

调用关系:它被客户端底层 HTTP 流程调用。它内部把判断工作交给 is_initialize_post;如果不需要拦截,就转交 inner.http_request_stream。测试函数通过 FailFirstInitializeHttpClient::new 和 fail_next_initialize 控制它什么时候失败。

调用图:调用 1 个内部函数(is_initialize_post);外部调用 1 个(clone)。

is_initialize_post92–104 ↗
fn is_initialize_post(params: &HttpRequestParams) -> bool

作用:判断一份 HTTP 请求是不是 JSON-RPC 的 initialize 初始化请求。JSON-RPC 可以理解成用 JSON 写明“我要调用哪个方法”的远程调用格式。

数据流:进去的是 HTTP 请求参数 → 它检查请求方法是不是 POST,再尝试把请求体当 JSON 解析,读取里面的 method 字段,看是否等于 initialize → 出来是 true 或 false;解析失败、没有 body、没有 method 都会当成 false。

调用关系:它只被 FailFirstInitializeHttpClient::http_request_stream 调用。这个小判断让假客户端只拦截初始化请求,不会误伤 echo、list_tools 等后续正常业务请求。

调用图:被 1 处调用(http_request_stream)。

streamable_http_initialize_retries_remote_no_response_error107–121 ↗
async fn streamable_http_initialize_retries_remote_no_response_error() -> anyhow::Result<()>

作用:验证初始化时如果第一次像“远端没回应”一样失败,客户端会自动再试一次并最终可用。

数据流:先启动测试服务器并创建一个第一次初始化会失败的假 HTTP 客户端 → 用它创建客户端,再调用 echo 工具 → 最后检查初始化尝试次数是 2,且 echo 返回了预期内容。

调用关系:这个测试调用 spawn_streamable_http_server 搭服务器,调用 FailFirstInitializeHttpClient::new 准备故障,调用 create_client_with_http_client 走真实建连流程,再用 call_echo_tool 验证连接真的能工作。

调用图:调用 5 个内部函数(default_for_tests, new, call_echo_tool, create_client_with_http_client, spawn_streamable_http_server);外部调用 2 个(new, assert_eq!)。

streamable_http_initialize_retries_transient_http_status124–135 ↗
async fn streamable_http_initialize_retries_transient_http_status() -> anyhow::Result<()>

作用:验证 initialize 初始化请求遇到一次临时 HTTP 状态码,比如 502,客户端会重试并继续工作。502 通常表示网关或上游服务暂时出问题。

数据流:先启动测试服务器 → 安排下一次 initialize POST 返回一次 502 → 创建客户端并调用 echo 工具 → 输出应是正常的 echo 结果,说明初始化失败被重试修复了。

调用关系:它通过 arm_initialize_post_failure 布置故障,通过 create_client 触发初始化,通过 call_echo_tool 检查客户端后续请求是否正常。

调用图:调用 4 个内部函数(arm_initialize_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 1 个(assert_eq!)。

streamable_http_initialize_retries_json_rpc_transient_status138–149 ↗
async fn streamable_http_initialize_retries_json_rpc_transient_status() -> anyhow::Result<()>

作用:验证 initialize 初始化阶段,如果失败是包在 JSON-RPC 错误里的临时状态,客户端也会把它当成可重试问题处理。

数据流:先启动测试服务器 → 安排 initialize 的 JSON-RPC 响应里报告一次 502 类错误 → 创建客户端并调用 echo → 最后拿到正常 echo 结果。

调用关系:它和普通 HTTP 502 测试相似,但故障由 arm_initialize_post_json_rpc_failure 安排在 JSON-RPC 层。这样能覆盖两种报错入口:HTTP 状态码本身,以及 JSON-RPC 返回体里的状态信息。

调用图:调用 4 个内部函数(arm_initialize_post_json_rpc_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 1 个(assert_eq!)。

streamable_http_retries_initialized_notification_status152–169 ↗
async fn streamable_http_retries_initialized_notification_status() -> anyhow::Result<()>

作用:验证初始化后的 initialized 通知如果遇到一次临时失败,客户端也会重试,而不是把整个连接过程判死刑。

数据流:先启动测试服务器 → 安排 initialized 通知的 JSON-RPC POST 第一次返回 502 类错误 → 创建客户端并调用 echo 工具 → 返回正常结果,说明通知失败被恢复了。

调用关系:它调用 arm_initialized_notification_post_json_rpc_failure 布置通知阶段的故障,再通过 create_client 和 call_echo_tool 触发并验证完整建连流程。

调用图:调用 4 个内部函数(arm_initialized_notification_post_json_rpc_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 1 个(assert_eq!)。

streamable_http_tools_list_retries_transient_http_status172–200 ↗
async fn streamable_http_tools_list_retries_transient_http_status() -> anyhow::Result<()>

作用:验证已经建立会话后,list_tools 列工具请求遇到一次临时 HTTP 502,会自动重试并得到同样结果。

数据流:先启动服务器并创建客户端 → 第一次正常调用 list_tools,保存正确答案 → 安排下一次会话 POST 返回一次 502 → 再调用 list_tools → 输出应和第一次保存的结果完全一样。

调用关系:它使用 arm_session_post_failure 在会话请求阶段制造 HTTP 状态码失败。这里不走 echo,而是直接测 list_tools,说明重试机制不只服务于工具调用,也覆盖工具列表请求。

调用图:调用 3 个内部函数(arm_session_post_failure, create_client, spawn_streamable_http_server);外部调用 2 个(from_secs, assert_eq!)。

streamable_http_tools_list_retries_json_rpc_transient_status203–225 ↗
async fn streamable_http_tools_list_retries_json_rpc_transient_status() -> anyhow::Result<()>

作用:验证 list_tools 请求遇到 JSON-RPC 层的临时 502 错误时,也会重试并返回正确工具列表。

数据流:先正常获取一次工具列表作为标准答案 → 安排下一次会话 POST 在 JSON-RPC 响应中失败一次 → 再获取工具列表 → 最终结果应等于标准答案。

调用关系:它调用 arm_session_post_json_rpc_failure 布置 JSON-RPC 层故障,并通过客户端的 list_tools 调用触发。它补足了上一条测试没有覆盖的错误包装形式。

调用图:调用 3 个内部函数(arm_session_post_json_rpc_failure, create_client, spawn_streamable_http_server);外部调用 2 个(from_secs, assert_eq!)。

streamable_http_404_session_expiry_recovers_and_retries_once228–247 ↗
async fn streamable_http_404_session_expiry_recovers_and_retries_once() -> anyhow::Result<()>

作用:验证会话过期返回 404 时,客户端会重新建立会话,并把原来的请求再试一次。

数据流:先创建客户端并用 echo 做一次热身,确认会话正常 → 安排下一次会话请求返回一次 404 → 再调用 echo → 客户端应恢复会话并返回正确 echo 结果。

调用关系:它通过 arm_session_post_failure 制造 404。这个测试重点验证会话恢复路径:call_echo_tool 触发错误,客户端内部应重新初始化,然后重发原请求。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 1 个(assert_eq!)。

streamable_http_session_recovery_retries_initialize_failure250–275 ↗
async fn streamable_http_session_recovery_retries_initialize_failure() -> anyhow::Result<()>

作用:验证会话过期后重新初始化时,如果第一次重新初始化也失败,客户端仍会再试并最终恢复。

数据流:先用特殊 HTTP 客户端创建连接,并成功 echo 一次 → 安排会话请求返回 404,让客户端进入恢复流程 → 再设置下一次 initialize 故意失败 → 调用 echo 后应成功,并且初始化总尝试次数为 3。

调用关系:它把 arm_session_post_failure 和 FailFirstInitializeHttpClient::fail_next_initialize 组合起来,测试一个更曲折的恢复故事:先会话过期,再恢复初始化失败,最后仍应成功。

调用图:调用 6 个内部函数(default_for_tests, new, arm_session_post_failure, call_echo_tool, create_client_with_http_client, spawn_streamable_http_server);外部调用 2 个(new, assert_eq!)。

streamable_http_401_does_not_trigger_recovery278–302 ↗
async fn streamable_http_401_does_not_trigger_recovery() -> anyhow::Result<()>

作用:验证 401 未授权错误不会触发会话恢复。401 通常表示身份凭证不对,重建会话也解决不了。

数据流:先建立正常会话并 echo 热身 → 安排接下来两次会话请求都返回 401 → 连续两次调用 echo 都应失败,并且错误信息包含 401 → 没有返回正常结果。

调用关系:它调用 arm_session_post_failure 制造 401,并用两次 call_echo_tool 观察行为。两次都失败说明客户端没有把 401 当成会话过期去自动重连。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 2 个(assert!, assert_eq!)。

streamable_http_403_scope_challenge_returns_insufficient_scope305–328 ↗
async fn streamable_http_403_scope_challenge_returns_insufficient_scope() -> anyhow::Result<()>

作用:验证 403 禁止访问里如果带有 Bearer 权限提示,客户端会把它解释成“权限范围不足”,而不是普通失败。

数据流:先正常建立会话并 echo 热身 → 安排下一次会话请求返回 403,同时在 WWW-Authenticate 头里放入 Bearer error="insufficient_scope" 和需要的 scope → 调用 echo 得到错误 → 错误文字应包含 Insufficient scope。

调用关系:它通过 arm_session_post_failure 设置 403 和认证头。测试关注的是错误翻译:客户端要读懂服务器给的权限挑战信息,并把它变成更有用的权限不足错误。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 2 个(assert!, assert_eq!)。

streamable_http_403_finds_bearer_challenge_in_later_header_value331–357 ↗
async fn streamable_http_403_finds_bearer_challenge_in_later_header_value() -> anyhow::Result<()>

作用:验证当 403 响应里有多个认证头时,客户端能在后面的头里找到 Bearer 权限提示。Bearer 可以理解成“拿令牌证明身份”的认证方式。

数据流:先正常建连并热身 → 安排 403 响应带两个 WWW-Authenticate 值,第一个是 Basic,第二个才是 Bearer insufficient_scope → 调用 echo → 输出错误应仍然识别为 Insufficient scope。

调用关系:它和上一条 403 测试配合,专门覆盖“Bearer 不在第一个头里”的情况,防止客户端只看第一项而漏掉真正有用的权限说明。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 2 个(assert!, assert_eq!)。

streamable_http_404_recovery_only_retries_once360–386 ↗
async fn streamable_http_404_recovery_only_retries_once() -> anyhow::Result<()>

作用:验证 404 会话过期恢复只会为同一个请求重试一次,避免遇到连续 404 时陷入无限重试。

数据流:先正常 echo 热身 → 安排接下来两次会话请求返回 404 → 调用 echo 时,客户端会尝试恢复并重试一次,但第二个 404 仍会让这次调用失败 → 再调用一次 echo 时应恢复正常并返回预期结果。

调用关系:它用 arm_session_post_failure 设置两个 404。这个测试保护的是重试边界:客户端可以帮忙恢复,但不能像卡住的门铃一样一直按下去。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 2 个(assert!, assert_eq!)。

streamable_http_non_session_failure_does_not_trigger_recovery389–413 ↗
async fn streamable_http_non_session_failure_does_not_trigger_recovery() -> anyhow::Result<()>

作用:验证普通服务器错误 500 不会触发会话恢复。500 通常是服务器内部坏了,重建会话不一定有用。

数据流:先建立正常会话并 echo 热身 → 安排后面两次会话请求返回 500 → 连续两次调用 echo 都应失败且错误信息包含 500 → 没有自动恢复成成功请求。

调用关系:它通过 arm_session_post_failure 制造 500,并用两次 call_echo_tool 确认客户端没有把所有错误都当成会话过期。它和 404 测试一起划清了“该恢复”和“不该恢复”的边界。

调用图:调用 4 个内部函数(arm_session_post_failure, call_echo_tool, create_client, spawn_streamable_http_server);外部调用 2 个(assert!, assert_eq!)。

network-proxy/src/mitm_tests.rs源码 ↗
testtest run

这个文件不是真正跑在线上的代理逻辑,而是给那套逻辑做“安全演练”。这里的 MITM 指“中间人检查”:代理站在请求和目标网站中间,看清 HTTP 请求内容,再决定放不放。测试会搭出假的代理配置、假的目标网站、假的请求,然后调用真正的检查函数,看结果是不是符合规则。它覆盖几类关键情况:受限模式下禁止写操作;请求里的 Host 和连接目标不一致时拒绝;连上之后发现目标是内网地址时还要再拦一次;GitHub 这类配置了 hook 的站点,只有匹配规则的请求才允许特殊处理;请求头里的 authorization 可以被替换成安全来源的密钥。可以把它理解成机场安检的模拟考试:每个测试都拿一种乘客和行李来试,确保安检员既不会漏掉危险物,也不会乱扣正常人。

函数细节9
github_write_hook19–37 ↗
fn github_write_hook() -> crate::mitm_hook::MitmHookConfig

作用:这个辅助函数造出一份 GitHub 写操作的 MITM hook 配置。hook 可以理解成一条特殊规则:只有发往指定主机、方法和路径都匹配的请求,才会被允许做指定的请求头处理。

数据流:进去没有外部输入 → 它固定生成一份配置:目标主机是 api.github.com,只匹配 POST 和 PUT,并且路径要以 /repos/openai/ 开头;同时要求先删掉 authorization 请求头,再注入一个来自 CODEX_GITHUB_TOKEN 的 Bearer token → 出来的是一份可供测试复用的 MitmHookConfig。

调用关系:多个测试需要同一套 GitHub hook 规则时会调用它,避免每个测试重复写配置。后面的测试会在它生成的基础上稍微改动,比如把密钥来源从环境变量换成临时文件,或者清空注入请求头,用来验证不同安全分支。

调用图:被 3 处调用(mitm_policy_allows_matching_hooked_write_in_full_mode, mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_full_mode, mitm_policy_blocks_matching_hooked_write_in_limited_mode);外部调用 2 个(default, vec!)。

policy_ctx39–51 ↗
fn policy_ctx(
    app_state: Arc<NetworkProxyState>,
    mode: NetworkMode,
    target_host: &str,
    target_port: u16,
) -> MitmPolicyContext

作用:这个辅助函数把一次 MITM 检查需要的上下文打包好。上下文就是检查请求时必须知道的背景:代理状态、网络模式、目标主机和端口。

数据流:进去的是代理共享状态、网络模式、目标主机名和端口 → 它把主机名转成自己的字符串,并和其他信息一起塞进 MitmPolicyContext → 出来的是一份可直接传给检查函数的上下文对象。

调用关系:几乎所有 MITM 策略测试都会先用它准备环境,然后把生成的上下文交给 mitm_blocking_response。它本身不判断安全规则,只负责把测试需要的材料摆到正确位置。

调用图:被 6 处调用(mitm_policy_allows_matching_hooked_write_in_full_mode, mitm_policy_blocks_disallowed_method_and_records_telemetry, mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_full_mode, mitm_policy_blocks_matching_hooked_write_in_limited_mode, mitm_policy_rechecks_local_private_target_after_connect, mitm_policy_rejects_host_mismatch)。

mitm_policy_blocks_disallowed_method_and_records_telemetry54–90 ↗
async fn mitm_policy_blocks_disallowed_method_and_records_telemetry()

作用:这个测试确认:在受限网络模式下,对允许域名发 POST 这种写操作也会被拦住,并且系统会记下“为什么拦”。这很重要,因为只允许访问某个域名,并不等于允许对它做修改。

数据流:进去的是测试临时创建的代理配置:只允许 example.com,并处于 Limited 受限模式 → 它构造一个发往 example.com 的 POST 请求,交给 MITM 拦截判断 → 出来应是一条 403 Forbidden 响应,同时代理状态里多了一条拦截记录,记录方法是 POST、主机是 example.com、端口是 443、原因是方法不允许。

调用关系:这个测试先用 network_proxy_state_for_policy 建立测试用代理状态,再用 policy_ctx 包好检查上下文,最后调用真正的 mitm_blocking_response。它验证的是方法限制这条主线规则,以及拦截遥测记录是否同步写入。

调用图:调用 3 个内部函数(default, policy_ctx, network_proxy_state_for_policy);外部调用 5 个(new, assert_eq!, builder, empty, vec!)。

mitm_policy_rejects_host_mismatch93–119 ↗
async fn mitm_policy_rejects_host_mismatch()

作用:这个测试确认:连接目标说是 example.com,但 HTTP 请求头里写成 evil.example 时,代理会拒绝。这样可以防止请求“挂羊头卖狗肉”。

数据流:进去的是允许 example.com 的代理配置,以及 Full 完整网络模式 → 它构造一个连接目标为 example.com、但 Host 请求头为 evil.example 的 GET 请求 → 出来应是一条 400 Bad Request 响应,并且不会写入普通的“被策略拦截”记录,因为这是请求自身不一致的问题。

调用关系:它同样通过 policy_ctx 准备上下文,再调用 mitm_blocking_response。这个测试关注的是 MITM 检查里的主机一致性校验,和其他测试的域名允许、方法限制不同。

调用图:调用 3 个内部函数(default, policy_ctx, network_proxy_state_for_policy);外部调用 5 个(new, assert_eq!, builder, empty, vec!)。

mitm_policy_rechecks_local_private_target_after_connect122–154 ↗
async fn mitm_policy_rechecks_local_private_target_after_connect()

作用:这个测试确认:即使已经进入 MITM 内层请求检查,代理也会重新检查目标是不是内网或本机地址。这样可以防止请求绕过外层限制去访问不该碰的内网资源。

数据流:进去的是一份不允许本地绑定的代理配置,目标地址是 10.0.0.1 这种私有内网地址 → 它构造一个发往该地址的 GET 请求,并交给 MITM 检查 → 出来应是一条 403 Forbidden 响应,同时拦截记录里写明原因是本地或内网目标不允许,主机为 10.0.0.1,端口为 443。

调用关系:这个测试用 network_proxy_state_for_policy 创建带有 allow_local_binding=false 的状态,用 policy_ctx 指定实际连接目标,再让 mitm_blocking_response 做二次判断。它验证的是“连接后也不能放松警惕”这条安全兜底。

调用图:调用 3 个内部函数(default, policy_ctx, network_proxy_state_for_policy);外部调用 5 个(new, assert_eq!, builder, empty, vec!)。

mitm_policy_allows_matching_hooked_write_in_full_mode157–192 ↗
async fn mitm_policy_allows_matching_hooked_write_in_full_mode()

作用:这个测试确认:在 Full 完整网络模式下,如果请求完全匹配 GitHub hook,并且 hook 能拿到要注入的密钥,那么这个写操作可以通过。也就是说,特殊授权的写请求不会被普通写操作限制误伤。

数据流:进去的是一个临时密钥文件、一份 GitHub hook 配置、允许 api.github.com 的 Full 模式代理配置 → 它把 hook 的密钥来源改成临时文件,构造一个 POST 到 /repos/openai/codex/issues 的请求 → 出来应是没有拦截响应,也就是允许继续转发;代理状态里也不应有拦截记录。

调用关系:它先调用 github_write_hook 得到标准 GitHub 规则,再用 policy_ctx 和 network_proxy_state_for_policy 搭好环境,最后调用 mitm_blocking_response。它验证 hook 机制能在完整模式下为受控写操作开一个安全通道。

调用图:调用 4 个内部函数(default, github_write_hook, policy_ctx, network_proxy_state_for_policy);外部调用 8 个(new, new, assert!, assert_eq!, builder, empty, write, vec!)。

mitm_policy_blocks_matching_hooked_write_in_limited_mode195–236 ↗
async fn mitm_policy_blocks_matching_hooked_write_in_limited_mode()

作用:这个测试确认:即使请求匹配 GitHub hook,只要网络模式是 Limited 受限模式,POST 写操作仍然会被拦住。受限模式的总规则比 hook 更严格。

数据流:进去的是一份 GitHub hook 配置,但注入请求头动作被清空,代理处于 Limited 模式并允许 api.github.com → 它构造一个匹配路径的 POST 请求 → 出来应是 403 Forbidden,响应头说明是按方法策略拦截;拦截记录写明原因是方法不允许、方法是 POST、主机是 api.github.com、端口是 443。

调用关系:它复用 github_write_hook 生成基础规则,再通过 policy_ctx 进入 MITM 检查。它和允许写操作的测试形成对照,说明同样的 hook 在不同网络模式下结果不同。

调用图:调用 4 个内部函数(default, github_write_hook, policy_ctx, network_proxy_state_for_policy);外部调用 5 个(new, assert_eq!, builder, empty, vec!)。

mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_full_mode239–285 ↗
async fn mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_full_mode()

作用:这个测试确认:对配置了 hook 的主机来说,如果请求没有命中 hook 规则,即使在 Full 模式下也会被拦。这样可以避免用户自带的敏感请求头绕过受控的注入流程。

数据流:进去的是 GitHub hook、临时密钥文件、允许 api.github.com 的 Full 模式代理配置 → 它构造一个发往 api.github.com 的 GET 请求,路径在 hook 主机下但方法不匹配,还带着用户自己提供的 authorization → 出来应是 403 Forbidden,响应头说明被 MITM hook 拒绝;拦截记录写明原因是 hook 未允许,方法是 GET,主机是 api.github.com,端口是 443。

调用关系:它先用 github_write_hook 建立“这个主机需要受控处理”的规则,再调用 mitm_blocking_response 验证未命中的请求会被拒绝。它检查的是 hook 主机的默认安全姿态:没有明确匹配,就不要放行。

调用图:调用 4 个内部函数(default, github_write_hook, policy_ctx, network_proxy_state_for_policy);外部调用 7 个(new, new, assert_eq!, builder, empty, write, vec!)。

apply_mitm_hook_actions_replaces_authorization_header288–320 ↗
fn apply_mitm_hook_actions_replaces_authorization_header()

作用:这个测试确认:执行 MITM hook 动作时,旧的 authorization 请求头会被删除,并替换成安全来源提供的新值,同时其他无关请求头不受影响。

数据流:进去的是一组请求头,其中有用户提供的 authorization 和 x-request-id;还有一组 hook 动作,要求删除 authorization 并注入新的 Bearer secret-token → 它调用 apply_mitm_hook_actions 修改这组请求头 → 出来时 authorization 变成新的安全值,x-request-id 仍然保持原样。

调用关系:这个测试直接检查请求头改写函数 apply_mitm_hook_actions,不经过完整的 MITM 策略判断。它补上了更细的一层验证:前面的策略测试关心放不放行,这里关心放行前请求头到底有没有按规则改对。

调用图:外部调用 5 个(new, from_static, from_static, assert_eq!, vec!)。

uds/src/lib_tests.rs源码 ↗
testtest run

这是一份测试文件,不是正式业务流程的一部分。它验证的是本库里和 Unix 域套接字有关的基础能力。Unix 域套接字可以理解成同一台机器上两个程序之间的“本地电话线”,常用来做安全、快速的进程间通信。这里重点测三件事:第一,放 socket 文件的目录会不会被创建,并且权限是否收紧到只有拥有者能访问;第二,代码能不能分清普通文件和 socket 文件,避免误删或误判;第三,监听端和客户端之间是否真的能通过这个 socket 来回传数据。测试里大量使用临时目录,像一次性沙盒,用完就丢,避免污染真实机器环境。有些测试在没有权限绑定 socket 时会跳过,这是为了让测试在受限环境里也能友好运行。

函数细节5
prepare_private_socket_directory_creates_directory10–19 ↗
async fn prepare_private_socket_directory_creates_directory()

作用:这个测试确认:当指定的 socket 目录还不存在时,准备目录的函数会把它建出来。没有这个保证,后面创建 socket 文件时就可能因为目录不存在而失败。

数据流:它先创建一个临时目录,再在里面拼出一个还不存在的子目录路径。接着把这个路径交给 prepare_private_socket_directory。函数运行后,测试检查这个路径现在是不是一个真正的目录;如果不是,测试就失败。

调用关系:这是对目录准备流程的最基础检查。它自己只搭建临时环境并做断言,核心工作交给被测试的 prepare_private_socket_directory;测试框架在运行测试时直接调用它。

调用图:外部调用 2 个(assert!, new)。

prepare_private_socket_directory_sets_existing_permissions_to_owner_only23–43 ↗
async fn prepare_private_socket_directory_sets_existing_permissions_to_owner_only()

作用:这个测试确认:如果 socket 目录已经存在,不管原来权限是太宽还是不合适,准备函数都会把权限改成只有拥有者能进。这样可以防止别的用户偷看或干扰本地通信入口。

数据流:它先创建临时目录,然后分别造出两个已有目录,并故意设置成不同权限。接着调用 prepare_private_socket_directory。调用之后,它读取目录的实际权限,只看最低的三组权限位,确认结果正好是 0700,也就是只有目录拥有者可读、可写、可进入。

调用关系:这是权限安全方面的测试,只有 Unix 系统上才运行,因为权限位是 Unix 的概念。它把各种初始权限场景喂给 prepare_private_socket_directory,再用文件系统元数据检查结果是否被修正。

调用图:外部调用 7 个(assert_eq!, from_mode, format!, create_dir, metadata, set_permissions, new)。

regular_file_path_is_not_stale_socket_path47–57 ↗
async fn regular_file_path_is_not_stale_socket_path()

作用:这个测试确认:一个普通文件不会被误认为是“残留的 socket 路径”。这很重要,因为误判可能导致程序把用户的普通文件当垃圾删掉。

数据流:它在临时目录里写入一个普通文件,内容是简单的字节文本。然后把这个普通文件路径交给 is_stale_socket_path 检查。测试期望返回 false,意思是:这不是一个需要清理的旧 socket 路径。

调用关系:这是对 is_stale_socket_path 的防误伤测试。它不创建 socket,只创建普通文件,用来证明检测逻辑不会把所有存在的路径都当成 socket 处理。

调用图:外部调用 3 个(assert!, write, new)。

bound_listener_path_is_stale_socket_path60–77 ↗
async fn bound_listener_path_is_stale_socket_path()

作用:这个测试确认:当某个路径上真的绑定了 Unix socket 监听器时,检测函数能识别出它是 socket 路径。这里的“stale”更像是在测试代码里对 socket 路径状态的判断,而不是普通文件。

数据流:它先创建临时目录和 socket 路径,然后尝试在这个路径上绑定一个 UnixListener,也就是本地监听端。如果环境不允许绑定,就打印提示并跳过。绑定成功后,它把同一路径交给 is_stale_socket_path,期望得到 true。

调用关系:这个测试把真实的 UnixListener::bind 纳入流程,先制造一个真实 socket 文件,再交给 is_stale_socket_path 验证。它连接了底层 socket 创建能力和本库的路径识别逻辑。

调用图:调用 1 个内部函数(bind);外部调用 4 个(assert!, eprintln!, panic!, new)。

stream_round_trips_data_between_listener_and_client80–121 ↗
async fn stream_round_trips_data_between_listener_and_client()

作用:这个测试确认:本库暴露的 UnixListener 和 UnixStream 能让服务端和客户端真正互相收发数据。它验证的不是只连得上,而是消息能完整走一个来回。

数据流:它先在临时路径上绑定监听器。然后启动一个异步服务端任务,等待客户端连接,读取 7 个字节的 request,再写回 response。另一边,客户端用 UnixStream 连接同一个路径,发送 request,读取 8 个字节的 response。最后测试确认两边收到的内容都完全正确,并等待服务端任务结束。

调用关系:这是最接近真实使用方式的端到端测试。测试框架调用它后,它用 UnixListener::bind 建服务端入口,用 tokio::spawn 启动并发服务端任务,再用 UnixStream::connect 建客户端连接;读写动作由异步 I/O 完成,用来证明整条本地通信链路可用。

调用图:调用 2 个内部函数(bind, connect);外部调用 5 个(assert_eq!, eprintln!, panic!, new, spawn)。

utils/rustls-provider/tests/provider.rs源码 ↗
testtest run

这个测试文件像一次“开机自检”。项目里有一个函数会为 rustls 安装默认的加密提供者。rustls 是 Rust 里常用的 TLS 安全通信库,TLS 可以理解成浏览器网址里 HTTPS 背后的加密通道。这个测试先调用安装函数,然后向 rustls 询问:现在有没有默认的加密提供者?如果没有,就直接报错。接着它检查这个提供者支持的签名方案里,是否包含 ECDSA_NISTP521_SHA512。这个名字很长,本质上是一种验证证书签名的方法。为什么重要?因为有些服务器或证书会用到它;如果支持没装好,程序可能在建立 HTTPS 连接时失败。这个文件不处理真实网络请求,只验证“加密工具箱里有没有这件工具”。

函数细节1
ensure_provider_installs_ecdsa_p521_sha512_support4–16 ↗
fn ensure_provider_installs_ecdsa_p521_sha512_support()

作用:这个测试确认调用 ensure_rustls_crypto_provider 之后,rustls 的默认加密提供者已经安装,并且支持 ECDSA_NISTP521_SHA512 这种证书签名验证方式。有人改加密配置时,这个测试能及时发现支持被弄丢了。

数据流:开始时它没有接收外部输入。它先调用 ensure_rustls_crypto_provider,让项目安装 rustls 要用的加密提供者;然后用 rustls::crypto::CryptoProvider::get_default 读取当前默认提供者。如果读不到,就 panic,也就是让测试失败并说明提供者没装上。读到之后,它查看这个提供者支持的签名方案列表,再用 assert! 检查里面是否包含 ECDSA_NISTP521_SHA512。结果是测试通过,或者在缺少提供者、缺少算法支持时失败。

调用关系:这是测试框架运行测试时直接调用的函数。它先把核心工作交给外部的 ensure_rustls_crypto_provider,再向 rustls 查询安装结果,最后用 assert! 做判断;如果连默认提供者都没有,它会用 panic! 立刻终止测试并报错。

调用图:外部调用 4 个(assert!, ensure_rustls_crypto_provider, panic!, get_default)。

utils/rustls-provider/tests/preinstalled.rs源码 ↗
testtest

rustls 是一个做安全网络连接的库,类似给 HTTPS 这类通信装上“加密和验身份”的工具箱。这个测试关心的是其中的 CryptoProvider,也就是 rustls 用来决定采用哪些加密算法、签名验证算法的“默认工具箱”。测试先故意安装一个被改过的默认提供者:它把签名验证算法清空,相当于贴上一个明显的标记。然后调用项目里的 ensure_rustls_crypto_provider,这个函数本来是为了确保默认加密提供者存在。最后测试再把当前默认提供者取出来,检查那个“标记”还在不在。如果还在,说明项目没有覆盖预先安装的提供者;如果不在,就说明它擅自换了工具箱,可能破坏其他代码的设置。这个文件的重要性在于,它守住了一个边界:只在缺少默认配置时补上,不碰已经存在的配置。

函数细节1
ensure_provider_preserves_preinstalled_provider10–26 ↗
fn ensure_provider_preserves_preinstalled_provider()

作用:这个测试函数确认:如果 rustls 已经有默认加密提供者,ensure_rustls_crypto_provider 不应该把它替换掉。它用一个被刻意改过的提供者当作“记号”,检查调用后这个记号是否还存在。

数据流:进去时没有外部输入。函数先拿到 rustls 自带的 aws-lc-rs 默认提供者,并把它的签名验证算法改成空列表;接着把这个改过的提供者安装成全局默认值;然后调用 ensure_rustls_crypto_provider;最后重新读取全局默认提供者,检查它仍然缺少某个签名方案。结果是:如果检查通过,说明原来的提供者被保留;如果没有默认提供者或算法状态不对,测试就失败。

调用关系:这个函数是测试运行器在测试阶段自动调用的。它先借用 rustls 的 default_provider 和 install_default 准备现场,再调用项目真正要验证的 ensure_rustls_crypto_provider,最后通过 get_default 读取现场结果,并用断言判断这个工具函数有没有越界覆盖已有配置。

调用图:外部调用 5 个(assert!, ensure_rustls_crypto_provider, panic!, get_default, default_provider)。