App-server 单元测试和共享集成夹具
这一阶段不是用户平时会看到的主流程,而是幕后“质检车间”。一部分测试直接检查 app-server 的关键小零件:配置迁移会不会搬丢东西,config.toml 读写会不会乱改,启动参数能不能生效,追踪链路能不能接上,远程控制、线程处理、会话摘要在出错和边角情况里是否靠谱。另一部分是集成测试的公用道具:假登录、假配置、假模型服务器、假分析服务器、假回复、假历史会话和假客户端,让测试像真跑服务一样检查,又不用碰真实外部服务。最后几个入口文件把这些测试登记成套件,方便一次跑全。
单元测试入口点
这些聚焦于 crate 层级的测试在更广泛的集成框架介入前,验证 CLI 解析、配置服务、跟踪和核心请求处理器行为。
app-server/src/config/external_agent_config_tests.rs源码 ↗
这个文件不提供正式功能,而是用很多临时目录和假文件,模拟用户电脑里已有的外部助手配置,再调用 ExternalAgentConfigService 去“发现可迁移内容”和“执行迁移”。测试会检查两类事:第一,系统能不能正确列出该迁的东西,比如 settings.json、skills、AGENTS.md、插件和 MCP 服务器;第二,真正迁移后,Codex 生成的 config.toml、hooks.json、技能文件和 agent 配置是不是符合预期。它还特别覆盖了很多容易出错的边角情况:目标文件已经有内容就不覆盖、空目标可以覆盖、本地设置优先于项目设置、坏 JSON 不应拖垮别的迁移、已有插件或服务器不重复迁移、Git 插件需要异步等待等。没有这些测试,迁移功能改动时很容易悄悄破坏用户已有配置。
fixture_paths14–19 ↗
fn fixture_paths() -> (TempDir, PathBuf, PathBuf)
作用:准备一套干净的临时路径,给测试当“假用户家目录”和“假 Codex 目录”。这样每个测试都像在一台新电脑上跑,不会互相污染。
数据流:进去没有参数 → 创建一个临时目录,并在里面拼出外部助手目录和 .codex 目录路径 → 出来三个东西:临时目录本身、外部助手路径、Codex 路径。
调用关系:很多检测和导入测试一开始都会先叫它搭测试场地;它只负责造路径,然后这些路径会交给 service_for_paths 创建测试服务。
调用图:被 23 处调用(detect_home_infers_external_official_marketplace_when_missing_from_settings, detect_home_lists_config_skills_and_agents_md, detect_home_lists_enabled_plugins_from_settings, detect_home_lists_recent_sessions, detect_home_plugins_uses_local_settings_over_project_settings, detect_home_skips_config_when_target_already_has_supported_fields, detect_home_skips_plugins_with_invalid_marketplace_source, detect_home_skips_plugins_without_marketplace_source, detect_home_skips_skills_when_all_skill_directories_exist, detect_home_supports_relative_external_agent_plugin_marketplace_path (+13 more));外部调用 1 个(new)。
service_for_paths21–26 ↗
fn service_for_paths(
external_agent_home: PathBuf,
codex_home: PathBuf,
) -> ExternalAgentConfigService
作用:用指定的两个目录创建一个专门给测试用的迁移服务。它让测试不用碰真实用户目录。
数据流:进去是外部助手目录路径和 Codex 目录路径 → 调用 ExternalAgentConfigService::new_for_test 把路径塞进服务里 → 出来一个可以执行 detect 和 import 的服务对象。
调用关系:几乎所有测试在布置好假文件后都会调用它;它把测试准备的路径交给真正的迁移服务,后续检测或导入都由这个服务完成。
调用图:调用 1 个内部函数(new_for_test);被 46 处调用(detect_home_infers_external_official_marketplace_when_missing_from_settings, detect_home_lists_config_skills_and_agents_md, detect_home_lists_enabled_plugins_from_settings, detect_home_lists_recent_sessions, detect_home_plugins_uses_local_settings_over_project_settings, detect_home_skips_config_when_target_already_has_supported_fields, detect_home_skips_plugins_with_invalid_marketplace_source, detect_home_skips_plugins_without_marketplace_source, detect_home_skips_skills_when_all_skill_directories_exist, detect_home_supports_relative_external_agent_plugin_marketplace_path (+15 more))。
github_plugin_details28–36 ↗
fn github_plugin_details() -> MigrationDetails
作用:快速造一份“要迁移 GitHub 插件”的示例详情。这样相关测试不用每次手写一大段结构。
数据流:进去没有参数 → 填好 marketplace 名字 acme-tools 和插件 formatter,其余字段用默认值 → 出来一份 MigrationDetails。
调用关系:import_plugins_defers_marketplace_source_validation_to_add_marketplace 用它生成测试输入;它自己不做迁移,只是帮测试少写重复数据。
调用图:被 1 处调用(import_plugins_defers_marketplace_source_validation_to_add_marketplace);外部调用 2 个(default, vec!)。
assert_single_plugin_raw_error38–54 ↗
fn assert_single_plugin_raw_error(
raw_errors: &[ExternalAgentConfigImportRawError],
failure_stage: &str,
source: &str,
)
作用:检查插件迁移失败时,系统是不是记录了一条格式正确的原始错误。它是测试里的“小验货员”。
数据流:进去是一组原始错误、期望失败阶段、期望来源插件名 → 检查错误数量、类型、阶段、来源和消息是否符合预期 → 没有返回值,失败就让测试报错。
调用关系:几个插件失败场景会调用它;它不参与迁移,只负责把重复的错误断言集中起来。
调用图:被 3 处调用(import_plugins_defers_marketplace_source_validation_to_add_marketplace, import_plugins_infers_external_official_marketplace_when_missing_from_settings, import_plugins_requires_source_marketplace_details);外部调用 2 个(assert!, assert_eq!)。
import_success56–68 ↗
fn import_success(
item_type: ExternalAgentConfigMigrationItemType,
cwd: Option<PathBuf>,
source: impl Into<String>,
target: impl Into<String>,
) -> ExternalAgentConfigImportSuccess
作用:快速构造一条“迁移成功记录”,方便测试拿它和真实结果比较。
数据流:进去是迁移项目类型、所在目录、来源文字和目标文字 → 把来源和目标转成字符串并塞进成功结果结构 → 出来 ExternalAgentConfigImportSuccess。
调用关系:导入类测试在写期望结果时会用它;它只是造期望值,不调用迁移服务。
调用图:外部调用 1 个(into)。
detect_home_lists_config_skills_and_agents_md71–131 ↗
async fn detect_home_lists_config_skills_and_agents_md()
作用:确认扫描用户级外部助手目录时,能发现配置文件、技能目录和说明文件都需要迁移。
数据流:进去是测试框架启动的异步测试 → 先造临时目录和假 settings.json、skills、配置说明文件 → 调 detect 得到迁移清单,并和期望清单比较。
调用关系:它通过 fixture_paths 和 service_for_paths 搭环境,再把主要工作交给迁移服务的 detect;最后用断言验证发现结果。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 5 个(assert_eq!, format!, create_dir_all, write, vec!)。
detect_home_lists_recent_sessions134–184 ↗
async fn detect_home_lists_recent_sessions()
作用:确认系统能在外部助手的历史项目目录里找到近期会话,并把它列为可迁移内容。
数据流:进去没有业务参数 → 写入一个带 cwd、时间戳和用户消息的假 session.jsonl → detect 返回 Sessions 项,里面带会话路径、项目目录和标题。
调用关系:它使用 fixture_paths 建假目录,用 service_for_paths 调迁移服务;重点验证会话发现逻辑,而不是实际迁移会话。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 5 个(assert_eq!, now, create_dir_all, write, json!)。
detect_repo_lists_agents_md_for_each_cwd187–234 ↗
async fn detect_repo_lists_agents_md_for_each_cwd()
作用:确认传入多个工作目录时,系统会按每个目录查找仓库里的外部助手说明文件。
数据流:进去是异步测试 → 创建一个 Git 仓库和嵌套目录,并写入外部助手说明文件 → detect 针对传入的 cwd 列出 AGENTS.md 迁移项。
调用关系:它直接创建 TempDir,再用 service_for_paths 调 detect;测试的是“从嵌套目录找到仓库根目录”这条流程。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
detect_repo_still_reports_non_plugin_items_when_home_config_is_invalid237–325 ↗
async fn detect_repo_still_reports_non_plugin_items_when_home_config_is_invalid()
作用:确认全局 Codex 配置坏掉时,仓库里的普通迁移项仍然能被发现。也就是一个地方坏了,不能让所有搬家清单都消失。
数据流:进去没有参数 → 写入无效 config.toml,同时准备仓库 settings、skills 和说明文件 → detect 仍返回配置、技能、说明文件三类迁移项。
调用关系:它通过 service_for_paths 调 detect;重点验证迁移服务遇到坏配置时会尽量继续,而不是整体失败。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
detect_repo_lists_mcp_hooks_commands_and_subagents328–447 ↗
async fn detect_repo_lists_mcp_hooks_commands_and_subagents()
作用:确认仓库级扫描能发现 MCP 服务器、Hooks、命令和子代理这些更复杂的迁移对象。
数据流:进去是测试 → 在假仓库里写 .mcp.json、settings.json、命令 Markdown 和子代理 Markdown → detect 返回四类迁移项,并附上名字详情。
调用关系:它布置多种源文件后调用 service_for_paths 创建服务;detect 是被测主流程,断言负责确认每种项目都被识别。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
detect_repo_skips_hooks_when_only_unsupported_hooks_exist450–473 ↗
async fn detect_repo_skips_hooks_when_only_unsupported_hooks_exist()
作用:确认如果外部助手的 Hooks 都是 Codex 不支持的形式,系统不会列出无意义的迁移项。
数据流:进去没有参数 → 写入只包含不支持事件或不支持条件的 hooks 设置 → detect 返回空清单。
调用关系:它调用迁移服务的 detect,验证过滤逻辑;这样用户不会看到迁不过来的 Hooks。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
import_repo_migrates_mcp_hooks_commands_and_subagents476–671 ↗
async fn import_repo_migrates_mcp_hooks_commands_and_subagents()
作用:确认仓库里的 MCP、Hooks、命令和子代理真的能迁成 Codex 能读的文件。
数据流:进去是测试 → 写入多种外部助手源配置 → 调 import 后读取 config.toml、hooks.json、技能文件和 agent toml → 输出通过断言体现:文件内容必须和期望一致。
调用关系:它先用 service_for_paths 得到服务,再把四个迁移项目交给 import;随后用 TOML/JSON 解析检查生成结果是否可被 Codex 配置类型接受。
调用图:调用 1 个内部函数(service_for_paths);外部调用 11 个(new, assert!, assert_eq!, format!, create_dir_all, read_to_string, write, from_str, from_value, from_str (+1 more))。
import_repo_mcp_preserves_existing_same_named_server674–734 ↗
async fn import_repo_mcp_preserves_existing_same_named_server()
作用:确认 Codex 已经有同名 MCP 服务器时,迁移不会覆盖它。
数据流:进去无参数 → 同时写入外部 .mcp.json 和已有 Codex config.toml → detect 不列迁移项,import 后原 config.toml 内容保持不变。
调用关系:它先跑 detect 验证不会提示重复迁移,再直接跑 import 验证即使被要求导入也不会改坏已有配置。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
detect_repo_mcp_lists_only_missing_servers737–789 ↗
async fn detect_repo_mcp_lists_only_missing_servers()
作用:确认 MCP 检测只列出 Codex 还没有的服务器,不把已有服务器重复算进去。
数据流:进去是测试 → 外部配置里有 docs 和 mixedTransport,Codex 已有 mixedTransport → detect 只返回 docs 这一项详情。
调用关系:它把假文件交给迁移服务 detect;验证服务会比较源配置和目标配置后只报告缺失部分。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
import_home_migrates_supported_config_fields_skills_and_agents_md792–873 ↗
async fn import_home_migrates_supported_config_fields_skills_and_agents_md()
作用:确认用户级迁移能搬走 Codex 支持的设置、技能和说明文件,并把外部助手名字改成 Codex。
数据流:进去没有参数 → 写 settings.json、SKILL.md 和说明文件 → import 后读取 Codex 的 AGENTS.md、config.toml 和技能文件 → 断言内容只包含支持字段且品牌词已替换。
调用关系:它使用 fixture_paths 和 service_for_paths;把 Config、Skills、AgentsMd 三项交给 import,检查多个迁移零件能一起工作。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 7 个(assert_eq!, format!, create_dir_all, read_to_string, write, from_str, vec!)。
import_home_config_uses_local_settings_over_project_settings876–918 ↗
async fn import_home_config_uses_local_settings_over_project_settings()
作用:确认用户级配置迁移时,本地 settings.local.json 会覆盖普通 settings.json 的同名设置。
数据流:进去无参数 → 写项目设置和本地设置,两边有重名环境变量和沙箱开关 → import 后 config.toml 使用本地值,同时保留只在项目设置里的值。
调用关系:它通过迁移服务 import Config;验证配置合并顺序,也就是本地私有设置优先。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 6 个(assert_eq!, create_dir_all, read_to_string, write, from_str, vec!)。
import_home_config_ignores_invalid_local_settings921–949 ↗
async fn import_home_config_ignores_invalid_local_settings()
作用:确认本地设置文件如果是坏 JSON,不会阻止普通设置迁移。
数据流:进去没有参数 → 写一个正常 settings.json 和一个无效 settings.local.json → import 后只按正常设置生成 config.toml。
调用关系:它调用 import 的 Config 分支;测试迁移服务对坏本地文件的容错能力。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 4 个(assert_eq!, create_dir_all, write, vec!)。
import_home_skips_empty_config_migration952–984 ↗
async fn import_home_skips_empty_config_migration()
作用:确认如果外部配置里没有 Codex 可用的新内容,系统不会生成空的 config.toml。
数据流:进去无参数 → 写入只包含无用或不需要迁移字段的 settings.json → import 返回成功数和错误数都是 0,目标 config.toml 不存在。
调用关系:它调用迁移服务 import;重点验证“没东西可搬就别制造文件”的行为。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 6 个(assert!, assert_eq!, format!, create_dir_all, write, vec!)。
import_local_plugins_returns_completed_status987–1073 ↗
async fn import_local_plugins_returns_completed_status()
作用:确认本地插件市场里的插件可以立即完成迁移,而不需要后台异步下载。
数据流:进去无参数 → 建本地 marketplace 和 plugin manifest,再写 enabledPlugins 设置 → import 返回一个成功插件,没有 pending 项,并在 config.toml 启用插件。
调用关系:它通过 fixture_paths 和 service_for_paths 调 import;验证本地插件导入会同步完成。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 8 个(assert!, assert_eq!, create_dir_all, read_to_string, write, json!, to_string_pretty, vec!)。
import_git_plugins_returns_pending_async_status1076–1137 ↗
async fn import_git_plugins_returns_pending_async_status()
作用:确认来自 Git 仓库的插件不会立刻安装,而是被标记成待后续异步处理。
数据流:进去没有参数 → 写一个 Git 来源 marketplace 的插件设置 → import 返回 pending_plugin_imports,item 结果没有成功也没有错误,config.toml 不被创建。
调用关系:它调用迁移服务 import 的 Plugins 分支;验证需要网络或下载的插件会被延期处理。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, vec!)。
detect_home_skips_config_when_target_already_has_supported_fields1140–1172 ↗
async fn detect_home_skips_config_when_target_already_has_supported_fields()
作用:确认 Codex 目标配置已经包含对应设置时,检测阶段不会再提示迁移配置。
数据流:进去无参数 → 外部 settings 和 Codex config.toml 写入相同可支持字段 → detect 返回空清单。
调用关系:它使用 fixture_paths 和 service_for_paths 调 detect;验证重复迁移会被跳过。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_home_skips_skills_when_all_skill_directories_exist1175–1193 ↗
async fn detect_home_skips_skills_when_all_skill_directories_exist()
作用:确认所有技能目录在目标位置已存在时,检测不会再提示迁移技能。
数据流:进去无参数 → 源 skills 有 skill-a,目标 .agents/skills 也已有 skill-a → detect 返回空清单。
调用关系:它调用迁移服务 detect;测试的是技能目录去重逻辑。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 2 个(assert_eq!, create_dir_all)。
import_repo_agents_md_rewrites_terms_and_skips_non_empty_targets1196–1281 ↗
async fn import_repo_agents_md_rewrites_terms_and_skips_non_empty_targets()
作用:确认迁移仓库说明文件时会替换外部助手品牌词,并且不会覆盖已有内容的 AGENTS.md。
数据流:进去无参数 → 准备一个没有目标文件的仓库和一个已有非空 AGENTS.md 的仓库 → import 后第一个生成改名后的内容,第二个保持原样。
调用关系:它把两个 AgentsMd 迁移项交给 import;同时验证品牌词重写和保护用户已有文件这两条规则。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
import_repo_agents_md_overwrites_empty_targets1284–1332 ↗
async fn import_repo_agents_md_overwrites_empty_targets()
作用:确认目标 AGENTS.md 只有空白时,可以被迁移内容覆盖。
数据流:进去无参数 → 写源说明文件,并写一个只含空格和制表符的目标 AGENTS.md → import 后目标变成迁移后的 Codex guidance。
调用关系:它调用 import 的 AgentsMd 分支;补充验证“空文件可覆盖,非空文件不可覆盖”的边界。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
detect_repo_prefers_non_empty_external_agent_agents_source1335–1376 ↗
async fn detect_repo_prefers_non_empty_external_agent_agents_source()
作用:确认仓库根目录的源说明文件为空时,系统会改用外部助手目录里的非空说明文件。
数据流:进去无参数 → 根目录 CLAUDE.md 写空白,.claude/CLAUDE.md 写有效内容 → detect 返回从 .claude 目录迁移到 AGENTS.md 的项目。
调用关系:它调用迁移服务 detect;验证源文件选择规则,避免把空说明文件当成可迁移内容。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
import_repo_hooks_preserves_disabled_codex_hooks_feature1379–1447 ↗
async fn import_repo_hooks_preserves_disabled_codex_hooks_feature()
作用:确认迁移 Hooks 时,如果 Codex 配置里已经显式关闭 hooks 功能,迁移不会偷偷改开关。
数据流:进去无参数 → 写外部 hooks 和 Codex config.toml 中 codex_hooks=false → import 生成 hooks.json,但 config.toml 保持原样。
调用关系:它调用 import 的 Hooks 分支;验证生成 hooks 文件和保留功能开关是两件独立的事。
调用图:调用 1 个内部函数(service_for_paths);外部调用 7 个(new, assert_eq!, create_dir_all, read_to_string, write, from_str, vec!)。
import_repo_mcp_uses_home_settings_toggles_when_repo_settings_missing1450–1516 ↗
async fn import_repo_mcp_uses_home_settings_toggles_when_repo_settings_missing()
作用:确认仓库没有自己的 MCP 开关设置时,会参考用户级外部助手设置来决定哪些 MCP 服务器迁移。
数据流:进去无参数 → 用户级设置禁用 blocked,项目配置里有 allowed 和 blocked → import 后只把 allowed 写进 Codex config.toml。
调用关系:它调用迁移服务 import;验证 MCP 服务器的启用/禁用规则可以从全局设置继承。
调用图:调用 1 个内部函数(service_for_paths);外部调用 8 个(new, assert_eq!, create_dir_all, read_to_string, write, json!, from_str, vec!)。
import_repo_mcp_uses_local_settings_toggles_over_project_settings1519–1577 ↗
async fn import_repo_mcp_uses_local_settings_toggles_over_project_settings()
作用:确认仓库级 MCP 开关里,本地 settings.local.json 优先于项目 settings.json。
数据流:进去无参数 → .mcp.json 有三个服务器,项目设置和本地设置给出不同启用禁用列表 → import 后只迁移本地设置允许的 local-enabled。
调用关系:它调用 import 的 McpServerConfig 分支;验证本地配置覆盖项目配置的优先级。
调用图:调用 1 个内部函数(service_for_paths);外部调用 7 个(new, assert_eq!, create_dir_all, read_to_string, write, from_str, vec!)。
import_repo_mcp_ignores_invalid_home_settings_when_repo_settings_missing1580–1625 ↗
async fn import_repo_mcp_ignores_invalid_home_settings_when_repo_settings_missing()
作用:确认用户级 settings.json 损坏时,MCP 迁移不会因此失败,而是按项目配置继续。
数据流:进去无参数 → 写坏的用户级 settings.json,再写项目配置里的 docs MCP 服务器 → import 后 docs 正常进入 config.toml。
调用关系:它通过 service_for_paths 调 import;验证坏全局设置不会挡住可用的项目 MCP 数据。
调用图:调用 1 个内部函数(service_for_paths);外部调用 8 个(new, assert_eq!, create_dir_all, read_to_string, write, json!, from_str, vec!)。
import_repo_uses_non_empty_external_agent_agents_source1628–1659 ↗
async fn import_repo_uses_non_empty_external_agent_agents_source()
作用:确认真正导入说明文件时,也会选择非空的外部助手目录源文件。
数据流:进去无参数 → 根目录源文件为空,.claude 里的源文件有内容 → import 后 AGENTS.md 来自非空源,并替换成 Codex guidance。
调用关系:它和对应的 detect 测试配套;这里验证选择规则在实际写文件时也生效。
调用图:调用 1 个内部函数(service_for_paths);外部调用 6 个(new, assert_eq!, format!, create_dir_all, write, vec!)。
import_continues_after_failed_migration_item1662–1693 ↗
async fn import_continues_after_failed_migration_item()
作用:确认一个迁移项失败后,后面的迁移项仍会继续执行。这样不会因为一个坏插件而耽误别的文件迁移。
数据流:进去无参数 → 先给一个缺少详情的插件迁移项,再给一个有效 AGENTS.md 迁移项 → import 整体继续,最后 AGENTS.md 被成功写入。
调用关系:它调用迁移服务 import;验证 import 的批处理流程有容错,不是一错全停。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
migration_metric_tags_for_skills_include_skills_count1696–1704 ↗
fn migration_metric_tags_for_skills_include_skills_count()
作用:确认技能迁移上报指标时,会带上技能数量。指标就是给统计系统看的标签。
数据流:进去无参数 → 调 migration_metric_tags,传入 Skills 和数量 3 → 断言返回标签里有 migration_type=skills 和 skills_count=3。
调用关系:它直接测试指标辅助函数;不走文件迁移流程,只检查统计标签格式。
调用图:外部调用 1 个(assert_eq!)。
detect_home_lists_enabled_plugins_from_settings1707–1753 ↗
async fn detect_home_lists_enabled_plugins_from_settings()
作用:确认用户级 settings.json 里启用的插件会被检测成可迁移项。
数据流:进去无参数 → 写 enabledPlugins,其中两个为 true、一个为 false,并写 marketplace 来源 → detect 只列出启用且有来源的插件。
调用关系:它用 fixture_paths 和 service_for_paths 调 detect;验证插件发现会按启用状态和市场来源过滤。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_home_plugins_uses_local_settings_over_project_settings1756–1811 ↗
async fn detect_home_plugins_uses_local_settings_over_project_settings()
作用:确认插件检测时,本地设置能覆盖普通设置里的启用状态。
数据流:进去无参数 → settings.json 启用 formatter 和 legacy,settings.local.json 禁用 formatter 并启用 deployer → detect 返回 legacy 和 deployer。
调用关系:它调用迁移服务 detect;测试插件设置合并规则和本地优先级。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_repo_skips_plugins_that_are_already_configured_in_codex1814–1875 ↗
async fn detect_repo_skips_plugins_that_are_already_configured_in_codex()
作用:确认全局 Codex 配置里已经启用的插件,不会在仓库检测里重复提示迁移。
数据流:进去无参数 → 仓库外部设置启用两个插件,Codex 全局 config 已有 formatter → detect 只提示 deployer。
调用关系:它调用 detect;验证插件迁移会参考全局 Codex 插件配置做去重。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
detect_repo_skips_plugins_that_are_disabled_in_codex1878–1918 ↗
async fn detect_repo_skips_plugins_that_are_disabled_in_codex()
作用:确认 Codex 全局配置里明确禁用的插件,也不会被迁移检测重新启用。
数据流:进去无参数 → 外部设置启用 formatter,Codex config 中 formatter enabled=false → detect 返回空清单。
调用关系:它测试 detect 的插件过滤逻辑;保护用户在 Codex 里做过的禁用选择。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
detect_repo_skips_plugins_without_explicit_enabled_in_codex1921–1960 ↗
async fn detect_repo_skips_plugins_without_explicit_enabled_in_codex()
作用:确认 Codex 配置里已有插件表但没有明确 enabled 字段时,也视为已配置,不再提示迁移。
数据流:进去无参数 → 外部设置启用 formatter,Codex config 已有该插件段落但无 enabled → detect 返回空。
调用关系:它调用迁移服务 detect;验证“已配置”的判断不只看 enabled=true。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
import_plugins_requires_details1963–1973 ↗
async fn import_plugins_requires_details()
作用:确认直接导入插件时必须提供具体插件详情,否则返回清楚的错误。
数据流:进去无参数 → 调 import_plugins 时传 details=None → 得到 InvalidData 错误,错误文字说明缺少详情。
调用关系:它直接测试 import_plugins,而不是批量 import;验证调用者不能不给插件清单。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 1 个(assert_eq!)。
detect_repo_does_not_skip_plugins_only_configured_in_project_codex1976–2037 ↗
async fn detect_repo_does_not_skip_plugins_only_configured_in_project_codex()
作用:确认仓库自己的 .codex 配置里已有插件,不会让全局插件迁移检测跳过它。
数据流:进去无参数 → 仓库外部设置启用 formatter,仓库 .codex/config.toml 也有 formatter,但全局 Codex 配置没有 → detect 仍提示迁移。
调用关系:它调用 detect;验证插件去重只看全局 Codex 插件配置,不看项目内 Codex 配置。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
detect_home_skips_plugins_without_marketplace_source2040–2062 ↗
async fn detect_home_skips_plugins_without_marketplace_source()
作用:确认插件虽然启用了,但找不到插件市场来源时,不会被列入迁移。
数据流:进去无参数 → settings.json 只有 enabledPlugins,没有 extraKnownMarketplaces → detect 返回空清单。
调用关系:它测试 detect 的安全过滤;没有来源就不知道去哪安装,所以不提示迁移。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_home_skips_plugins_with_invalid_marketplace_source2065–2092 ↗
async fn detect_home_skips_plugins_with_invalid_marketplace_source()
作用:确认插件市场来源格式无效时,检测会跳过该插件。
数据流:进去无参数 → 写 enabledPlugins 和 source=github 这种不符合要求的来源 → detect 返回空。
调用关系:它调用迁移服务 detect;验证不可靠的 marketplace 信息不会进入迁移清单。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_repo_filters_plugins_against_installed_marketplace2095–2221 ↗
async fn detect_repo_filters_plugins_against_installed_marketplace()
作用:确认如果插件市场已经安装,检测会按市场清单过滤,只迁可安装的插件。
数据流:进去无参数 → 构造一个已安装 marketplace,里面一个插件标记不可安装、一个可安装、一个源设置里有但市场清单缺失 → detect 只返回 available。
调用关系:它调用 detect;验证插件迁移不仅看 settings,也会看已安装市场的 manifest。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
import_plugins_requires_source_marketplace_details2224–2269 ↗
async fn import_plugins_requires_source_marketplace_details()
作用:确认导入插件时,如果请求的 marketplace 在源设置里找不到,会记录失败而不是乱装。
数据流:进去无参数 → 源设置里只有 acme-tools,但要求导入 other-tools → import_plugins 返回失败 marketplace、失败插件 id,并产生一条原始错误。
调用关系:它用 assert_single_plugin_raw_error 检查错误细节;直接测试 import_plugins 的错误路径。
调用图:调用 3 个内部函数(assert_single_plugin_raw_error, fixture_paths, service_for_paths);外部调用 5 个(default, assert_eq!, create_dir_all, write, vec!)。
import_plugins_defers_marketplace_source_validation_to_add_marketplace2272–2304 ↗
async fn import_plugins_defers_marketplace_source_validation_to_add_marketplace()
作用:确认 marketplace 来源看起来需要进一步校验时,错误会在添加 marketplace 的阶段体现。
数据流:进去无参数 → 写一个 local 路径 marketplace,再用 github_plugin_details 请求导入 → import_plugins 返回 marketplace 和插件失败,并记录 plugin_import 阶段错误。
调用关系:它调用 github_plugin_details 造输入,再用 assert_single_plugin_raw_error 验证失败记录;测试导入流程如何处理后续校验失败。
调用图:调用 4 个内部函数(assert_single_plugin_raw_error, fixture_paths, github_plugin_details, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
import_plugins_supports_external_agent_plugin_marketplace_layout2307–2380 ↗
async fn import_plugins_supports_external_agent_plugin_marketplace_layout()
作用:确认外部助手自己的插件市场目录布局,也能被 Codex 插件导入理解。
数据流:进去无参数 → 创建 .claude-plugin/marketplace.json 和插件 .codex-plugin/plugin.json → import_plugins 成功导入 marketplace 和插件,并在 config.toml 启用插件。
调用关系:它通过 service_for_paths 调 import_plugins;验证本地 marketplace 布局兼容。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 9 个(default, assert!, assert_eq!, create_dir_all, read_to_string, write, json!, to_string_pretty, vec!)。
detect_home_supports_relative_external_agent_plugin_marketplace_path2383–2454 ↗
async fn detect_home_supports_relative_external_agent_plugin_marketplace_path()
作用:确认用户级插件市场路径可以写成相对路径,检测时能正确解析。
数据流:进去无参数 → settings.json 里 marketplace path 是 ./my-marketplace,并创建对应文件 → detect 找到 cloudflare 插件迁移项。
调用关系:它调用 detect;验证相对路径会按外部助手家目录解析。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 3 个(assert_eq!, create_dir_all, write)。
detect_home_infers_external_official_marketplace_when_missing_from_settings2457–2500 ↗
async fn detect_home_infers_external_official_marketplace_when_missing_from_settings()
作用:确认官方外部插件市场即使没写在 extraKnownMarketplaces 里,也能被检测推断出来。
数据流:进去无参数 → settings.json 只启用 sample@官方市场名 → detect 仍返回这个插件迁移项。
调用关系:它用 fixture_paths 和 service_for_paths 调 detect;测试官方 marketplace 的特殊兜底逻辑。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 4 个(assert_eq!, format!, create_dir_all, write)。
import_plugins_supports_relative_external_agent_plugin_marketplace_path2503–2575 ↗
async fn import_plugins_supports_relative_external_agent_plugin_marketplace_path()
作用:确认导入插件时,也支持用户级设置里的相对 marketplace 路径。
数据流:进去无参数 → 建相对路径指向的 marketplace 和插件文件 → import_plugins 成功,config.toml 里出现启用的 cloudflare@my-plugins。
调用关系:它和对应 detect 测试配套;这里验证实际导入阶段也按外部助手家目录解析相对路径。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 7 个(default, assert!, assert_eq!, create_dir_all, read_to_string, write, vec!)。
import_plugins_infers_external_official_marketplace_when_missing_from_settings2578–2624 ↗
async fn import_plugins_infers_external_official_marketplace_when_missing_from_settings()
作用:确认导入官方 marketplace 时,即使源设置里没写 marketplace 详情,也会尝试按官方来源处理,并正确报告插件失败。
数据流:进去无参数 → 只写 sample@官方市场名的启用项 → import_plugins 返回 marketplace 成功,但具体插件未成功,并记录一条插件导入错误。
调用关系:它调用 assert_single_plugin_raw_error 检查错误;测试官方 marketplace 兜底在导入阶段的结果。
调用图:调用 3 个内部函数(assert_single_plugin_raw_error, fixture_paths, service_for_paths);外部调用 6 个(default, assert_eq!, format!, create_dir_all, write, vec!)。
detect_repo_supports_project_relative_external_agent_plugin_marketplace_path2627–2706 ↗
async fn detect_repo_supports_project_relative_external_agent_plugin_marketplace_path()
作用:确认仓库级插件市场路径写成相对路径时,会按项目根目录解析。
数据流:进去无参数 → 在 repo/my-marketplace 下建 marketplace,仓库 .claude/settings.json 写 ./my-marketplace → detect 返回 cloudflare 插件迁移项。
调用关系:它直接创建 TempDir 并调用 service_for_paths;验证项目级相对路径不同于用户级相对路径。
调用图:调用 1 个内部函数(service_for_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。
import_plugins_supports_project_relative_external_agent_plugin_marketplace_path2709–2786 ↗
async fn import_plugins_supports_project_relative_external_agent_plugin_marketplace_path()
作用:确认实际导入仓库级插件时,项目相对 marketplace 路径能正常工作。
数据流:进去无参数 → 在仓库里建 my-marketplace 和插件文件 → import_plugins 传入 repo_root 作为 cwd → 成功导入并在全局 Codex config.toml 启用插件。
调用关系:它调用 import_plugins 而不是批量 import;验证 cwd 会参与解析相对 marketplace 路径。
调用图:调用 1 个内部函数(service_for_paths);外部调用 8 个(default, new, assert!, assert_eq!, create_dir_all, read_to_string, write, vec!)。
import_skills_returns_only_new_skill_directory_names2789–2806 ↗
fn import_skills_returns_only_new_skill_directory_names()
作用:确认导入技能时,返回值只包含这次新复制的技能目录名,不包含目标里早就有的技能。
数据流:进去无参数 → 源目录有 skill-a 和 skill-b,目标已存在 skill-a → import_skills 只复制 skill-b,并返回 ["skill-b"]。
调用关系:它直接调用服务的 import_skills;测试技能迁移的去重和返回结果格式。
调用图:调用 2 个内部函数(fixture_paths, service_for_paths);外部调用 2 个(assert_eq!, create_dir_all)。
app-server/src/config_manager_service_tests.rs源码 ↗
这份文件不是正式运行时给用户调用的代码,而是“验收清单”。它用临时目录假装用户的配置目录,写入各种 config.toml 内容,然后调用 ConfigManager 去读、写、批量写配置,再检查文件内容、返回状态和错误码是否符合预期。它重点保护几类容易出问题的地方:写配置时要保留注释和顺序;删除不存在的配置不能凭空改文件;旧版 profile 配置要被拒绝;托管配置比用户配置优先时,要能说明是谁覆盖了谁;版本号不对要拒绝,避免把别人刚改的内容覆盖掉;合并写入和替换写入要有不同效果。可以把它理解成配置服务的“安全检查表”:每个测试都在模拟一个真实用户可能遇到的小场景,确保服务既好用又不偷偷破坏配置文件。
toml_value_to_item_handles_nested_config_tables15–61 ↗
fn toml_value_to_item_handles_nested_config_tables()
作用:这个测试确认把普通 TOML 数据转成可编辑 TOML 项目时,嵌套表格不会被弄丢或变成隐式表。简单说,就是配置里的多层小节还能保持正常结构。
数据流:它先准备一段带有 mcp_servers.docs 和 http_headers 的 TOML 文本 → 用 TOML 解析器读成内存里的值,再交给转换函数变成可编辑格式 → 最后逐层检查根表、服务器表、文档服务器表和请求头表都存在,并且字段值还是原来的 docs-server 和 42。
调用关系:这个测试由测试框架直接运行。它主要围绕 toml_value_to_item 的转换结果做断言,借助外部的 from_str 解析 TOML,并用断言宏确认转换没有破坏嵌套配置。
调用图:外部调用 3 个(assert!, assert_eq!, from_str)。
write_value_preserves_comments_and_order64–106 ↗
async fn write_value_preserves_comments_and_order() -> Result<()>
作用:这个测试确认写入新配置项时,原文件里的注释和大致顺序会保留下来。没有这个保证,用户手写的说明文字可能会被工具清掉。
数据流:它在临时目录里写入一份带注释、分节和已有字段的配置文件 → 创建一个不带托管配置的测试版 ConfigManager,把 features.personality=true 写进去 → 读回文件,检查新增项被放进 [features] 下面,同时原注释、空行和已有顺序没有被打乱。
调用关系:测试框架运行它后,它通过 without_managed_config_for_tests 搭一个干净的配置服务,再调用服务的 write_value。文件读写交给标准库,最后用断言比较完整文本。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(assert_eq!, json!, read_to_string, write, tempdir)。
clear_missing_nested_config_is_noop109–130 ↗
async fn clear_missing_nested_config_is_noop() -> Result<()>
作用:这个测试确认删除一个本来就不存在的嵌套配置时,不会把空文件改出内容。也就是说,“擦掉不存在的东西”应该什么都不做。
数据流:它创建一个空的配置文件 → 调用 write_value,把 features.personality 写成 JSON 的空值,表示清除这个配置 → 返回状态应该是成功,覆盖信息为空,文件内容仍然是空字符串。
调用关系:测试框架运行它。它依赖 ConfigManager::without_managed_config_for_tests 创建服务,然后把场景交给 write_value,最后用断言确认服务没有做多余修改。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 3 个(assert_eq!, write, tempdir)。
write_value_rejects_legacy_profile_selector133–162 ↗
async fn write_value_rejects_legacy_profile_selector() -> Result<()>
作用:这个测试确认服务会拒绝写入旧版的 profile 选择器。这样可以避免新旧两套配置方式混在一起,导致用户以为切了配置但实际效果不清楚。
数据流:它先写入一个只有 model 的配置文件 → 尝试把顶层 profile 改成 work → 服务应该返回验证错误,并且错误信息说明 profile 是旧式配置选择器;文件内容保持原样。
调用关系:测试框架触发后,它用无托管配置的 ConfigManager 执行 write_value。这个测试主要守住写入校验这道门,确保非法旧字段不会进入文件。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(assert!, assert_eq!, json!, write, tempdir)。
write_value_rejects_legacy_profile_table165–194 ↗
async fn write_value_rejects_legacy_profile_table() -> Result<()>
作用:这个测试确认服务会拒绝写入旧版的 profiles.* 配置表。它防止用户继续创建已经不推荐的旧格式 profile。
数据流:它创建一个空配置文件 → 尝试写入 profiles.work.model = "gpt-work" → 服务返回配置验证错误,错误文字提到 profiles 里包含旧式 profile 表;文件仍然为空。
调用关系:测试框架运行它。它通过 without_managed_config_for_tests 创建服务,再调用 write_value,用断言确认错误码、错误信息和磁盘文件都符合预期。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(assert!, assert_eq!, json!, write, tempdir)。
batch_write_rejects_legacy_profile_selector197–236 ↗
async fn batch_write_rejects_legacy_profile_selector() -> Result<()>
作用:这个测试确认批量写配置时,只要其中有旧版 profile 字段,整批写入都会失败。这样不会出现一半成功、一半非法的尴尬状态。
数据流:它先准备一个已有 model 的配置文件 → 发起批量编辑:一个改 model,另一个写 profile → 服务应拒绝整批操作,返回配置验证错误,并且原文件里的 model 没有被改掉。
调用关系:测试框架调用它。它把多条编辑交给 ConfigManager 的 batch_write,验证批量写入在遇到非法旧配置时会整体回滚,而不是先写一部分。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(assert!, assert_eq!, write, tempdir, vec!)。
write_value_supports_nested_app_paths239–298 ↗
async fn write_value_supports_nested_app_paths() -> Result<()>
作用:这个测试确认服务能写入 apps.app1... 这种多层应用配置路径。它保证单个应用的开关和工具审批设置能被正确保存和读回。
数据流:它从空配置开始 → 先把整个 apps 配置写成包含 app1 的对象 → 再单独写入 apps.app1.default_tools_approval_mode = "prompt" → 读取配置后,应该看到 app1 仍是禁用状态,同时默认工具审批模式变成 Prompt。
调用关系:测试框架运行它。它先通过 write_value 写两次,再通过 read 读取最终合成配置,用协议里的 AppsConfig、AppConfig 等类型来确认结构正确。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 4 个(assert_eq!, json!, write, tempdir)。
write_value_supports_custom_mcp_server_default_tool_approval_mode301–341 ↗
async fn write_value_supports_custom_mcp_server_default_tool_approval_mode() -> Result<()>
作用:这个测试确认自定义 MCP 服务器也能写入默认工具审批模式。MCP 可以理解成外部工具服务器,这里是在保证它的额外配置不会被服务忽略。
数据流:它先写入一个名叫 docs 的 MCP 服务器配置 → 调用 write_value 增加 mcp_servers.docs.default_tools_approval_mode = "approve" → 读回原始文件确认文本写进去了,再通过配置读取接口确认这个值出现在额外配置里。
调用关系:测试框架运行它。它用无托管配置的 ConfigManager 写入和读取,标准库负责读写文件,断言检查文件层和解析后的配置层都能看到新值。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 6 个(assert!, assert_eq!, json!, read_to_string, write, tempdir)。
read_includes_origins_and_layers344–410 ↗
async fn read_includes_origins_and_layers()
作用:这个测试确认读取配置时,不只返回最终配置值,还会告诉调用方这些值来自哪一层。这样用户看到某个设置生效时,也能知道是用户文件、托管文件还是系统默认值决定的。
数据流:它准备一个用户配置文件和一个托管配置文件,用户写 model,托管写 approval_policy → 创建带托管配置路径的测试服务并读取配置,要求包含层信息 → 最后检查最终审批策略来自托管文件,层列表里按优先级包含托管层、用户层和系统层;在 macOS 上如果机器额外提供 MDM 托管层,测试会先忽略它。
调用关系:测试框架运行它。它通过 new_for_tests 和 LoaderOverrides::with_managed_config_path_for_tests 搭出多层配置环境,再调用 read,重点验证配置层栈和来源追踪。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, try_from);外部调用 6 个(assert!, assert_eq!, matches!, write, tempdir, vec!)。
write_value_succeeds_when_managed_preferences_expand_home_directory_paths414–458 ↗
async fn write_value_succeeds_when_managed_preferences_expand_home_directory_paths() -> Result<()>
作用:这个 macOS 专用测试确认托管偏好设置里写了 ~/... 这种家目录路径时,不会影响普通用户配置的写入。它防止系统托管配置的路径展开过程误伤写配置功能。
数据流:它创建用户配置文件,并把一段托管偏好配置编码成 base64,其中包含 ~/code 路径 → 创建带这些托管偏好的测试服务 → 写入新的 model 值后,返回成功,用户配置文件被正确改成更新后的模型名。
调用关系:这个测试只在 macOS 编译运行。测试框架运行它后,它用 new_for_tests 搭服务,托管偏好由 loader override 注入,核心验证点仍是 write_value 可以正常完成。
调用图:调用 3 个内部函数(new_for_tests, default, with_managed_config_path_for_tests);外部调用 5 个(assert_eq!, json!, write, tempdir, vec!)。
write_value_reports_override461–514 ↗
async fn write_value_reports_override()
作用:这个测试检查当托管配置已经设置了某个值时,用户写入相同有效值不会被误报为“被覆盖”。它关注的是返回给界面的提示是否准确。
数据流:它让用户配置写 approval_policy = on-request,托管配置写 approval_policy = never → 用户再尝试把同一项写成 never → 写入返回成功;随后读取配置确认最终值来自托管文件,但写入结果里没有覆盖提示元数据。
调用关系:测试框架运行它。它使用 new_for_tests 创建含托管层的服务,先调用 write_value,再调用 read 检查来源信息,确保写入结果和读取结果都合理。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, try_from);外部调用 6 个(assert!, assert_eq!, json!, write, tempdir, vec!)。
version_conflict_rejected517–538 ↗
async fn version_conflict_rejected()
作用:这个测试确认写配置时如果带了错误的版本号,服务会拒绝写入。版本号像“防误覆盖标签”,用来避免把别人刚改过的文件覆盖掉。
数据流:它创建一个用户配置文件 → 调用 write_value 时故意传入 sha256:bogus 这个假的期望版本 → 服务返回版本冲突错误,而不是继续写文件。
调用关系:测试框架运行它。它通过无托管配置的 ConfigManager 直接测试 write_value 的并发保护逻辑,最后只检查错误码是否是 ConfigVersionConflict。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 4 个(assert_eq!, json!, write, tempdir)。
write_value_defaults_to_user_config_path541–562 ↗
async fn write_value_defaults_to_user_config_path()
作用:这个测试确认调用方不传文件路径时,服务会默认写到用户的 config.toml。这让前端或调用方不必每次都知道完整路径。
数据流:它在临时用户目录中创建空的默认配置文件 → 调用 write_value,但把 file_path 留空,只要求写入 model = "gpt-new" → 最后读默认配置文件,确认新模型名已经写进去。
调用关系:测试框架运行它。它用 without_managed_config_for_tests 创建服务,重点验证 write_value 在缺省路径时会自己选择用户配置文件。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(assert!, json!, read_to_string, write, tempdir)。
write_value_defaults_to_selected_user_config_path565–601 ↗
async fn write_value_defaults_to_selected_user_config_path()
作用:这个测试确认如果当前选中了另一份用户配置文件,不传文件路径时会写到被选中的那份,而不是默认主配置。这样 profile v2 这类“多配置文件”场景不会写错地方。
数据流:它准备主配置文件和一个 work.config.toml 选中配置文件 → 通过 loader override 告诉服务当前用户配置路径是选中文件,profile 名是 work → 调用不带 file_path 的写入后,选中文件变成 model = "gpt-work",主配置仍保持 gpt-main。
调用关系:测试框架运行它。它用 new_for_tests 加载带选中路径的服务设置,再让 write_value 自行决定目标文件,最后分别检查两个文件没有写反。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, from_absolute_path);外部调用 5 个(assert_eq!, json!, write, tempdir, vec!)。
load_default_config_preserves_selected_user_config_path_after_load_error604–636 ↗
async fn load_default_config_preserves_selected_user_config_path_after_load_error()
作用:这个测试确认选中的配置文件即使内容坏了,之后加载默认配置时也不会忘记“原本选中的是哪份文件”。这对错误恢复很重要。
数据流:它准备主配置文件和一份内容非法的选中配置文件 → 先调用 load_latest_config,预期因为选中文件不是合法 TOML 而失败 → 再调用 load_default_config,要求它能加载默认配置,并且配置层栈里记住的用户配置文件仍是那份选中文件。
调用关系:测试框架运行它。它用 new_for_tests 注入选中配置路径,先故意触发加载错误,再检查 load_default_config 的恢复路径不会丢掉用户选择。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, from_absolute_path);外部调用 4 个(assert_eq!, write, tempdir, vec!)。
invalid_user_value_rejected_even_if_overridden_by_managed639–671 ↗
async fn invalid_user_value_rejected_even_if_overridden_by_managed()
作用:这个测试确认用户写入的值即使最终会被托管配置盖掉,也必须先通过合法性检查。不能因为“反正不会生效”就允许脏数据写进用户文件。
数据流:它让用户文件只有 model,托管文件设置 approval_policy = never → 尝试把用户文件里的 approval_policy 写成非法值 bogus → 服务返回配置验证错误,用户文件仍只保留原来的 model。
调用关系:测试框架运行它。它搭出托管配置覆盖用户配置的环境,然后调用 write_value,验证写入校验发生在落盘之前,而且不会被托管层绕过。
调用图:调用 3 个内部函数(new_for_tests, default, with_managed_config_path_for_tests);外部调用 6 个(assert_eq!, json!, read_to_string, write, tempdir, vec!)。
reserved_builtin_provider_override_rejected674–699 ↗
async fn reserved_builtin_provider_override_rejected()
作用:这个测试确认用户不能覆盖保留的内置模型供应商 ID,比如 openai。这是为了避免把系统内建供应商配置改成不可预测的状态。
数据流:它准备一个普通用户配置文件 → 尝试写入 model_providers.openai.name = "OpenAI Override" → 服务返回配置验证错误,错误信息提到保留的内置 provider ID 和 openai;原文件不变。
调用关系:测试框架运行它。它用无托管配置的服务测试 write_value 的保护规则,最后检查错误内容和文件内容,确保非法覆盖没有落盘。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 6 个(assert!, assert_eq!, json!, read_to_string, write, tempdir)。
write_value_rejects_feature_requirement_conflict702–743 ↗
async fn write_value_rejects_feature_requirement_conflict()
作用:这个测试确认如果企业/云端要求某个功能必须开启,用户不能把它写成相反值。这样本地配置不会违反组织统一下发的要求。
数据流:它创建空用户配置,并用测试夹具模拟企业要求 [features] personality = true → 用户尝试写入 features.personality = false → 服务返回配置验证错误,错误信息说明这个功能值冲突,用户配置文件仍为空。
调用关系:测试框架运行它。它通过 CloudConfigBundleFixture::loader_with_enterprise_requirement 注入企业要求,再用 new_for_tests 创建服务,核心检查 write_value 会把云端要求纳入校验。
调用图:调用 3 个内部函数(new_for_tests, without_managed_config_for_tests, loader_with_enterprise_requirement);外部调用 6 个(assert!, assert_eq!, json!, write, tempdir, vec!)。
read_reports_managed_overrides_user_and_session_flags746–806 ↗
async fn read_reports_managed_overrides_user_and_session_flags()
作用:这个测试确认当同一个配置同时出现在用户文件、会话参数和托管文件里时,读取结果会显示托管文件优先。它还要确认层列表能按优先级说清楚谁压过了谁。
数据流:它准备用户文件写 model=user,命令行/会话覆盖写 model=session,托管文件写 model=system → 读取配置并要求包含层信息 → 最终模型应该是 system,来源应该是托管文件,层列表应显示托管层在会话层和用户层前面;macOS 额外 MDM 层会被测试忽略。
调用关系:测试框架运行它。它通过 new_for_tests 同时注入会话覆盖和托管配置,再调用 read,验证配置合并顺序和来源报告。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, try_from);外部调用 5 个(assert_eq!, matches!, write, tempdir, vec!)。
write_value_reports_managed_override809–842 ↗
async fn write_value_reports_managed_override()
作用:这个测试确认用户写入的值如果被托管配置盖住,写入接口会明确返回“已被覆盖”的状态。这样界面可以告诉用户:你写了,但实际生效的是管理员配置。
数据流:它创建空用户配置,托管文件设置 approval_policy = never → 用户写入 approval_policy = on-request → 写入本身成功,但返回状态是 OkOverridden,并带上覆盖它的托管层信息,以及真正生效的值 never。
调用关系:测试框架运行它。它用 new_for_tests 搭出托管层,然后调用 write_value,检查服务不仅会写文件,还会报告最终效果被更高优先级配置改变了。
调用图:调用 4 个内部函数(new_for_tests, default, with_managed_config_path_for_tests, try_from);外部调用 5 个(assert_eq!, json!, write, tempdir, vec!)。
upsert_merges_tables_replace_overwrites845–929 ↗
async fn upsert_merges_tables_replace_overwrites() -> Result<()>
作用:这个测试区分两种写入方式:Upsert 是“合并更新”,Replace 是“整体替换”。它确保复杂表格配置不会在合并时误删旧字段,也不会在替换时残留旧字段。
数据流:它先写入一个 mcp_servers.linear 配置,里面有普通字段、env_http_headers 和 http_headers 子表 → 用 Upsert 写入新对象后,旧的 env_http_headers.existing 应保留,http_headers 里旧值更新、新值加入 → 然后重置文件,用 Replace 写同一个对象,结果应只剩新对象包含的内容,旧的 env_http_headers 被删除。
调用关系:测试框架运行它。它通过无托管配置的 ConfigManager 两次调用 write_value,分别测试 MergeStrategy::Upsert 和 MergeStrategy::Replace,再用 TOML 解析结果做结构级比较。
调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 6 个(assert_eq!, json!, read_to_string, write, tempdir, from_str)。
app-server/src/main_tests.rs源码 ↗
这个测试文件解决的是一个很实际的问题:用户启动 app-server 时,可能不想改配置文件,只想在命令行里临时指定某些设置,比如用哪个模型、沙箱模式是什么。如果这部分解析错了,程序可能会用错模型,或者用错安全限制。这里模拟了一次真实启动命令:程序名后面带了 -c model="gpt-5-codex",又带了 --config sandbox_mode="read-only",还顺手带了 --listen off。测试先让 AppServerArgs 像正式运行时一样解析这些命令行参数,再把配置覆盖项转成 TOML 值。TOML 可以理解成一种常见配置格式。最后用断言比较结果,确认只提取出了两个配置覆盖项,并且内容完全正确。它就像收银员核对小票:用户说了什么,系统记下来的就必须一字不差。
app_server_accepts_cli_config_overrides7–37 ↗
fn app_server_accepts_cli_config_overrides()
作用:这个测试确认 app-server 能同时接受 -c 和 --config 两种写法传入的配置覆盖项。有人改命令行参数解析代码时,可以靠它及时发现有没有把这些覆盖设置弄坏。
数据流:输入是一组假装从命令行传进来的字符串,包括程序名、两个配置覆盖项和一个监听开关。函数先调用参数解析,把这些字符串变成 AppServerArgs 结构;再从里面取出配置覆盖项并解析成 TOML 值;最后把得到的列表和预期列表比较。结果是测试通过或失败;它不改真实配置文件,也不启动服务器。
调用关系:它在测试运行时由 Rust 的测试框架自动调用。函数把命令行解析这件事交给 AppServerArgs::try_parse_from,再用 assert_eq! 检查解析结果是否和预期一样;如果未来解析逻辑变了,这个测试会像报警器一样指出行为是否偏离了原来的约定。
调用图:外部调用 2 个(try_parse_from, assert_eq!)。
app-server/src/message_processor_tracing_tests.rs源码 ↗
这个文件不是正式业务代码,而是给开发者的安全网。追踪信息可以理解成“快递单号”:一个请求从客户端进来、服务器处理、核心模块继续干活,每一步都应该贴着同一个单号,这样线上排查问题时才能看清整条路线。这里用内存里的追踪导出器收集 span(一次可观察的工作片段),再搭一个假的响应服务器和 MessageProcessor,模拟客户端发 initialize、thread/start、turn/start 请求。测试会分别检查:没有外部追踪时服务器自己能产生追踪;有外部 W3C Trace Context(网页和云服务常用的追踪头格式)时,服务器 span 会认外部 span 当父节点;内部通知和核心 turn 处理也会成为它的后代。这样可以防止以后改消息处理流程时,把链路追踪弄断。
RemoteTrace::new70–83 ↗
fn new(trace_id: &str, parent_span_id: &str) -> Self
作用:造一个假的远端追踪上下文,用来假装客户端已经带着一条追踪链发来了请求。测试用它来检查服务器会不会正确接上这条链。
数据流:进去的是十六进制字符串形式的 trace id 和父 span id → 函数把它们解析成追踪库认识的编号,并拼出 W3C traceparent 字符串,还附带一个 tracestate → 出来的是 RemoteTrace,里面同时保存解析后的编号和可塞进请求里的上下文。
调用关系:两个核心测试都会先调用它准备“外部父节点”。后面的请求把它生成的 context 放进 JSON-RPC 请求里,再由测试查导出的 span 是否沿用了这些 id。
调用图:被 2 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children, turn_start_jsonrpc_span_parents_core_turn_spans);外部调用 3 个(from_hex, from_hex, format!)。
init_test_tracing86–101 ↗
fn init_test_tracing() -> &'static TestTracing
作用:初始化整套测试用的追踪收集器。它把 span 暂存在内存里,方便测试稍后直接读取和断言。
数据流:进去没有普通参数,但会读取一个全局 OnceLock(只初始化一次的保险箱)→ 第一次调用时创建内存导出器、追踪提供者、W3C 上下文传播器和全局 tracing subscriber → 出来的是静态 TestTracing 引用,后续测试重复使用同一套追踪设备。
调用关系:TracingHarness::new 会调用它。因为 tracing 的全局订阅器通常只能装一次,所以这里集中安装,避免每个测试重复设置导致崩溃。
request_from_client_request103–106 ↗
fn request_from_client_request(request: ClientRequest) -> JSONRPCRequest
作用:把更好写的 ClientRequest 转成实际处理器吃的 JSON-RPC 请求格式。测试不用手写底层 JSON。
数据流:进去是一个 ClientRequest 枚举值 → 先序列化成 JSON 值,再反序列化成 JSONRPCRequest → 出来的是 MessageProcessor::process_request 能直接处理的请求对象。
调用关系:TracingHarness::request 在真正发请求前调用它。它像一个转接头,把测试里的高层请求接到服务器的底层入口上。
调用图:被 1 处调用(request);外部调用 2 个(from_value, to_value)。
TracingHarness::new118–157 ↗
async fn new() -> Result<Self>
作用:搭好一整套能跑消息处理器的测试环境。它让测试可以像真实客户端一样发请求,但所有外部依赖都是假的、可控的。
数据流:进去没有参数 → 创建假的响应服务器、临时配置目录、测试配置、MessageProcessor、输出消息通道和追踪收集器,然后先发送 initialize 请求完成会话初始化 → 出来的是 TracingHarness,里面有处理器、接收响应的管道、会话状态和追踪工具。
调用关系:两个测试一开始都会调用它。它内部会用 build_test_config、build_test_processor 和 init_test_tracing,并通过自己的 request 方法完成初始化握手。
调用图:调用 4 个内部函数(new, build_test_config, build_test_processor, init_test_tracing);被 2 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children, turn_start_jsonrpc_span_parents_core_turn_spans);外部调用 7 个(new, default, new, Integer, create_mock_responses_server_repeating_assistant, assert!, rebuild_interest_cache)。
TracingHarness::reset_tracing159–161 ↗
fn reset_tracing(&self)
作用:清空已经收集到的追踪 span,让后面的断言只看新产生的内容。这样测试不会被初始化阶段的 span 干扰。
数据流:进去的是 harness 自己保存的 tracing 引用 → 调用内存导出器的 reset → 出来没有返回值,但追踪缓存被清空了。
调用关系:在需要把“准备阶段”和“真正要测的请求”分开的测试里使用。它不创建新环境,只是把计数板擦干净。
TracingHarness::shutdown163–166 ↗
async fn shutdown(self)
作用:测试结束时把 MessageProcessor 的后台线程和任务收干净。这样不会留下悬空任务影响其他测试。
数据流:进去的是整个 harness 的所有权 → 让处理器关闭线程,再等待后台任务排空 → 出来没有业务结果,但运行中的测试资源被正常释放。
调用关系:核心测试在断言完成后调用它。它把测试环境的收尾工作交给 MessageProcessor 自己的 shutdown_threads 和 drain_background_tasks。
TracingHarness::request168–188 ↗
async fn request(&mut self, request: ClientRequest, trace: Option<W3cTraceContext>) -> T
作用:模拟客户端发一个请求,并等到对应响应回来。它还可以把远端追踪上下文塞进请求里。
数据流:进去是 ClientRequest 和可选的 W3CTraceContext → 取出请求 id,把请求转成 JSON-RPC 格式,设置 trace 字段,交给 MessageProcessor 处理 → 再从输出通道里读匹配这个 id 的响应,反序列化成调用者想要的类型返回。
调用关系:TracingHarness::start_thread 会调用它,初始化和 turn/start 测试也依赖它。它前半段连接 MessageProcessor,后半段把等待响应的活交给 read_response。
调用图:调用 2 个内部函数(read_response, request_from_client_request);被 1 处调用(start_thread);外部调用 3 个(clone, id, panic!)。
TracingHarness::start_thread190–209 ↗
async fn start_thread(
&mut self,
request_id: i64,
trace: Option<W3cTraceContext>,
) -> ThreadStartResponse
作用:用测试方式启动一个对话线程,并确保“线程已启动”的通知也已经收到。测试后续发 turn/start 必须先有线程。
数据流:进去是请求 id 和可选追踪上下文 → 组装 thread/start 请求,调用 TracingHarness::request 得到 ThreadStartResponse → 再继续读输出通道直到看到 ThreadStarted 通知 → 出来是线程启动响应。
调用关系:两个测试都会用它制造 thread/start 场景。它把普通请求发送交给 request,把等待通知交给 read_thread_started_notification。
调用图:调用 2 个内部函数(request, read_thread_started_notification);外部调用 2 个(Integer, default)。
build_test_config212–227 ↗
async fn build_test_config(codex_home: &Path, server_uri: &str) -> Result<Config>
作用:生成一份测试专用配置,让 app-server 访问假的响应服务器,而不是连真实服务。这样测试稳定、不会花钱、也不依赖网络。
数据流:进去是临时 codex_home 路径和 mock server 地址 → 写入一份 mock responses 配置文件,再用 ConfigBuilder 加载这个临时目录 → 出来是一份 Config。
调用关系:TracingHarness::new 调用它准备配置。随后 build_test_processor 会拿这份配置创建真正的 MessageProcessor。
调用图:被 1 处调用(new);外部调用 4 个(new, to_path_buf, write_mock_responses_config_toml, default)。
build_test_processor229–272 ↗
async fn build_test_processor(
config: Arc<Config>,
) -> (
Arc<MessageProcessor>,
mpsc::Receiver<crate::outgoing_message::OutgoingEnvelope>,
)
作用:按测试需要组装一个 MessageProcessor。它把认证、配置、消息输出、环境管理、分析事件等依赖都换成测试可用的版本。
数据流:进去是一份共享 Config → 创建输出通道、AuthManager、ConfigManager、analytics 客户端、OutgoingMessageSender,再把这些装进 MessageProcessorArgs → 出来是共享的 MessageProcessor 和接收输出消息的 mpsc receiver(异步消息队列接收端)。
调用关系:TracingHarness::new 调用它。之后测试通过 harness.request 间接驱动这个 processor,并从返回的 outgoing_rx 里读取响应和通知。
调用图:调用 8 个内部函数(analytics_events_client_from_config, new, new, new, default, default_for_tests, new, shared_from_config);被 1 处调用(new);外部调用 6 个(clone, new, new, default, default, channel)。
run_current_thread_test_with_stack274–294 ↗
fn run_current_thread_test_with_stack(name: &str, future: F) -> Result<()>
作用:在一个单独线程里跑异步测试,并给这个线程更大的栈空间。它是为了避免某些测试在默认栈太小时崩掉。
数据流:进去是线程名和一个会返回 Result 的异步任务 → 新建线程,设置 4MB 栈,在里面创建 tokio 当前线程运行时并执行这个 future → 出来是异步任务的成功或失败;如果线程 panic,就转成 anyhow 错误。
调用关系:thread_start_jsonrpc_span_exports_server_span_and_parents_children 用它包住整个测试。它不参与业务处理,只是提供更稳的测试运行外壳。
调用图:被 1 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children);外部调用 2 个(anyhow!, new)。
span_attr296–304 ↗
fn span_attr(span: &'a SpanData, key: &str) -> Option<&'a str>
作用:从一个 span 里取出某个字符串属性。测试用它看 span 上有没有 rpc.method、turn.id、codex.op 这些标签。
数据流:进去是一个 SpanData 和属性名 → 遍历 span 的属性列表,找到 key 相同且值是字符串的那一项 → 出来是这个字符串值的引用;找不到或类型不对就返回 None。
调用关系:多个查找和格式化辅助函数都会靠它读 span 标签。它像查快递单上的某一栏信息。
find_rpc_span_with_trace306–326 ↗
fn find_rpc_span_with_trace(
spans: &'a [SpanData],
kind: SpanKind,
method: &str,
trace_id: TraceId,
) -> &'a SpanData
作用:在一堆导出的 span 里找到指定 trace、指定 JSON-RPC 方法、指定类型的 RPC span。找不到就把现有 span 打印出来方便排错。
数据流:进去是 span 列表、SpanKind、方法名和 trace id → 逐个检查 span 类型、rpc.system、rpc.method 和 trace id → 出来是匹配的 SpanData 引用;如果没有,就 panic 并附上 format_spans 的调试文本。
调用关系:两个核心测试都用它定位服务器请求 span。它依赖 span_attr 判断标签,失败时用 format_spans 生成更可读的错误信息。
调用图:被 2 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children, turn_start_jsonrpc_span_parents_core_turn_spans);外部调用 1 个(iter)。
find_span_with_trace328–346 ↗
fn find_span_with_trace(
spans: &'a [SpanData],
trace_id: TraceId,
description: &str,
predicate: F,
) -> &'a SpanData
作用:按自定义条件在某条 trace 里找 span。它比 find_rpc_span_with_trace 更通用,适合找核心模块产生的内部 span。
数据流:进去是 span 列表、trace id、一段描述文字和一个判断函数 → 只看同一 trace 里的 span,再用调用者给的条件筛选 → 出来是匹配的 span;找不到就 panic 并打印所有 span 摘要。
调用关系:turn_start_jsonrpc_span_parents_core_turn_spans 用它找 codex.op=user_input 的核心 turn span。它让测试可以表达“我要找这种特征的 span”,而不局限于 RPC。
调用图:被 1 处调用(turn_start_jsonrpc_span_parents_core_turn_spans);外部调用 1 个(iter)。
format_spans348–365 ↗
fn format_spans(spans: &[SpanData]) -> String
作用:把一组 span 整理成多行文字,方便测试失败时人能看懂发生了什么。
数据流:进去是 span 列表 → 对每个 span 摘出名字、span id、类型、父 id、trace id 和 rpc.method → 出来是一段换行拼接的字符串。
调用关系:很多断言辅助函数在找不到目标 span 时会调用它。它不改变测试结果,只让失败信息更像“现场照片”。
调用图:外部调用 1 个(iter)。
span_depth_from_ancestor367–390 ↗
fn span_depth_from_ancestor(
spans: &[SpanData],
child: &SpanData,
ancestor: &SpanData,
) -> Option<usize>
作用:计算一个 span 是不是另一个 span 的后代,以及隔了几层。用来确认追踪树的父子关系没有断。
数据流:进去是全部 span、一个 child span 和一个 ancestor span → 从 child 的 parent_span_id 一层层往上找父 span → 如果遇到 ancestor,就返回层数;如果找不到或链断了,就返回 None。
调用关系:assert_span_descends_from 会调用它做强断言,assert_has_internal_descendant_at_min_depth 也用同样的思路检查是否存在足够深的内部后代。
调用图:被 1 处调用(assert_span_descends_from);外部调用 1 个(iter)。
assert_span_descends_from392–403 ↗
fn assert_span_descends_from(spans: &[SpanData], child: &SpanData, ancestor: &SpanData)
作用:断言一个 span 必须挂在另一个 span 下面。它验证“这一步工作确实属于那个请求”。
数据流:进去是 span 列表、子 span 和祖先 span → 调用 span_depth_from_ancestor 检查能不能沿父链追到祖先 → 能追到就正常返回;追不到就 panic,并打印所有 span 摘要。
调用关系:turn_start_jsonrpc_span_parents_core_turn_spans 用它确认核心 user_input span 是 turn/start 服务器 span 的后代。
调用图:调用 1 个内部函数(span_depth_from_ancestor);被 1 处调用(turn_start_jsonrpc_span_parents_core_turn_spans);外部调用 1 个(panic!)。
assert_has_internal_descendant_at_min_depth405–424 ↗
fn assert_has_internal_descendant_at_min_depth(
spans: &[SpanData],
ancestor: &SpanData,
min_depth: usize,
)
作用:断言某个请求 span 下面至少有一个内部 span,而且深度达到要求。它用来证明请求处理过程中真的产生了嵌套的内部工作片段。
数据流:进去是全部 span、祖先 span 和最小深度 → 遍历同一 trace 的 Internal span,并检查它到祖先的距离是否不小于要求 → 找到就返回;找不到就 panic,并打印 span 摘要。
调用关系:thread_start_jsonrpc_span_exports_server_span_and_parents_children 多次调用它,分别检查 thread/start 下面有一层和更深层的内部 span。
调用图:被 1 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children);外部调用 2 个(iter, panic!)。
read_response426–455 ↗
async fn read_response(
outgoing_rx: &mut mpsc::Receiver<crate::outgoing_message::OutgoingEnvelope>,
request_id: i64,
) -> T
作用:从输出消息队列里等一个指定请求 id 的响应。队列里可能有别的消息,所以它会一直筛到目标响应为止。
数据流:进去是输出接收端和请求 id → 最多每次等 5 秒收一条消息,跳过非目标连接、非响应、id 不匹配的消息 → 找到后把 response.result 反序列化成调用者需要的类型并返回。
调用关系:TracingHarness::request 用它等待 MessageProcessor 处理完请求后的结果。它是测试和异步输出通道之间的过滤器。
调用图:调用 1 个内部函数(recv);被 1 处调用(request);外部调用 4 个(Integer, from_value, from_secs, timeout)。
read_thread_started_notification457–501 ↗
async fn read_thread_started_notification(
outgoing_rx: &mut mpsc::Receiver<crate::outgoing_message::OutgoingEnvelope>,
)
作用:等待“线程已启动”通知出现。thread/start 不只返回响应,还会发通知;测试需要确认通知也走完了。
数据流:进去是输出接收端 → 循环接收消息,既检查发给单个连接的消息,也检查广播消息,跳过无关内容 → 一旦看到 ServerNotification::ThreadStarted 就返回。
调用关系:TracingHarness::start_thread 在拿到 thread/start 响应后调用它。这样后续测试不会在线程还没完全宣布启动时就继续执行。
调用图:调用 1 个内部函数(recv);被 1 处调用(start_thread);外部调用 3 个(matches!, from_secs, timeout)。
wait_for_exported_spans503–526 ↗
async fn wait_for_exported_spans(tracing: &TestTracing, predicate: F) -> Vec<SpanData>
作用:反复等待追踪 span 被导出,直到满足测试给的条件。异步系统里 span 不一定立刻出现,所以需要轮询。
数据流:进去是 TestTracing 和一个判断函数 → 最多循环 200 次,每次让出执行权、强制 flush 追踪提供者、读取内存导出器里的 span,再用 predicate 判断 → 满足就返回当前 span 列表;超时就 panic 并打印最后看到的 span。
调用关系:两个测试都用它等待目标 span 出现。wait_for_new_exported_spans 也在它基础上只截取新增 span。
调用图:被 3 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children, turn_start_jsonrpc_span_parents_core_turn_spans, wait_for_new_exported_spans);外部调用 5 个(new, panic!, from_millis, yield_now, sleep)。
wait_for_new_exported_spans528–541 ↗
async fn wait_for_new_exported_spans(
tracing: &TestTracing,
baseline_len: usize,
predicate: F,
) -> Vec<SpanData>
作用:只等待并返回某个基准点之后新导出的 span。它适合在同一个测试里先做一次请求,再测第二次请求。
数据流:进去是 tracing、已有 span 数量和判断新增 span 的条件 → 调用 wait_for_exported_spans,要求总数超过 baseline_len 且新增部分满足条件 → 出来时丢掉旧 span,只返回新增 span。
调用关系:thread_start_jsonrpc_span_exports_server_span_and_parents_children 先收集无 trace 请求的 span,再用它等待带远端 trace 的第二批 span。
调用图:调用 1 个内部函数(wait_for_exported_spans);被 1 处调用(thread_start_jsonrpc_span_exports_server_span_and_parents_children)。
thread_start_jsonrpc_span_exports_server_span_and_parents_children545–633 ↗
fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> Result<()>
作用:测试 thread/start 请求的追踪是否正确:服务器会导出 JSON-RPC server span,带远端 trace 时会接到远端父 span 下面,内部工作也会挂在它下面。
数据流:进去没有业务输入,是测试框架调用 → 创建 harness,先发一个不带 trace 的 thread/start 并检查有 server span 和内部后代;再准备 RemoteTrace,发一个带 trace 的 thread/start,等待新增 span → 最后断言 server span 的 trace id、父 span id、remote 标记、span id 和内部后代深度都正确,并关闭 harness。
调用关系:它串起 RemoteTrace::new、TracingHarness::new、TracingHarness::start_thread、wait_for_exported_spans、wait_for_new_exported_spans、find_rpc_span_with_trace 和 assert_has_internal_descendant_at_min_depth。外层用 run_current_thread_test_with_stack 运行,避免栈空间问题。
调用图:调用 7 个内部函数(new, new, assert_has_internal_descendant_at_min_depth, find_rpc_span_with_trace, run_current_thread_test_with_stack, wait_for_exported_spans, wait_for_new_exported_spans);外部调用 3 个(assert!, assert_eq!, assert_ne!)。
turn_start_jsonrpc_span_parents_core_turn_spans637–711 ↗
async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()>
作用:测试 turn/start 请求会把核心对话处理产生的 span 挂到 JSON-RPC 请求 span 下面。也就是说,从客户端请求到核心 user_input 工作,追踪链应该连成一棵树。
数据流:进去没有业务输入,是 tokio 测试框架调用 → 创建 harness,先启动线程,清空旧追踪,再带 RemoteTrace 发 turn/start 文本输入 → 等到 server span 和 codex.op=user_input span 都导出 → 断言 server span 接住远端父 span、带有正确 turn.id,并确认核心 span 是 server span 的后代,最后关闭 harness。
调用关系:它用 TracingHarness::new 和 start_thread 准备会话,用 RemoteTrace::new 注入外部追踪,用 wait_for_exported_spans 等结果,再通过 find_rpc_span_with_trace、find_span_with_trace 和 assert_span_descends_from 检查父子关系。
调用图:调用 6 个内部函数(new, new, assert_span_descends_from, find_rpc_span_with_trace, find_span_with_trace, wait_for_exported_spans);外部调用 4 个(Integer, assert!, assert_eq!, vec!)。
app-server/src/request_processors/external_agent_config_processor_tests.rs源码 ↗
这个测试文件关心一个很具体的问题:当外部代理的配置被迁移或更新后,系统要不要重新加载运行时资源。可以把它想成店里换了菜单、工具或插件后,前台要不要立刻重新拿一份新清单。文件里先用 migration_item 做出一个简化版的“迁移项目”,只填最关键的类型,其他说明、目录、细节都留空。然后测试函数逐个检查不同类型:配置、技能、MCP 服务器配置、钩子、命令、插件这些会影响运行时行为,所以应该触发刷新;而会话记录只是历史状态,不该触发刷新。这个文件本身不实现刷新规则,只是给规则立“验收标准”,防止以后有人改代码时不小心把这些判断改坏。
migration_item3–12 ↗
fn migration_item(
item_type: ExternalAgentConfigMigrationItemType,
) -> ExternalAgentConfigMigrationItem
作用:这个小工具函数用来快速造一个测试用的迁移项目。测试只关心项目类型,所以它把其他字段都填成空值,省得每个断言都写一大段重复代码。
数据流:进去的是一个迁移项目类型,比如“配置”或“插件” → 它把这个类型放进 ExternalAgentConfigMigrationItem 结构里,并把描述设为空字符串、工作目录和细节设为空 → 出来的是一个完整但很简化的迁移项目对象,供测试拿去判断是否需要刷新。
调用关系:它服务于下面的测试函数,让测试能专注表达“这个类型该不该触发刷新”。它内部会创建空字符串,相当于把不重要的字段用默认空内容补齐。
调用图:外部调用 1 个(new)。
migration_items_that_update_runtime_sources_trigger_refresh15–37 ↗
fn migration_items_that_update_runtime_sources_trigger_refresh()
作用:这个测试确认:会改变运行时资源的迁移项目必须触发刷新,而不会影响运行时资源的项目不触发刷新。它是在保护一条关键规则,防止配置更新后系统状态不同步。
数据流:进去的是一组由 migration_item 临时造出来的迁移项目,每次只放一种类型 → 它把这些项目交给 migration_items_need_runtime_refresh 判断,并用断言检查结果是否符合预期 → 测试通过表示刷新规则正确;如果某个类型判断错了,测试会失败,提醒开发者规则被破坏了。
调用关系:它是这个文件的核心测试用例。运行测试时,测试框架会调用它;它借助 migration_item 准备输入,再检查 migration_items_need_runtime_refresh 的行为是否正确,并用 assert! 断言把预期写清楚。
调用图:外部调用 1 个(assert!)。
app-server/src/request_processors/remote_control_processor/remote_control_processor_tests.rs源码 ↗
远程控制功能可能因为两类原因失败:一种是请求本身不对,比如配对码没填或填了两个;另一种是服务器后面的远程控制能力不可用,属于内部故障。这个测试文件就像一组“质检清单”,逐条确认这些情况会被翻译成正确的 JSON-RPC 错误。JSON-RPC 是一种用 JSON 格式发请求和返回结果的通信规则,错误里通常有错误码和文字说明。这里重点检查两件事:没有 remote control handle(可以理解成连接远程控制后端的把手)时,配对开始和查询状态都会报内部错误;输入参数或用户可处理的问题会报无效请求,而真正的后端失败会报内部错误。这样客户端才能知道:是自己该改请求,还是服务器出了问题。
pairing_status_rejects_missing_pairing_codes47–60 ↗
fn pairing_status_rejects_missing_pairing_codes()
作用:这个测试确认:查询配对状态时,配对码和手动配对码不能都不填。没有任何码,服务器就不知道该查哪一次配对,所以应该报“无效请求”。
数据流:进去的是一个 pairing_code 和 manual_pairing_code 都为空的参数对象 → 测试调用 validate_pairing_status_params 做参数检查 → 出来应该是 Err,错误码是 INVALID_REQUEST_ERROR_CODE,消息提示必须提供 pairingCode 或 manualPairingCode。
调用关系:这个函数由普通测试框架运行。它不走完整请求处理流程,而是直接测试参数校验函数 validate_pairing_status_params,再用 assert_eq! 确认校验结果正是预期的错误。
调用图:外部调用 1 个(assert_eq!)。
pairing_status_rejects_conflicting_pairing_codes63–77 ↗
fn pairing_status_rejects_conflicting_pairing_codes()
作用:这个测试确认:查询配对状态时,不能同时提交普通配对码和手动配对码。因为两个码可能指向不同的配对流程,同时给会让服务器无法明确判断用户想查哪个。
数据流:进去的是一个同时包含 pairing_code 和 manual_pairing_code 的参数对象 → 测试调用 validate_pairing_status_params → 出来应该是 Err,错误码是 INVALID_REQUEST_ERROR_CODE,消息说明两种配对码只能二选一。
调用关系:这个测试直接围绕参数校验函数展开。它把一个故意冲突的输入交给 validate_pairing_status_params,再用 assert_eq! 确保函数没有放行这个模糊请求。
调用图:外部调用 1 个(assert_eq!)。
pairing_start_maps_invalid_input_to_invalid_request80–92 ↗
fn pairing_start_maps_invalid_input_to_invalid_request()
作用:这个测试确认:发起配对时,如果底层错误表示“输入不合适”,最终要翻译成 JSON-RPC 的“无效请求”。这能让客户端明白问题可能在请求本身,而不是服务器炸了。
数据流:进去的是一个 io::Error,错误种类是 InvalidInput,文字是“remote control pairing is unavailable” → 测试调用 map_pairing_start_error 把普通输入输出错误转换成接口错误 → 出来应该是 INVALID_REQUEST_ERROR_CODE,并保留原来的错误消息。
调用关系:这个测试检查错误转换这一个小环节。map_pairing_start_error 是被测对象,assert_eq! 负责确认转换后的错误码和消息都符合对外接口约定。
调用图:外部调用 1 个(assert_eq!)。
pairing_start_maps_backend_failures_to_internal_error95–104 ↗
fn pairing_start_maps_backend_failures_to_internal_error()
作用:这个测试确认:发起配对时,如果底层发生的是普通后端失败,就应该对外报告“内部错误”。这避免把服务器内部问题错误地说成用户请求有问题。
数据流:进去的是一个 io::Error::other,表示没有归类为用户输入问题的后端失败,消息是“remote control pairing failed” → 测试调用 map_pairing_start_error → 出来应该是 INTERNAL_ERROR_CODE,并带着同样的错误消息。
调用关系:这个函数专门补上和 InvalidInput 相反的场景。它把后端失败交给 map_pairing_start_error,再用 assert_eq! 检查该错误是否被归入服务器内部故障。
调用图:外部调用 1 个(assert_eq!)。
client_management_maps_user_actionable_errors_to_invalid_request107–123 ↗
fn client_management_maps_user_actionable_errors_to_invalid_request()
作用:这个测试确认:客户端管理相关操作里,一些用户可能自己处理的问题,要映射成“无效请求”。比如输入不对、找不到对象、权限不够、暂时不能执行,这些都不该被说成纯服务器崩溃。
数据流:进去的是一组 io::ErrorKind:InvalidInput、NotFound、PermissionDenied、WouldBlock,每个都带同一条错误消息 → 测试循环调用 map_client_management_error → 每次出来都应该是 INVALID_REQUEST_ERROR_CODE,并保留“client management unavailable”这条消息。
调用关系:这个测试一次覆盖多个相似错误种类。它在循环里把每种用户可理解、可处理的错误交给 map_client_management_error,再用 assert_eq! 确认它们都被统一翻译成无效请求。
调用图:外部调用 1 个(assert_eq!)。
client_management_maps_backend_failures_to_internal_error126–135 ↗
fn client_management_maps_backend_failures_to_internal_error()
作用:这个测试确认:客户端管理操作如果遇到真正的后端失败,要返回“内部错误”。这样外部调用者能区分“我请求写错了”和“服务器内部没办成”。
数据流:进去的是一个 io::Error::other,消息是“client management failed” → 测试调用 map_client_management_error 做错误翻译 → 出来应该是 INTERNAL_ERROR_CODE,并保留原始错误说明。
调用关系:这个函数测试客户端管理错误转换的兜底情况。它把无法归为用户可处理问题的错误交给 map_client_management_error,然后用 assert_eq! 确认对外表现为内部错误。
调用图:外部调用 1 个(assert_eq!)。
app-server/src/request_processors/thread_processor_tests.rs源码 ↗
这里不是正式运行的服务器代码,而是专门用来“找茬”的测试文件。线程处理器要处理很多容易出错的事:用户传来的工作目录可能是相对路径,也可能是绝对路径;后台终端列表要能分页;动态工具的名字和输入格式要符合外部接口要求;恢复旧会话时不能随便覆盖用户新指定的配置;连接断开时也不能留下假订阅或没清理的请求。这个文件把这些场景拆成很多小测试,每个测试都先搭一个很小的假场景,再调用真正的生产函数,最后用断言确认结果对不对。它的重要性在于:这些问题平时不一定天天出现,但一旦出错,用户看到的可能就是会话丢失、权限判断错误、工具无法调用、前端一直等不到回应等很难排查的问题。
thread_list_cwd_filter_tests::normalize_thread_list_cwd_filter_preserves_absolute_paths9–21 ↗
fn normalize_thread_list_cwd_filter_preserves_absolute_paths()
作用:检查用户给线程列表加“当前工作目录过滤条件”时,如果传入的是绝对路径,系统不会自作聪明地改掉它。绝对路径就是从磁盘根位置开始写清楚的完整地址。
数据流:进去的是一个平台对应的完整路径字符串,比如 Unix 的 /srv/repo-b 或 Windows 的 C:\srv\repo-b → 测试把它包装成一个过滤条件,交给 normalize_thread_list_cwd_filters → 出来应该还是同一个路径,只是变成程序内部使用的路径列表。
调用关系:这个测试直接检查 normalize_thread_list_cwd_filters。它模拟客户端请求线程列表时按目录筛选,确认这一步不会破坏已经明确写好的目录。
调用图:外部调用 3 个(from, assert_eq!, cfg!)。
thread_list_cwd_filter_tests::normalize_thread_list_cwd_filter_resolves_relative_paths_against_server_cwd24–36 ↗
fn normalize_thread_list_cwd_filter_resolves_relative_paths_against_server_cwd() -> std::io::Result<()>
作用:检查相对路径会按服务器当前目录补成完整路径。相对路径就是像 repo-b 这种没有从根目录开始写的地址。
数据流:进去的是字符串 repo-b → 测试先用 relative_to_current_dir 算出它相对服务器当前目录的完整结果,再调用 normalize_thread_list_cwd_filters → 出来应该是同一个完整路径列表。
调用关系:它验证 normalize_thread_list_cwd_filters 的另一半行为:客户端传短路径时,服务器要先把它变成明确位置,后续筛选线程才不会因为目录含糊而错配。
调用图:调用 1 个内部函数(relative_to_current_dir);外部调用 1 个(assert_eq!)。
background_terminal_pagination_tests::terminal45–57 ↗
fn terminal(process_id: &str) -> ThreadBackgroundTerminal
作用:造一个假的后台终端记录,方便分页测试反复使用。后台终端可以理解成线程里还在运行的命令窗口。
数据流:进去的是一个 process_id 字符串 → 函数用它拼出 item_id、command 等字段,并根据系统选择 /tmp 或 C:\tmp 作为目录 → 出来是一个完整的 ThreadBackgroundTerminal 测试对象。
调用关系:它是 background_terminal_pagination_tests::paginates_with_process_id_cursor 的小帮手。测试需要多条终端记录时,就用它快速生成结构一致、只有进程编号不同的数据。
调用图:调用 1 个内部函数(from_absolute_path);外部调用 2 个(cfg!, format!)。
background_terminal_pagination_tests::paginates_with_process_id_cursor60–95 ↗
fn paginates_with_process_id_cursor()
作用:检查后台终端列表分页能按进程编号游标继续翻页。游标可以理解成书签,告诉服务器“上次看到哪儿了”。
数据流:进去的是几条假的终端记录、一个可选游标和页大小 → paginate_background_terminals 取出当前页,并返回下一页的游标 → 测试确认第一页、第二页、游标缺失时的报错都符合预期。
调用关系:这个测试围绕 paginate_background_terminals 展开,并用 terminal 生成输入数据。它保证前端分批拉取后台终端时,不会重复、漏掉,游标找不到时也会明确失败。
调用图:外部调用 4 个(assert!, assert_eq!, paginate_background_terminals, vec!)。
thread_processor_behavior_tests::forked_from_id_from_rollout99–105 ↗
async fn forked_from_id_from_rollout(path: &Path) -> Option<String>
作用:从一个会话记录文件里读出“这个线程是从哪个线程分叉来的”。分叉可以理解成从旧对话复制出一个新分支继续聊。
数据流:进去的是记录文件路径 → 它调用 read_session_meta_line 读取会话元信息行,再取出 forked_from_id → 出来是可选的线程编号字符串;读不到或没有这个字段就返回空。
调用关系:这是 read_summary_from_rollout_preserves_forked_from_id 测试里的辅助函数。它把底层读取动作包得更简单,让测试只关心分叉编号有没有被正确保存。
调用图:外部调用 1 个(read_session_meta_line)。
thread_processor_behavior_tests::dynamic_tool152–174 ↗
fn dynamic_tool(
namespace: Option<&str>,
name: impl Into<String>,
input_schema: Value,
defer_loading: bool,
) -> DynamicToolSpec
作用:快速造一个动态工具配置,用来测试工具校验规则。动态工具就是运行时才告诉模型“你可以调用这个功能”的工具。
数据流:进去的是可选命名空间、工具名、输入格式说明和是否延迟加载 → 函数组装出工具函数;如果有命名空间,就把函数放进命名空间里 → 出来是一个 DynamicToolSpec。
调用关系:很多 validate_dynamic_tools 相关测试都会调用它。它省掉重复搭测试数据的代码,让每个测试只突出自己要验证的规则。
thread_processor_behavior_tests::validate_dynamic_tools_rejects_unsupported_input_schema177–186 ↗
thread_processor_behavior_tests::validate_dynamic_tools_accepts_sanitizable_input_schema189–198 ↗
fn validate_dynamic_tools_accepts_sanitizable_input_schema()
作用:检查有些不完整但可以修正的工具输入格式不会被误杀。比如缺少 type 这种常见写法,核心层可以补救。
数据流:进去的是一个只有 properties 的工具 schema → validate_dynamic_tools 做校验 → 出来应该是成功,没有报错。
调用关系:它保证 validate_dynamic_tools 不会过于苛刻。这样外部接入方写了常见但略简略的 schema 时,系统仍能兼容。
调用图:外部调用 1 个(vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_accepts_nullable_field_schema201–216 ↗
fn validate_dynamic_tools_accepts_nullable_field_schema()
作用:检查工具输入字段允许“字符串或空值”这种写法。空值就是 JSON 里的 null。
数据流:进去的是一个对象 schema,其中 query 字段类型是 string 或 null → validate_dynamic_tools 校验 → 出来应为成功。
调用关系:它覆盖 validate_dynamic_tools 对可空字段的支持,避免合法的工具参数描述被错误拒绝。
调用图:外部调用 1 个(vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_accepts_same_name_in_different_namespaces219–243 ↗
fn validate_dynamic_tools_accepts_same_name_in_different_namespaces()
作用:检查两个不同命名空间里可以有同名工具。命名空间像文件夹,不同文件夹里可以有同名文件。
数据流:进去的是 codex_app 和 other_app 两个命名空间,各有一个 my_tool → validate_dynamic_tools 检查重名规则 → 出来应为成功。
调用关系:它验证 validate_dynamic_tools 的重名判断是按命名空间隔开的,而不是全局一刀切。
调用图:外部调用 1 个(vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_accepts_responses_compatible_identifiers246–258 ↗
fn validate_dynamic_tools_accepts_responses_compatible_identifiers()
作用:检查符合 Responses API 要求的工具名和命名空间名会被接受。Responses API 是外部模型服务的一种接口。
数据流:进去的是包含字母、数字、下划线和短横线的命名空间与工具名 → validate_dynamic_tools 校验 → 出来应为成功。
调用关系:它确认 validate_dynamic_tools 允许外部接口认可的名字格式,避免把合法名字挡在门外。
调用图:外部调用 1 个(vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_rejects_duplicate_name_in_same_namespace261–285 ↗
thread_processor_behavior_tests::thread_turns_list_merges_in_progress_active_turn_before_agent_status_running288–325 ↗
fn thread_turns_list_merges_in_progress_active_turn_before_agent_status_running()
作用:检查列出对话轮次时,会把正在进行但还没落盘的当前轮次补进去。轮次可以理解成一次用户提问到助手回答的过程。
数据流:进去的是已保存的消息、一条正在进行的 live-turn、线程状态等 → reconstruct_thread_turns_for_turns_list 合并这些信息 → 出来的轮次列表最后一项应该就是正在进行的 active_turn。
调用关系:它验证 reconstruct_thread_turns_for_turns_list。这个函数服务于前端查看线程轮次列表,确保用户能看到当前正在发生的对话,而不是只能看到旧记录。
调用图:外部调用 2 个(assert_eq!, vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_rejects_empty_namespace328–341 ↗
thread_processor_behavior_tests::validate_dynamic_tools_rejects_reserved_namespace344–357 ↗
thread_processor_behavior_tests::validate_dynamic_tools_rejects_name_not_supported_by_responses360–377 ↗
thread_processor_behavior_tests::validate_dynamic_tools_rejects_namespace_not_supported_by_responses380–397 ↗
thread_processor_behavior_tests::validate_dynamic_tools_rejects_name_longer_than_responses_limit400–415 ↗
thread_processor_behavior_tests::validate_dynamic_tools_rejects_namespace_fields_over_limits418–441 ↗
fn validate_dynamic_tools_rejects_namespace_fields_over_limits()
作用:检查命名空间名字和描述太长时都会被拒绝。
数据流:先进去一个 65 字符的命名空间名 → validate_dynamic_tools 应报名字过长;然后把名字改短、描述改成 1025 字符 → 再校验应报描述过长。
调用关系:它测试 validate_dynamic_tools 对命名空间字段的长度限制,防止过大的描述或名字传到外部接口。
调用图:外部调用 3 个(assert!, unreachable!, vec!)。
thread_processor_behavior_tests::validate_dynamic_tools_rejects_reserved_responses_namespace444–458 ↗
thread_processor_behavior_tests::summary_from_stored_thread_preserves_millisecond_precision461–507 ↗
fn summary_from_stored_thread_preserves_millisecond_precision()
作用:检查从数据库里的线程记录生成摘要时,时间里的毫秒不会丢。毫秒就是一秒的千分之一。
数据流:进去的是 created_at 和 updated_at 带 .678、.789 的 StoredThread → summary_from_stored_thread 生成 ConversationSummary → 出来的 timestamp 和 updated_at 应保留毫秒。
调用关系:它验证 summary_from_stored_thread 的时间格式行为。线程列表和排序依赖这些时间,精度丢失可能让显示和同步变得不准。
调用图:调用 2 个内部函数(read_only, from_string);外部调用 3 个(parse_from_rfc3339, from, assert_eq!)。
thread_processor_behavior_tests::requested_permissions_trust_project_uses_permission_profile_intent510–583 ↗
fn requested_permissions_trust_project_uses_permission_profile_intent()
作用:检查系统判断“用户是否信任这个项目”时,看的是权限配置的真实意图,而不是只看表面名字。
数据流:进去的是多种权限覆盖配置:完全访问、工作区可写、只读、自定义可写规则等 → requested_permissions_trust_project 判断每种是否算信任项目 → 出来应是可写类为真,只读类为假。
调用关系:它测试 requested_permissions_trust_project。这个判断会影响项目能否获得更高权限,错了可能要么过度放权,要么误拦用户操作。
调用图:调用 4 个内部函数(from_runtime_permissions, read_only, workspace_write, restricted);外部调用 3 个(assert!, test_path_buf, vec!)。
thread_processor_behavior_tests::config_load_error_marks_cloud_config_bundle_failures_for_relogin586–610 ↗
fn config_load_error_marks_cloud_config_bundle_failures_for_relogin()
作用:检查云端配置加载因为认证失败时,返回给客户端的错误会明确提示需要重新登录。
数据流:进去的是一个 CloudConfigBundleLoadError,错误码 Auth,HTTP 状态 401,并带详细说明 → config_load_error 转成应用层错误 → 出来应带 reason、errorCode、action=relogin、statusCode 和 detail。
调用关系:它验证 config_load_error 对云配置认证错误的特殊包装。前端看到 action=relogin 后,才能引导用户重新登录。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_eq!, other)。
thread_processor_behavior_tests::config_load_error_leaves_non_cloud_config_bundle_failures_unmarked613–624 ↗
fn config_load_error_leaves_non_cloud_config_bundle_failures_unmarked()
作用:检查普通配置加载错误不会被误标成云配置问题。
数据流:进去的是一个普通 IO 错误文本 → config_load_error 转成应用层错误 → 出来的 message 要说明配置加载失败,但 data 应该为空。
调用关系:它防止 config_load_error 过度归因。不是云配置的问题,就不该让前端显示重新登录或云策略相关提示。
调用图:外部调用 3 个(assert!, assert_eq!, other)。
thread_processor_behavior_tests::config_load_error_marks_non_auth_cloud_config_bundle_failures_without_relogin627–644 ↗
fn config_load_error_marks_non_auth_cloud_config_bundle_failures_without_relogin()
作用:检查云配置加载失败但不是认证问题时,会标明云配置原因,但不会要求重新登录。
数据流:进去的是错误码 RequestFailed 的 CloudConfigBundleLoadError → config_load_error 包装 → 出来的 data 应包含 cloudConfigBundle 和错误码、详情,但没有 action=relogin。
调用关系:它验证 config_load_error 能区分认证失败和普通请求失败,让客户端给出合适提示。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, other)。
thread_processor_behavior_tests::config_load_error_marks_invalid_cloud_config_bundle_failures_without_relogin647–664 ↗
fn config_load_error_marks_invalid_cloud_config_bundle_failures_without_relogin()
作用:检查云配置包内容无效时,错误会标明是云配置包问题,但不会误导用户重新登录。
数据流:进去的是错误码 InvalidBundle 的 CloudConfigBundleLoadError → config_load_error 转换 → 出来的 data 应包含 reason、errorCode 和 detail。
调用关系:它继续覆盖 config_load_error 的云配置错误分类,确保配置内容坏了和登录失效不会混在一起。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, other)。
thread_processor_behavior_tests::derive_config_from_params_uses_session_thread_config_model_provider667–730 ↗
async fn derive_config_from_params_uses_session_thread_config_model_provider() -> Result<()>
作用:检查加载配置时,会优先使用会话线程配置里的模型服务商,而不是被请求里的同名覆盖项轻易改掉。
数据流:进去的是临时配置目录、一个会话级模型服务商 session、以及请求传入的一批覆盖配置 → ConfigManager::load_with_overrides 加载合并 → 出来的配置应使用 session 服务商,插件功能保持会话配置关闭,同时保留 bypass_hook_trust。
调用关系:它测试 ConfigManager 加载配置时和线程配置源的关系。这个场景保证恢复或继续会话时,模型服务商这类关键设置不会被不该生效的请求覆盖污染。
调用图:调用 3 个内部函数(new, default, new);外部调用 11 个(new, from, new, new, default, assert!, assert_eq!, default, default, json! (+1 more))。
thread_processor_behavior_tests::collect_resume_override_mismatches_includes_service_tier733–788 ↗
fn collect_resume_override_mismatches_includes_service_tier()
作用:检查恢复线程时,如果请求的服务档位和当前记录不同,会被列入不匹配提示。服务档位可以理解成模型服务的优先级或套餐。
数据流:进去的是请求里 service_tier=priority,以及已保存快照里 service_tier=flex → collect_resume_override_mismatches 比较两边 → 出来应是一条说明 requested 和 active 不一致的字符串。
调用关系:它验证 collect_resume_override_mismatches。恢复旧会话时,这个函数帮助发现用户想改的配置和旧会话实际配置不一致。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, test_path_buf)。
thread_processor_behavior_tests::test_thread_metadata790–806 ↗
fn test_thread_metadata(
model: Option<&str>,
reasoning_effort: Option<ReasoningEffort>,
) -> Result<ThreadMetadata>
作用:造一份线程元数据,供恢复配置相关测试使用。元数据就是线程的身份、模型、创建时间等基本档案。
数据流:进去的是可选模型名和可选推理强度 → 函数创建固定线程编号和 rollout 路径,用 ThreadMetadataBuilder 生成元数据,再填入模型和推理强度 → 出来是一份 ThreadMetadata。
调用关系:它是多个 merge_persisted_resume_metadata 测试和时间格式测试的辅助函数,让这些测试不用重复手写元数据。
调用图:调用 2 个内部函数(from_string, new);外部调用 3 个(from, now, default)。
thread_processor_behavior_tests::summary_from_thread_metadata_formats_protocol_timestamps_as_seconds809–822 ↗
fn summary_from_thread_metadata_formats_protocol_timestamps_as_seconds() -> Result<()>
作用:检查从线程元数据生成摘要时,协议里的时间会格式化到秒,而不是带毫秒。
数据流:进去的是 created_at 和 updated_at 带毫秒的 ThreadMetadata → summary_from_thread_metadata 生成摘要 → 出来的 timestamp 和 updated_at 应变成没有毫秒的 ISO 时间字符串。
调用关系:它验证 summary_from_thread_metadata 的协议兼容格式。这个行为和 summary_from_stored_thread 的毫秒保留形成对照,说明不同来源的摘要格式有不同约定。
调用图:外部调用 3 个(parse_from_rfc3339, test_thread_metadata, assert_eq!)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_prefers_persisted_model_and_reasoning_effort825–854 ↗
fn merge_persisted_resume_metadata_prefers_persisted_model_and_reasoning_effort() -> Result<()>
作用:检查恢复旧线程时,如果用户没有显式指定模型,就会沿用旧线程保存的模型和推理强度。
数据流:进去的是空请求覆盖、默认类型安全覆盖,以及带模型 gpt-5.1-codex-max 和 high 推理强度的元数据 → merge_persisted_resume_metadata 合并 → 出来会把模型、模型服务商和 model_reasoning_effort 填入覆盖配置。
调用关系:它测试 merge_persisted_resume_metadata 的默认继承行为。恢复会话时,这能让旧线程继续使用原来的模型设置。
调用图:外部调用 3 个(test_thread_metadata, assert_eq!, default)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_preserves_explicit_overrides857–885 ↗
fn merge_persisted_resume_metadata_preserves_explicit_overrides() -> Result<()>
作用:检查用户明确指定的新配置不会被旧线程保存的配置覆盖。
数据流:进去的是已指定模型 gpt-5.2-codex、推理强度 low 的覆盖配置,以及旧元数据中的另一个模型和 high → merge_persisted_resume_metadata 合并 → 出来仍保留用户显式指定的模型和 low。
调用关系:它保护 merge_persisted_resume_metadata 的优先级规则:用户这次明确说了什么,就比历史记录更重要。
调用图:外部调用 5 个(default, from, test_thread_metadata, assert_eq!, String)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_skips_persisted_values_when_model_overridden888–914 ↗
fn merge_persisted_resume_metadata_skips_persisted_values_when_model_overridden() -> Result<()>
作用:检查请求里已经覆盖了模型时,旧线程里的模型相关值不会再被自动塞进来。
数据流:进去的是请求覆盖中有 model=gpt-5.2-codex,类型安全覆盖为空,旧元数据有另一个模型和推理强度 → merge_persisted_resume_metadata 合并 → 出来只保留请求里的 model,不额外填旧模型、服务商或推理强度。
调用关系:它测试 merge_persisted_resume_metadata 对 request_overrides 的识别,避免同一类设置从两个地方互相打架。
调用图:外部调用 5 个(from, test_thread_metadata, assert_eq!, default, String)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_skips_persisted_values_when_provider_overridden917–937 ↗
fn merge_persisted_resume_metadata_skips_persisted_values_when_provider_overridden() -> Result<()>
作用:检查用户已经指定模型服务商时,旧线程里的模型设置不会反过来覆盖它。
数据流:进去的是 typesafe_overrides 里 model_provider=oss,旧元数据里有 mock_provider 和模型 → merge_persisted_resume_metadata 合并 → 出来保留 oss,不填旧模型,也不生成请求覆盖。
调用关系:它验证 merge_persisted_resume_metadata 的另一条优先级规则:服务商被明确改了,就不要再套用旧服务商绑定的模型信息。
调用图:外部调用 3 个(default, test_thread_metadata, assert_eq!)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_skips_persisted_values_when_reasoning_effort_overridden940–966 ↗
fn merge_persisted_resume_metadata_skips_persisted_values_when_reasoning_effort_overridden() -> Result<()>
作用:检查用户已经指定推理强度时,不会被旧线程保存的推理强度覆盖。
数据流:进去的是请求覆盖里 model_reasoning_effort=low,旧元数据里是 high → merge_persisted_resume_metadata 合并 → 出来仍是 low,并且不自动填旧模型和服务商。
调用关系:它确保 merge_persisted_resume_metadata 尊重本次请求中的推理强度选择。
调用图:外部调用 5 个(from, test_thread_metadata, assert_eq!, default, String)。
thread_processor_behavior_tests::merge_persisted_resume_metadata_skips_missing_values969–988 ↗
fn merge_persisted_resume_metadata_skips_missing_values() -> Result<()>
作用:检查旧元数据里缺少模型和推理强度时,合并过程不会凭空造值。
数据流:进去的是没有模型、没有推理强度但有模型服务商的元数据,以及空覆盖配置 → merge_persisted_resume_metadata 合并 → 出来只填模型服务商,不填模型和推理强度。
调用关系:它补上 merge_persisted_resume_metadata 的空值场景,防止恢复会话时产生不存在的配置。
调用图:外部调用 3 个(test_thread_metadata, assert_eq!, default)。
thread_processor_behavior_tests::read_summary_from_rollout_returns_empty_preview_when_no_user_message991–1044 ↗
async fn read_summary_from_rollout_returns_empty_preview_when_no_user_message() -> Result<()>
作用:检查从 rollout 记录文件读取摘要时,如果里面没有用户消息,预览文字应为空,而不是报错或乱填。
数据流:进去的是临时写出的 rollout.jsonl,里面只有会话元信息,没有用户消息 → read_summary_from_rollout 读取文件和修改时间 → 出来是 ConversationSummary,其中 preview 是空字符串,其他基本字段正确。
调用关系:它测试 read_summary_from_rollout 对极简会话文件的兼容性。线程列表依赖摘要,即使没有聊天内容也要能显示这条线程。
调用图:调用 2 个内部函数(default, from_string);外部调用 10 个(new, new, new, new, assert_eq!, parse_from_rfc3339, format!, write, SessionMeta, new)。
thread_processor_behavior_tests::read_summary_from_rollout_preserves_agent_nickname1047–1094 ↗
async fn read_summary_from_rollout_preserves_agent_nickname() -> Result<()>
作用:检查从 rollout 读取摘要再转成线程对象时,子代理的昵称和角色不会丢失。子代理可以理解成主对话派出去的一个助手分身。
数据流:进去的是包含 agent_nickname=atlas、agent_role=explorer 的 rollout 文件 → read_summary_from_rollout 读摘要,summary_to_thread 转成线程 → 出来的线程应保留昵称和角色,并按预期处理 thread_source。
调用关系:它串起 read_summary_from_rollout 和 summary_to_thread,验证存储格式到前端线程对象之间的重要身份信息不会丢。
调用图:调用 3 个内部函数(default, from_string, from_absolute_path);外部调用 6 个(new, SubAgent, assert_eq!, format!, write, SessionMeta)。
thread_processor_behavior_tests::read_summary_from_rollout_preserves_forked_from_id1097–1132 ↗
async fn read_summary_from_rollout_preserves_forked_from_id() -> Result<()>
作用:检查 rollout 文件里的 forked_from_id 能被保存并读出来。
数据流:进去的是包含 forked_from_id 的 rollout 文件路径 → forked_from_id_from_rollout 调用 read_session_meta_line 读取元信息 → 出来应是同一个分叉来源线程编号。
调用关系:它通过辅助函数 forked_from_id_from_rollout 验证底层会话元信息读取,确保线程分叉关系不会丢失。
调用图:调用 2 个内部函数(default, from_string);外部调用 5 个(new, assert_eq!, format!, write, SessionMeta)。
thread_processor_behavior_tests::aborting_pending_request_clears_pending_state1135–1196 ↗
async fn aborting_pending_request_clears_pending_state() -> Result<()>
作用:检查线程清理时,正在等待客户端回答的请求会被取消,并从待处理列表里移除。
数据流:进去的是一个线程专属发送器,它先发出 ToolRequestUserInput 请求 → abort_pending_server_requests 取消所有待处理请求 → 出来客户端等待结果的通道收到错误,错误说明是轮次状态变化导致,并且 pending_requests_for_thread 为空。
调用关系:它测试 ThreadScopedOutgoingMessageSender 和 OutgoingMessageSender 的配合。当前端或轮次状态变化时,服务器不能让旧请求一直挂着。
调用图:调用 4 个内部函数(disabled, new, new, from_string);外部调用 7 个(new, ToolRequestUserInput, assert!, assert_eq!, panic!, channel, vec!)。
thread_processor_behavior_tests::summary_from_state_db_metadata_preserves_agent_nickname1199–1235 ↗
fn summary_from_state_db_metadata_preserves_agent_nickname() -> Result<()>
作用:检查从状态数据库里的元数据生成摘要时,代理昵称和角色会保留下来。
数据流:进去的是线程编号、路径、时间、来源 JSON、agent_nickname=atlas、agent_role=explorer 等数据库字段 → summary_from_state_db_metadata 组装摘要,summary_to_thread 转成线程 → 出来的线程保留昵称和角色。
调用关系:它验证 summary_from_state_db_metadata 到 summary_to_thread 的链路。无论摘要来自 rollout 文件还是状态数据库,代理身份都应一致。
调用图:调用 2 个内部函数(from_string, from_absolute_path);外部调用 4 个(from, SubAgent, assert_eq!, to_string)。
thread_processor_behavior_tests::removing_thread_state_clears_listener_and_active_turn_history1238–1279 ↗
async fn removing_thread_state_clears_listener_and_active_turn_history() -> Result<()>
作用:检查删除线程状态时,会取消监听器、清空订阅连接,并清掉正在进行轮次的历史快照。
数据流:进去的是一个 ThreadStateManager,里面有连接订阅、取消通道和一条 TurnStarted 事件 → remove_thread_state 删除该线程状态 → 出来取消通道收到信号,订阅列表为空,状态里的 cancel_tx 和 active_turn_snapshot 都为空。
调用关系:它测试 ThreadStateManager 的清理动作。线程卸载时,如果这些状态不清掉,可能会出现幽灵监听、旧轮次残留或资源泄漏。
调用图:调用 2 个内部函数(new, from_string);外部调用 6 个(default, default, assert!, assert_eq!, channel, TurnStarted)。
thread_processor_behavior_tests::removing_auto_attached_connection_preserves_listener_for_other_connections1282–1330 ↗
async fn removing_auto_attached_connection_preserves_listener_for_other_connections() -> Result<()>
作用:检查移除一个连接时,如果同一线程还有别的连接在看,线程监听器不能被误关掉。
数据流:进去的是两个连接都订阅了同一个线程,并设置了取消通道 → remove_connection 移除 connection_a → 出来不需要卸载线程,取消通道没有收到信号,订阅列表只剩 connection_b。
调用关系:它验证 ThreadStateManager 对多连接订阅的计数逻辑。一个浏览器标签关了,不应影响另一个仍在看的标签。
调用图:调用 2 个内部函数(new, from_string);外部调用 4 个(default, assert!, assert_eq!, channel)。
thread_processor_behavior_tests::adding_connection_to_thread_updates_has_connections_watcher1333–1381 ↗
async fn adding_connection_to_thread_updates_has_connections_watcher() -> Result<()>
作用:检查线程是否还有连接的观察器会随订阅增减及时更新。观察器可以理解成一个会自动通知变化的小仪表。
数据流:进去的是两个已初始化连接和一个线程 → 先订阅 connection_a,订阅观察器看到 true;取消 connection_a 后变 false;再添加 connection_b 后变 true → 出来每次变化都能被 watcher 收到。
调用关系:它测试 ThreadStateManager 的 has-connections watch 通知。后台任务可以靠这个信号判断线程是否还有前端在看。
调用图:调用 2 个内部函数(new, from_string);外部调用 4 个(from_secs, default, assert!, timeout)。
thread_processor_behavior_tests::closed_connection_cannot_be_reintroduced_by_auto_subscribe1384–1405 ↗
async fn closed_connection_cannot_be_reintroduced_by_auto_subscribe() -> Result<()>
作用:检查已经关闭的连接不能被自动订阅逻辑重新加回线程。
数据流:进去的是一个初始化后立刻被 remove_connection 删除的连接 → try_ensure_connection_subscribed 尝试把它订阅到线程 → 出来应返回空,并且线程没有订阅者。
调用关系:它验证 ThreadStateManager 对连接生命周期的保护。连接关了就是关了,不能因为自动订阅又“复活”。
调用图:调用 2 个内部函数(new, from_string);外部调用 3 个(default, assert!, assert_eq!)。
thread_processor_behavior_tests::first_attestation_capable_connection_for_thread_only_uses_thread_subscribers1408–1480 ↗
async fn first_attestation_capable_connection_for_thread_only_uses_thread_subscribers() -> Result<()>
作用:检查选择能做 attestation 的连接时,只会在当前线程的订阅者里选。attestation 可以理解成客户端向服务器证明某些状态或身份的能力。
数据流:进去的是多个连接:有的支持 attestation,有的不支持,有的订阅别的线程 → first_attestation_capable_connection_for_thread 分别查询两个线程 → 出来每个线程都返回自己订阅者中最早的支持连接。
调用关系:它测试 ThreadStateManager 的连接筛选逻辑。这样服务器请求证明时,不会把请求发给无关线程的连接,也不会发给不支持该能力的连接。
调用图:调用 2 个内部函数(new, from_string);外部调用 3 个(default, assert!, assert_eq!)。
app-server/src/request_processors/thread_summary_tests.rs源码 ↗
这份测试文件像是在给“会话摘要生成器”做一次小考。它先假装有一段聊天记录:开头是会话的基本信息,后面有两条用户消息。第一条看起来像 AGENTS.md 的项目说明,不应该当成用户真正想说的话;第二条虽然前面带了“旧上下文”,但里面用特殊标记标出了真正的用户输入“Count to 5”。测试会把这些假数据交给 extract_conversation_summary,也就是实际负责生成摘要的函数。然后它检查结果是不是符合预期:会话编号、时间、路径、模型来源、工作目录等都要正确,最关键的是 preview 必须是“Count to 5”。如果将来有人改坏了摘要逻辑,让它误把说明文件内容当成预览,这个测试就会失败,提醒开发者问题出在这里。
extract_conversation_summary_prefers_plain_user_messages9–68 ↗
fn extract_conversation_summary_prefers_plain_user_messages() -> Result<()>
作用:这个测试确认:生成会话摘要时,系统应该跳过类似项目说明的用户消息,抓住真正的用户请求作为预览文字。它的价值是防止会话列表里显示一堆无关的配置说明,而不是用户实际问了什么。
数据流:进去的是测试自己造出来的一段假会话记录,包括会话元信息、两条用户消息、时间戳和文件路径。测试先把字符串变成会话编号,再用 JSON 拼出聊天记录,并从第一条记录解析出会话元数据。接着它调用摘要提取函数,拿到实际生成的 ConversationSummary。最后它把实际结果和手写的期望结果做比较;如果完全一样,测试通过,否则测试失败。
调用关系:这个函数是在测试阶段由 Rust 测试框架自动运行的。它会用 ThreadId::from_string 准备一个合法会话编号,用 vec! 和 json! 组装输入数据,然后把核心工作交给 extract_conversation_summary。拿到结果后,它用 assert_eq! 做最终核对,相当于判卷:摘要函数有没有把真正的用户消息选出来。
调用图:调用 1 个内部函数(from_string);外部调用 3 个(from, assert_eq!, vec!)。
共享夹具原语
这些可复用辅助工具创建集成测试所依赖的模拟认证状态、配置文件、缓存模型、预设响应、发布数据和假后端服务。
app-server/tests/common/analytics_server.rs源码 ↗
有些功能运行时会把“发生了什么事”发给分析系统,比如用户做了某个操作。测试时如果真的去访问外部分析服务,就会慢、不稳定,还可能污染真实数据。这个文件用 wiremock 做了一个本地假的服务器。wiremock 可以理解成“测试用的假前台”:它站在门口,等着别人按指定方式来请求。这里它只接受一个请求:用 POST 方法访问 /codex/analytics-events/events。只要收到这样的请求,就直接回一个 200,意思是“成功了”。这样测试既能确认代码会发请求,又不用依赖真实网络和真实分析后台。
start_analytics_events_server8–16 ↗
async fn start_analytics_events_server() -> Result<MockServer>
作用:启动一个只供测试使用的假分析事件服务器。测试代码可以拿到这个服务器地址,让被测程序把分析事件发到这里,而不是发到真实服务。
数据流:进去时不需要传参数;函数先启动一个本地 MockServer,也就是假的 HTTP 服务器;然后设置规则:只要收到 POST 请求,并且路径是 /codex/analytics-events/events,就返回状态码 200;最后把已经配置好的服务器交出去。如果启动或配置出错,会用 Result 把错误带出去。
调用关系:它通常在测试开始前被调用,用来搭好外部分析服务的替身。它内部把具体工作交给 wiremock:用 start 启动服务器,用 method 和 path 描述要匹配的请求,用 given 建立匹配规则,用 ResponseTemplate::new(200) 准备成功响应。后续测试会使用返回的 MockServer 来运行被测代码。
app-server/tests/common/auth_fixtures.rs源码 ↗
很多功能都要先知道“用户是否已登录、账户是什么、套餐是什么、令牌能不能刷新”。如果每个测试都手写这些登录文件,很容易写错,也很难看懂。这个文件就像一个测试用的“假证件制作器”:ChatGptAuthFixture 用来拼出一套假的登录资料,比如访问令牌、刷新令牌、账号 ID;ChatGptIdTokenClaims 用来拼出 ID token 里的用户信息,比如邮箱、套餐、ChatGPT 用户 ID。encode_id_token 会把这些信息包装成测试用的 JWT(JSON Web Token,一种用点号分成三段的身份信息字符串),但它不做真正签名,只是让解析代码能读懂。write_chatgpt_auth 最后把这些资料写进测试目录下的 auth.json。这样测试就能稳定地模拟各种登录场景,比如套餐缺失、刷新失败、账号切换,而不用依赖外部服务。
ChatGptAuthFixture::new29–37 ↗
fn new(access_token: impl Into<String>) -> Self
作用:创建一份最基础的假 ChatGPT 登录资料。测试只要给一个访问令牌,就能得到默认可用的登录夹具,后面再按需要补充账号、邮箱、套餐等信息。
数据流:输入一个访问令牌 → 函数把它存起来,并自动填入默认刷新令牌、空账号 ID、默认的 ID token 声明、未指定的刷新时间 → 输出一个 ChatGptAuthFixture,供测试继续修改或直接写成 auth.json。
调用关系:这是大多数测试准备假登录状态的起点。调用图里很多测试会先用它造出夹具,比如查询认证状态、读取账户信息、挂载分析捕获、列出应用等测试;它内部只做简单转换和默认值填充,不把工作交给复杂流程。
调用图:被 100 处调用(get_auth_status_omits_token_after_permanent_refresh_failure, get_auth_status_omits_token_after_proactive_refresh_failure, get_auth_status_returns_token_after_proactive_refresh_recovery, get_account_omits_chatgpt_after_permanent_refresh_failure, get_account_with_chatgpt, get_account_with_chatgpt_missing_plan_claim_returns_unknown, mount_analytics_capture, list_apps_does_not_emit_empty_interim_updates, list_apps_emits_updates_and_returns_after_both_lists_load, list_apps_force_refetch_patches_updates_from_cached_snapshots (+15 more));外部调用 2 个(into, default)。
ChatGptAuthFixture::refresh_token39–42 ↗
fn refresh_token(mut self, refresh_token: impl Into<String>) -> Self
作用:给假登录资料换一个刷新令牌。刷新令牌可以理解成“续签凭证”,测试刷新登录状态时会用到它。
数据流:输入已有的 ChatGptAuthFixture 和新的刷新令牌文本 → 函数把夹具里的 refresh_token 改成新值 → 输出修改后的同一个夹具,方便继续链式设置。
调用关系:它通常接在 ChatGptAuthFixture::new 后面使用,用来调整测试场景。提供的调用图没有列出具体测试直接调用它,但它的设计就是给测试拼装假认证资料时使用。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::account_id44–47 ↗
fn account_id(mut self, account_id: impl Into<String>) -> Self
作用:给假登录资料填入账户 ID。测试需要模拟“这个令牌属于哪个账户”时会用它。
数据流:输入已有夹具和一个账户 ID → 函数把 account_id 从空值改成这个 ID → 输出修改后的夹具。
调用关系:它是夹具构建链上的一个可选步骤。后续 write_chatgpt_auth 会把这个 account_id 放进 TokenData,再保存到 auth.json 里。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::plan_type49–52 ↗
fn plan_type(mut self, plan_type: impl Into<String>) -> Self
作用:给假用户设置套餐类型,比如免费、Plus、Team 之类的值。测试可以用它检查系统看到不同套餐时会怎么表现。
数据流:输入已有夹具和套餐类型文本 → 函数把这个值写进夹具内部的 ID token 声明里 → 输出修改后的夹具。
调用关系:它修改的是 ChatGptAuthFixture 里包含的 ChatGptIdTokenClaims。等 write_chatgpt_auth 被调用时,这个套餐值会经过 encode_id_token 变成 JWT 里的声明。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::chatgpt_user_id54–57 ↗
fn chatgpt_user_id(mut self, chatgpt_user_id: impl Into<String>) -> Self
作用:给假用户设置 ChatGPT 用户 ID。测试需要区分不同用户身份时会用到它。
数据流:输入已有夹具和用户 ID → 函数把 chatgpt_user_id 写进 ID token 声明 → 输出修改后的夹具。
调用关系:它是准备假 ID token 的快捷入口。之后 encode_id_token 会把这个字段放到 JWT 的 OpenAI 认证信息区域里。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::chatgpt_account_id59–62 ↗
fn chatgpt_account_id(mut self, chatgpt_account_id: impl Into<String>) -> Self
作用:给假用户设置 ChatGPT 账户 ID。它和普通 account_id 类似,但这里是写进 ID token 声明里的账户信息。
数据流:输入已有夹具和 ChatGPT 账户 ID → 函数把该值保存到 claims.chatgpt_account_id → 输出修改后的夹具。
调用关系:它服务于需要测试 ID token 内账户声明的场景。写 auth.json 时,write_chatgpt_auth 会间接通过 encode_id_token 使用这个值。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::email64–67 ↗
fn email(mut self, email: impl Into<String>) -> Self
作用:给假登录用户设置邮箱。测试需要展示、读取或校验用户邮箱时会用它。
数据流:输入已有夹具和邮箱文本 → 函数把邮箱写进内部 claims → 输出修改后的夹具。
调用关系:它是 ChatGptAuthFixture 的链式设置方法之一。最终邮箱会被 encode_id_token 放进 JWT 的普通 payload,也就是身份信息正文里。
调用图:外部调用 1 个(into)。
ChatGptAuthFixture::last_refresh69–72 ↗
fn last_refresh(mut self, last_refresh: Option<DateTime<Utc>>) -> Self
作用:设置这份假登录资料“上次刷新令牌”的时间。测试可以用它模拟刚刷新过、很久没刷新、或者没有刷新记录的情况。
数据流:输入已有夹具和一个可选时间;这个时间本身也可以是空,表示明确没有刷新时间 → 函数把它记录到 last_refresh 字段 → 输出修改后的夹具。
调用关系:write_chatgpt_auth 会读取这个设置。如果测试没设置它,write_chatgpt_auth 会默认填当前时间;如果测试设置了,就按测试指定的时间保存。
ChatGptAuthFixture::claims74–77 ↗
fn claims(mut self, claims: ChatGptIdTokenClaims) -> Self
作用:一次性替换整份 ID token 声明。比逐个设置邮箱、套餐、用户 ID 更直接,适合测试已经准备好一整套身份声明的情况。
数据流:输入已有夹具和一个 ChatGptIdTokenClaims → 函数用新的 claims 覆盖旧的 claims → 输出修改后的夹具。
调用关系:它把身份声明的准备工作交给调用方。之后 write_chatgpt_auth 会拿这份完整 claims 去生成测试用 ID token。
ChatGptIdTokenClaims::new89–91 ↗
fn new() -> Self
作用:创建一份空的 ID token 声明。测试可以从空白开始,只填自己关心的邮箱、套餐或用户 ID。
数据流:没有业务输入 → 函数生成默认值,所有字段都是空 → 输出一个 ChatGptIdTokenClaims。
调用关系:它常被外部认证、登录、设置认证令牌等测试调用,作为拼装身份声明的起点。它本身只返回默认结构,后续通常会接 email、plan_type、chatgpt_user_id 等方法。
调用图:被 8 处调用(account_read_refresh_token_is_noop_in_external_mode, external_auth_refresh_error_fails_turn, external_auth_refresh_invalid_access_token_fails_turn, external_auth_refresh_mismatched_workspace_fails_turn, external_auth_refreshes_on_unauthorized, login_account_chatgpt_device_code_succeeds_and_notifies, set_auth_token_cancels_active_chatgpt_login, set_auth_token_updates_account_and_notifies);外部调用 1 个(default)。
ChatGptIdTokenClaims::email93–96 ↗
fn email(mut self, email: impl Into<String>) -> Self
作用:在 ID token 声明里填入邮箱。这样生成出来的假身份令牌就像包含了用户邮箱一样。
数据流:输入已有 claims 和邮箱文本 → 函数把 email 字段从空改成这个邮箱 → 输出修改后的 claims。
调用关系:它通常跟在 ChatGptIdTokenClaims::new 后面,用来一点点拼出身份信息。encode_id_token 之后会读取这个字段并写入 JWT。
调用图:外部调用 1 个(into)。
ChatGptIdTokenClaims::plan_type98–101 ↗
fn plan_type(mut self, plan_type: impl Into<String>) -> Self
作用:在 ID token 声明里填入套餐类型。测试可以借此模拟不同订阅等级。
数据流:输入已有 claims 和套餐类型文本 → 函数设置 plan_type 字段 → 输出修改后的 claims。
调用关系:它准备的数据会被 encode_id_token 放进 OpenAI 认证专用的 payload 区域。这样后续解析逻辑能像读真实令牌一样读到套餐。
调用图:外部调用 1 个(into)。
ChatGptIdTokenClaims::chatgpt_user_id103–106 ↗
fn chatgpt_user_id(mut self, chatgpt_user_id: impl Into<String>) -> Self
作用:在 ID token 声明里填入 ChatGPT 用户 ID。它让测试能模拟某个具体用户的身份。
数据流:输入已有 claims 和用户 ID → 函数设置 chatgpt_user_id 字段 → 输出修改后的 claims。
调用关系:它是构造假 JWT 内容的一个小零件。encode_id_token 会把这个值编码进令牌,write_chatgpt_auth 再把解析后的令牌数据保存起来。
调用图:外部调用 1 个(into)。
ChatGptIdTokenClaims::chatgpt_account_id108–111 ↗
fn chatgpt_account_id(mut self, chatgpt_account_id: impl Into<String>) -> Self
作用:在 ID token 声明里填入 ChatGPT 账户 ID。测试用它来模拟令牌里自带的账户归属信息。
数据流:输入已有 claims 和账户 ID → 函数设置 chatgpt_account_id 字段 → 输出修改后的 claims。
调用关系:它和其他 claims 设置方法一起,为 encode_id_token 准备原材料。没有它时,生成的测试令牌就不会包含这个账户声明。
调用图:外部调用 1 个(into)。
encode_id_token114–144 ↗
fn encode_id_token(claims: &ChatGptIdTokenClaims) -> Result<String>
作用:把测试准备好的身份声明编码成一个假的 JWT 字符串。JWT 是一种常见的登录身份令牌,长得像“三段用点号连接的文本”。
数据流:输入 ChatGptIdTokenClaims → 函数先做一个简单的 JWT 头部,再把邮箱、套餐、用户 ID、账户 ID 放进 payload;然后用 URL 安全的 base64 编码,最后拼上一个固定的假签名 → 输出一个形如 header.payload.signature 的字符串。
调用关系:write_chatgpt_auth 会调用它来生成原始 ID token。它生成的 token 随后马上交给 parse_chatgpt_jwt_claims 解析,目的是让测试数据走一遍和真实令牌类似的解析路径。
调用图:被 1 处调用(write_chatgpt_auth);外部调用 5 个(format!, json!, new, Object, to_vec)。
write_chatgpt_auth146–179 ↗
fn write_chatgpt_auth(
codex_home: &Path,
fixture: ChatGptAuthFixture,
cli_auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> Result<()>
作用:把一份假 ChatGPT 登录资料真正写到测试用的认证文件里。这样被测程序启动或读取账户时,会以为用户已经登录了。
数据流:输入测试目录 codex_home、一份 ChatGptAuthFixture、以及认证凭据保存模式 → 函数先把 claims 编成 ID token,再解析出系统内部使用的 token 数据;接着组装 AuthDotJson,填入 ChatGPT 登录模式、访问令牌、刷新令牌、账号和刷新时间;最后调用 save_auth 写到磁盘或对应的凭据存储 → 成功时返回空结果,失败时带上“写 auth.json”等上下文错误信息。
调用关系:这是这个文件里把假数据落地的最后一步。它调用 encode_id_token 造令牌,调用 parse_chatgpt_jwt_claims 把令牌变成系统认识的结构,再把保存工作交给 save_auth;很多测试会先用 ChatGptAuthFixture::new 准备数据,最后通过它把登录状态放进测试环境。
调用图:调用 3 个内部函数(encode_id_token, default, parse_chatgpt_jwt_claims);外部调用 1 个(save_auth)。
app-server/tests/common/config.rs源码 ↗
这个文件不是真正给用户运行时用的,而是给自动化测试准备环境用的。测试里经常需要假装有一个 Codex 配置目录,并让程序去连接一个假的模型服务器,而不是联网找真实服务。这里的函数就像“测试前的布景师”:它们拿到测试临时目录、假服务器地址、功能开关等信息,然后拼出 config.toml 并写到磁盘。第一种函数支持更多选项,比如功能开关、是否要求 OpenAI 认证、自动压缩 token 限制、模型供应商名字等;第二种函数更简单,重点是写入 chatgpt_base_url。重要的是,它们会把重试次数设成 0、沙箱设成只读、审批策略设成 never,让测试更可控、更快失败,也更安全。
write_mock_responses_config_toml6–80 ↗
fn write_mock_responses_config_toml(
codex_home: &Path,
server_uri: &str,
feature_flags: &BTreeMap<Feature, bool>,
auto_compact_limit: i64,
requires_openai_auth: Option<bool>,
作用:这个函数为测试生成一份比较完整的 config.toml。调用者可以指定假服务器地址、功能开关、模型供应商 ID、是否需要 OpenAI 认证等,让测试程序像读真实配置一样运行,但实际连的是测试服务器。
数据流:进去的是一个测试用的 Codex 主目录路径、假服务器地址、一组功能开关、自动压缩限制、认证要求、模型供应商 ID 和压缩提示词。它先把功能开关转换成配置文件里能识别的键值行,再根据是否要求 OpenAI 认证拼出不同的供应商配置;如果供应商 ID 是 openai,还会额外写 openai_base_url。最后它把这些内容组合成 config.toml,写到给定目录下。出来的是写文件的结果:成功就是空结果,失败就是系统返回的输入输出错误。
调用关系:它通常在测试开始前被测试代码调用,用来搭好配置文件这个“舞台”。函数内部把路径交给 join 来定位 config.toml,用 format! 拼出文本,再交给 std::fs::write 写到磁盘;它不继续调用项目里的其他业务逻辑,而是给后续被测试的程序提供配置输入。
write_mock_responses_config_toml_with_chatgpt_base_url82–108 ↗
fn write_mock_responses_config_toml_with_chatgpt_base_url(
codex_home: &Path,
server_uri: &str,
chatgpt_base_url: &str,
) -> std::io::Result<()>
作用:这个函数生成一份更简单的测试配置,重点是把 chatgpt_base_url 写进去。它适合那些只关心 ChatGPT 基础地址是否被正确读取和使用的测试。
数据流:进去的是测试用目录、假模型服务器地址和 ChatGPT 基础地址。它在目录下找到 config.toml 的位置,把固定的模型、沙箱、审批策略、模型供应商信息和传入的两个地址拼成配置文本,然后写入文件。出来的是写文件是否成功的结果;如果目录不可写或路径有问题,就返回输入输出错误。
调用关系:它也是测试布置阶段的小帮手,但比 write_mock_responses_config_toml 少很多可调选项。它只用 join 找文件位置,用 format! 生成配置内容,再用 std::fs::write 写文件;写完后,真正被测试的代码会像读取普通配置一样读取这份文件。
app-server/tests/common/mock_model_server.rs源码 ↗
真实的模型接口不适合直接放进自动化测试里:它可能慢、贵、不稳定,还会让测试结果受外部服务影响。这个文件就是为了解决这个问题。它用 wiremock(一个专门伪装 HTTP 服务的测试工具)启动一个本地假服务器,只接收发到类似 /v1/responses 的 POST 请求。测试可以提前塞进去一组回答,服务器就像发牌一样按顺序吐出这些回答;也可以设置成每次都返回同一句助手消息。这里的回答会包装成 SSE(Server-Sent Events,一种服务器连续推送事件文本的格式),因为真实模型接口通常也是这样一段段返回结果的。SeqResponder 像一个带计数器的发号机,每来一次请求就把计数加一,并取出对应的下一份回复。这样测试既能模拟真实接口,又能保证结果可控。
create_mock_responses_server_sequence14–31 ↗
async fn create_mock_responses_server_sequence(responses: Vec<String>) -> MockServer
作用:创建一个测试用的假模型服务器,并要求它必须被调用指定次数。每次请求 /v1/responses 时,它会按传入列表的顺序返回下一条假回复。
数据流:输入是一组字符串回复 → 函数先启动一个本地假服务器,再把这些回复放进 SeqResponder 里,并设置一个计数器从 0 开始 → 之后它注册一条规则:只要收到 POST 请求且路径像是以 /responses 结尾,就交给这个顺序回复器处理,并且要求调用次数等于回复数量 → 输出是已经配置好的 MockServer,测试代码可以拿它当真实模型服务来用。
调用关系:测试准备阶段会调用它来搭好假服务器。它先借助 start_mock_server 启动服务,再用 wiremock 的 given、method、path_regex 配好匹配规则;真正收到请求时,后续工作会交给 SeqResponder::respond 去取下一条回复。
调用图:调用 1 个内部函数(start_mock_server);外部调用 4 个(new, given, method, path_regex)。
create_mock_responses_server_sequence_unchecked35–50 ↗
async fn create_mock_responses_server_sequence_unchecked(responses: Vec<String>) -> MockServer
作用:创建一个会按顺序返回多条假回复的模型服务器,但不检查总共被调用了几次。适合那些请求次数不固定、只关心前几次返回内容的测试。
数据流:输入是一组字符串回复 → 函数启动本地假服务器,把回复和从 0 开始的调用计数器放入 SeqResponder → 它注册 POST 到 /responses 结尾路径的匹配规则,让请求按顺序拿回复 → 输出是配置好的 MockServer。和严格版本不同,它不会要求请求次数必须刚好等于回复数量。
调用关系:测试代码在不想对调用次数做硬性断言时会用它。它同样通过 start_mock_server 建服务器,通过 wiremock 的匹配器筛选请求,并把实际生成回复的工作交给 SeqResponder::respond。
调用图:调用 1 个内部函数(start_mock_server);外部调用 4 个(new, given, method, path_regex)。
SeqResponder::respond58–65 ↗
fn respond(&self, _: &wiremock::Request) -> ResponseTemplate
作用:这是顺序回复器真正出牌的地方。每收到一次请求,它就取出下一条预先准备好的回复,并把它包装成模型接口常用的 SSE 响应。
数据流:输入是一次 HTTP 请求,但这个函数并不读取请求内容 → 它用原子计数器(可以安全递增的数字,避免并发时数错)把调用次数加一,得到这次应该用第几条回复 → 从回复列表里取出对应字符串,如果没有对应回复就让测试失败 → 最后调用 sse_response 把字符串做成 HTTP 响应返回。
调用关系:它不会单独被测试代码直接调用,而是被 wiremock 在匹配到请求后自动调用。create_mock_responses_server_sequence 和 create_mock_responses_server_sequence_unchecked 会把它注册到假服务器上;它再把最后的响应格式化工作交给 responses::sse_response。
调用图:调用 1 个内部函数(sse_response);外部调用 1 个(fetch_add)。
create_mock_responses_server_repeating_assistant69–82 ↗
async fn create_mock_responses_server_repeating_assistant(message: &str) -> MockServer
作用:创建一个每次都返回同一句助手消息的假模型服务器。适合测试“只需要模型固定说一句话”的简单场景。
数据流:输入是一段助手要说的文字 → 函数启动本地假服务器,然后把“创建响应”“助手消息”“完成响应”这些事件拼成一段 SSE 内容 → 它注册规则:收到 POST 到 /responses 结尾的请求时,总是返回这段固定内容 → 输出是配置好的 MockServer。
调用关系:测试准备阶段会调用它来快速获得一个稳定的假模型接口。它用 responses::sse 组装事件流,用 responses::sse_response 包装成 HTTP 响应,再通过 wiremock 的 given、method、path_regex 把这份固定响应挂到服务器上。
调用图:调用 3 个内部函数(sse, sse_response, start_mock_server);外部调用 4 个(given, vec!, method, path_regex)。
app-server/tests/common/models_cache.rs源码 ↗
这份文件解决的是测试里的一个常见麻烦:程序启动后可能会去网络上刷新可用模型列表,但测试不应该依赖网络。它就像考试前先把参考答案放到本地抽屉里,让程序直接从抽屉拿,而不是临时出门去问别人。文件里先把项目内置的模型预设转换成缓存里需要的模型信息格式,再写成 codex 主目录下的 models_cache.json。缓存里还会放入当前时间和客户端版本,让读取缓存的 ModelsManager 认为这份缓存是新鲜的,可以直接用。它也提供了一个更灵活的入口:测试可以传入自己指定的模型列表,用来模拟某些特殊模型是否存在的场景。
preset_to_info16–61 ↗
fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo
作用:把一个“模型预设”转换成可以写进缓存文件的“模型信息”。简单说,就是把测试里现成的模型说明,整理成程序平时从服务器拿到的那种格式。
数据流:进去的是一个 ModelPreset,也就是内置模型预设,以及一个 priority,也就是排序用的优先级数字。它会复制模型的名称、说明、推理能力、服务档位、是否显示在选择器里等信息,再补上一些测试用的默认值,比如基础提示词、上下文窗口大小、截断策略。出来的是一个完整的 ModelInfo,后面可以直接放进 models_cache.json。
调用关系:它是写缓存前的格式转换小零件。write_models_cache 会先拿到所有内置模型预设,然后逐个调用它,把这些预设变成缓存能识别的模型信息。它内部会用 default_input_modalities 给模型填默认输入类型,也会用 bytes 创建按字节限制的截断策略。
调用图:调用 2 个内部函数(bytes, default_input_modalities);外部调用 2 个(default, new)。
write_models_cache67–86 ↗
fn write_models_cache(codex_home: &Path) -> std::io::Result<()>
作用:给测试写一份标准的 models_cache.json。调用它之后,测试里的 ModelsManager 通常就会用本地缓存,不再去网络刷新模型列表。
数据流:进去的是 codex_home,也就是测试用的 Codex 主目录路径。它先读取项目内置的所有模型预设,只挑出会显示在模型选择器里的模型;然后按列表顺序给它们编号作为排序优先级;再把每个预设转成 ModelInfo。最后它把整理好的模型列表交给 write_models_cache_with_models 写入磁盘。结果是在 codex_home 下面出现一个 models_cache.json 文件。
调用关系:这是测试最常用的便捷入口。测试准备环境时调用它,它会向 all_model_presets 要一份稳定的内置模型清单,再把真正写文件的工作交给 write_models_cache_with_models。
调用图:调用 2 个内部函数(write_models_cache_with_models, all_model_presets)。
write_models_cache_with_models90–105 ↗
fn write_models_cache_with_models(
codex_home: &Path,
models: Vec<ModelInfo>,
) -> std::io::Result<()>
作用:用调用者给定的模型列表写 models_cache.json。它适合那些需要精确控制“有哪些模型可用”的测试。
数据流:进去的是 codex_home 路径和一组 ModelInfo 模型信息。它先把路径拼成 codex_home/models_cache.json;再取当前 UTC 时间作为 fetched_at,表示缓存刚刚获取;再拿当前客户端版本;然后把这些内容和模型列表组装成 JSON(一种常见的文本数据格式);最后把格式化后的 JSON 写到磁盘。出来的主要结果不是返回数据,而是生成或覆盖这个缓存文件;如果写文件失败,会返回输入输出错误。
调用关系:它是实际落盘的函数。write_models_cache 会在准备好默认模型列表后调用它;测试也可以绕过默认列表,直接调用它写入自己定制的模型。它会用路径拼接、取当前时间、获取客户端版本、生成 JSON、把 JSON 转成漂亮的文本、写文件这些基础工具来完成最后一步。
调用图:被 1 处调用(write_models_cache);外部调用 6 个(join, now, client_version_to_whole, json!, to_string_pretty, write)。
app-server/tests/common/responses.rs源码 ↗
这个文件主要服务于测试。SSE 是“服务器发送事件”,可以理解成服务器一条一条往客户端推送的小纸条。被测的 app-server 需要读这些纸条,判断模型是要说最终答案、执行命令、打补丁、询问用户,还是申请权限。为了让测试稳定可重复,这里用几个小函数直接造出固定格式的 SSE 字符串。每个函数都会先生成一个“回复已创建”的事件,中间放入真正要测试的内容,比如一次工具调用或一段助手消息,最后再加上“回复完成”的事件。这样测试就像给程序播放一段录好的服务器对话,观察它后续会不会调用正确的工具、解析正确的参数。重要的是,这里还会把命令、路径、权限请求等内容包装成 JSON 字符串,因为真实接口里的工具参数就是这样传的。
create_shell_command_sse_response5–23 ↗
fn create_shell_command_sse_response(
command: Vec<String>,
workdir: Option<&Path>,
timeout_ms: Option<u64>,
call_id: &str,
) -> anyhow::Result<String>
作用:这个函数造一段“模型要求执行 shell_command 工具”的假 SSE 回复。测试会用它来检查程序能不能正确读出命令、工作目录和超时时间。
数据流:进去的是命令数组、可选的工作目录、可选的超时时间,以及这次工具调用的编号。函数先把命令数组拼成一条像人在终端里输入的命令字符串,再把命令、目录、超时包装成 JSON 文本,最后放进一串 SSE 事件里返回;如果命令拼接或 JSON 转换失败,就返回错误。
调用关系:它位于测试准备阶段,测试代码调用它来伪造模型回复。它把具体事件的制作交给 core_test_support::responses 里的事件构造函数,最后用 sse 把多条事件串成一份完整响应。
调用图:调用 1 个内部函数(sse);外部调用 4 个(json!, to_string, try_join, vec!)。
create_final_assistant_message_sse_response25–31 ↗
fn create_final_assistant_message_sse_response(message: &str) -> anyhow::Result<String>
作用:这个函数造一段“模型直接给出最终文字回答”的假 SSE 回复。测试用它来模拟没有工具调用、只有助手消息的场景。
数据流:进去的是一段助手要说的话。函数把它放进一个助手消息事件中,前面加上“回复创建”,后面加上“回复完成”,最后输出完整的 SSE 字符串。
调用关系:它通常被测试用例用来喂给被测程序,看看程序在收到普通最终回答时是否能正常结束或展示消息。事件细节由 responses 模块生成,它自己只负责把这些事件按正确顺序组装起来。
create_apply_patch_sse_response33–42 ↗
fn create_apply_patch_sse_response(
patch_content: &str,
call_id: &str,
) -> anyhow::Result<String>
作用:这个函数造一段“模型要求应用代码补丁”的假 SSE 回复。测试可以用它确认程序收到补丁内容后,会走修改文件的那条路。
数据流:进去的是补丁文本和工具调用编号。函数把补丁内容包装成一个特殊的工具调用事件,再和创建事件、完成事件一起拼成 SSE 字符串返回。
调用关系:它在测试里扮演“远端模型说要改代码”的录音带。真正构造补丁工具调用事件的工作交给 responses 模块里的辅助函数,最后仍由 sse 统一打包。
create_exec_command_sse_response44–62 ↗
fn create_exec_command_sse_response(call_id: &str) -> anyhow::Result<String>
作用:这个函数造一段“模型要求执行 exec_command 工具”的假 SSE 回复。它还会根据运行测试的系统选择合适的命令写法,避免 Windows 和类 Unix 系统命令不同导致测试坏掉。
数据流:进去的是工具调用编号。函数先判断当前是不是 Windows:Windows 用 cmd.exe,其他系统用 /bin/sh;然后拼出一条 echo hi 命令,把命令和等待时间写成 JSON 文本,最后包装成 SSE 事件串返回。
调用关系:测试代码调用它来模拟一次执行命令的请求。它用 cfg! 做系统判断,用 JSON 工具生成参数文本,再把事件交给 responses::sse 组合成被测程序可以读取的响应。
调用图:调用 1 个内部函数(sse);外部调用 5 个(cfg!, json!, to_string, once, vec!)。
create_request_user_input_sse_response64–85 ↗
fn create_request_user_input_sse_response(call_id: &str) -> anyhow::Result<String>
作用:这个函数造一段“模型要求向用户提问”的假 SSE 回复。测试用它来确认程序能识别需要用户选择的场景,比如继续还是停止。
数据流:进去的是工具调用编号。函数内部固定写好一个确认问题,包含问题编号、标题、问题文字,以及“是”和“否”两个选项;然后把这些内容转成 JSON 文本,塞进 request_user_input 工具调用事件,最后输出完整 SSE 字符串。
调用关系:它用于测试交互流程:被测程序读到这段响应后,应该知道模型不是在给最终答案,而是在请求用户输入。事件的外壳仍由 responses 模块生成,这个函数负责提供那份具体问题内容。
create_request_permissions_sse_response87–105 ↗
fn create_request_permissions_sse_response(call_id: &str) -> anyhow::Result<String>
作用:这个函数造一段“模型申请文件系统写权限”的假 SSE 回复。测试用它来检查程序在遇到权限申请时,会不会正确解析理由和要写入的目录。
数据流:进去的是工具调用编号。函数固定生成一个权限申请:理由是选择工作区根目录,请求对当前目录和 ../shared 拥有写权限;它把这份申请转成 JSON 文本,再包装成 request_permissions 工具调用的 SSE 响应返回。
调用关系:它服务于权限相关测试,模拟远端模型提出授权请求。它把权限内容做成 JSON 参数,再交给 responses 里的事件构造和 sse 打包函数,形成完整的假服务器回复。
app-server/tests/common/rollout.rs源码 ↗
项目会把一次对话保存成 rollout 文件,也就是一行一条 JSON 的日志文件,常叫 JSONL(每一行都是一个独立的 JSON)。测试如果想验证“能不能找回旧会话”,就需要一些假的旧记录。这个文件专门做这件事:按日期拼出 sessions/YYYY/MM/DD 目录,生成一个新的会话 UUID(一串唯一编号),写入最少但够用的会话元信息、用户消息和事件消息。它还提供几个变体:可以带模型供应商、Git 信息、会话来源、父会话编号、token 用量,或者更复杂的文本元素。可以把它理解成测试里的“道具师”:不负责真正聊天,只负责摆好一份逼真的聊天档案,方便别的测试检查系统读档、续聊、分叉时会不会出错。
rollout_path18–28 ↗
fn rollout_path(codex_home: &Path, filename_ts: &str, thread_id: &str) -> PathBuf
作用:根据 CODEX_HOME、文件名里的时间戳和会话编号,算出 rollout 文件应该放在哪里。别人用它可以避免手写路径,减少目录拼错或日期放错的风险。
数据流:进去的是一个根目录、一个形如 YYYY-MM-DDThh-mm-ss 的时间字符串、一个 thread_id。它从时间字符串里切出年、月、日,再拼成 CODEX_HOME/sessions/年/月/日/rollout-时间-thread_id.jsonl。出来的是这个文件的完整路径,它不创建文件,也不改磁盘。
调用关系:它是很多造假会话函数的共同“地址计算器”。create_fake_rollout_with_source_and_parent_thread_id 用它决定新文件写到哪;create_fake_rollout_with_token_usage 用它找到已经生成的文件,再往里面追加 token 用量记录。
调用图:被 2 处调用(create_fake_rollout_with_source_and_parent_thread_id, create_fake_rollout_with_token_usage);外部调用 2 个(join, format!)。
create_fake_rollout38–55 ↗
fn create_fake_rollout(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
git_info: Option<GitInfo>,
) -> Result<String>
作用:创建一个最普通、最小够用的假 rollout 会话文件。调用者只要给时间、预览文本等基本信息,就能拿到一个可用于测试的会话编号。
数据流:进去的是 CODEX_HOME、文件名时间、JSON 里的时间、用户消息预览、可选模型供应商、可选 Git 信息。它不自己写文件,而是把这些参数转交给更通用的 create_fake_rollout_with_source,并固定把会话来源设为命令行。出来的是新生成的会话 UUID 字符串,同时磁盘上多了一个假会话文件。
调用关系:它是最常用的简化入口,适合不关心会话来源的测试。create_fake_rollout_with_token_usage 会先调用它造出基础会话,再额外追加 token 统计事件。
调用图:调用 1 个内部函数(create_fake_rollout_with_source);被 1 处调用(create_fake_rollout_with_token_usage)。
create_fake_rollout_with_token_usage63–110 ↗
fn create_fake_rollout_with_token_usage(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
) -> Result<String>
作用:创建一个带 token 用量记录的假会话。token 可以理解成模型读写文字时使用的“计量单位”,这个函数让测试能检查恢复旧会话时,用量统计有没有被正确读回来。
数据流:进去的是 CODEX_HOME、两个时间字符串、消息预览和可选模型供应商。它先调用 create_fake_rollout 生成基础 rollout 文件,然后构造一条 TokenCount 事件,里面故意放了不对称的非零数字,比如总输入、缓存输入、输出、推理输出等。接着它用 rollout_path 找到文件,把原内容读出来,再把这条事件追加进去。出来的是同一个会话编号,磁盘文件也多了一行 token 用量 JSON。
调用关系:它服务于恢复会话和分叉会话相关的测试。它先借 create_fake_rollout 搭好基础档案,再借 rollout_path 找回档案位置,最后自己追加统计记录,方便后续测试验证系统重放历史时没有漏掉或弄反这些数字。
调用图:调用 2 个内部函数(create_fake_rollout, rollout_path);外部调用 5 个(format!, write, json!, TokenCount, to_value)。
create_fake_rollout_with_source113–132 ↗
fn create_fake_rollout_with_source(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
git_info: Option<GitInfo>,
source
作用:创建一个可以指定“会话来源”的假 rollout 文件。会话来源就是这段会话从哪里来的,比如命令行或别的入口,测试有时需要区分这一点。
数据流:进去的是 CODEX_HOME、时间、预览文本、可选模型供应商、可选 Git 信息,以及明确的 SessionSource。它把这些参数转交给更底层的 create_fake_rollout_with_source_and_parent_thread_id,并把父会话编号设为空。出来的是新会话 UUID,磁盘上写好对应的假 rollout 文件。
调用关系:它是带来源版本的便捷入口。create_fake_rollout 会调用它并默认来源为命令行;它自己再把真正的写文件工作交给 create_fake_rollout_with_source_and_parent_thread_id。
调用图:调用 1 个内部函数(create_fake_rollout_with_source_and_parent_thread_id);被 1 处调用(create_fake_rollout)。
create_fake_parented_rollout_with_source136–156 ↗
fn create_fake_parented_rollout_with_source(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
git_info: Option<GitInfo>,
作用:创建一个带“父会话编号”的假 rollout 文件。这个主要用于测试分叉或继承关系:新会话从哪个旧会话接出来。
数据流:进去的是 CODEX_HOME、时间、预览文本、模型供应商、Git 信息、会话来源,以及一个 parent_thread_id。它把父会话编号包成可选值,交给 create_fake_rollout_with_source_and_parent_thread_id。出来的是新生成的会话 UUID,同时文件里的会话元信息会记录它的父会话。
调用关系:它是专门给父子会话测试用的入口。真正生成 UUID、创建目录、写 JSONL 的活儿不在这里做,而是交给 create_fake_rollout_with_source_and_parent_thread_id,保证和普通会话的文件格式一致。
调用图:调用 1 个内部函数(create_fake_rollout_with_source_and_parent_thread_id)。
create_fake_rollout_with_source_and_parent_thread_id159–241 ↗
fn create_fake_rollout_with_source_and_parent_thread_id(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
model_provider: Option<&str>,
git_info: Option
作用:这是创建普通假 rollout 文件的核心函数。它负责真正生成会话编号、创建目录、写入最基本的三行历史记录,并把文件修改时间调成指定时间。
数据流:进去的是根目录、文件名时间、记录时间、预览文本、可选模型供应商、可选 Git 信息、会话来源,以及可选父会话编号。它先生成 UUID,并把它转成 ThreadId(项目里表示会话线程的编号);再用 rollout_path 算文件位置,创建父目录;然后组装 session_meta 这一行,记录会话是谁、何时开始、来源是什么、有没有父会话和 Git 信息;接着写入用户消息和用户事件两行;最后把文件的修改时间设置为 meta_rfc3339 对应的时间。出来的是 UUID 字符串,磁盘上有一个结构完整的 JSONL 文件。
调用关系:它是这个文件里多个公开辅助函数背后的真正工人。create_fake_rollout_with_source 和 create_fake_parented_rollout_with_source 都会把参数交给它;它内部又调用 rollout_path 来统一文件路径规则。这样测试无论创建哪种普通会话,最终格式都保持一致。
调用图:调用 2 个内部函数(rollout_path, from_string);被 2 处调用(create_fake_parented_rollout_with_source, create_fake_rollout_with_source);外部调用 9 个(new, from, new_v4, parse_from_rfc3339, create_dir_all, write, json!, to_value, new)。
create_fake_rollout_with_text_elements243–322 ↗
fn create_fake_rollout_with_text_elements(
codex_home: &Path,
filename_ts: &str,
meta_rfc3339: &str,
preview: &str,
text_elements: Vec<serde_json::Value>,
model_provider: Optio
作用:创建一个带复杂文本元素的假 rollout 文件。普通预览只是一段文字,而这个函数还能把额外的 text_elements 写进用户消息事件里,用来测试更丰富的输入内容。
数据流:进去的是 CODEX_HOME、时间、预览文本、一组 JSON 形式的 text_elements、可选模型供应商和可选 Git 信息。它生成 UUID,按时间创建 sessions/年/月/日 目录,组装会话元信息,再写三行 JSONL:会话元信息、用户消息预览、带 text_elements 和空 local_images 的用户事件。出来的是新会话 UUID,磁盘上多了一个包含这些文本元素的 rollout 文件。
调用关系:它和核心创建函数做的事很像,但为了定制 user_message 里的 text_elements,自己单独组装文件内容。测试如果要检查系统读取富文本、结构化文本片段,通常会用这个函数准备输入档案。
调用图:调用 1 个内部函数(from_string);外部调用 8 个(join, from, new_v4, format!, create_dir_all, write, json!, to_value)。
集成框架门面
此层将共享夹具组装为导出的支持界面,以及集成套件使用的进程级应用服务器框架。
app-server/tests/common/lib.rs源码 ↗
测试 app-server 时,很多东西不能真的去连线上服务,比如真实的登录系统、真实的模型服务器、真实的分析事件服务。这个文件就像测试厨房里的总调料架:它把各个小模块里的测试工具统一摆出来,别的测试只要引用这里,就能快速搭好一个假的运行环境。这里导出的东西包括:模拟认证信息、写入测试配置、启动假的模型响应服务器、制造假的对话记录、生成服务端推送事件等。文件自己真正写的函数只有 to_response,它用来把一个通用的 JSON-RPC 响应转换成测试想检查的具体类型。JSON-RPC 可以理解成一种“用 JSON 包装请求和回答”的通信格式;测试收到回答后,需要把里面的 result 拆出来,变成 Rust 里更好检查的数据结构。
to_response51–55 ↗
fn to_response(response: JSONRPCResponse) -> anyhow::Result<T>
作用:这个函数把测试里拿到的通用 JSON-RPC 回答,转换成调用者真正想要的具体数据类型。这样测试不用手工拆 JSON,也不用每次重复写转换代码。
数据流:进去的是一个 JSONRPCResponse,里面有一个 result 字段,装着服务端返回的结果。函数先用 serde_json::to_value 把 result 变成通用 JSON 值,再用 serde_json::from_value 按调用者指定的类型把它解析出来。成功时出来的是具体类型 T;失败时出来的是错误,告诉测试这个响应不能按预期格式理解。
调用关系:它通常会在测试拿到 app-server 的 JSON-RPC 响应之后使用,位置是在“收到回答”和“断言检查内容”之间。它把具体的 JSON 转换工作交给 serde_json::to_value 和 serde_json::from_value,自己只负责把这两步串起来,让测试代码更短、更清楚。
调用图:外部调用 2 个(from_value, to_value)。
app-server/tests/common/test_app_server.rs源码 ↗
测试 app server 不能只测一两个函数,因为很多问题只会在真实进程、真实输入输出、真实协议来回时出现。这个文件把这些麻烦事包成 TestAppServer:它负责启动服务器、设置测试专用环境变量、把请求写进子进程 stdin(标准输入)、从 stdout(标准输出)读回消息,还会缓存暂时用不上的消息。你可以把它想成一个测试用遥控器:按钮很多,每个按钮对应服务器的一种接口,比如登录、线程、插件、文件系统、远程控制等。它还处理初始化握手、请求编号、等待指定回复、等待通知,以及测试结束时尽量杀掉子进程,避免残留进程让测试变得不稳定。
TestAppServer::wait_for_exit134–136 ↗
async fn wait_for_exit(&mut self) -> std::io::Result<ExitStatus>
作用:等待被测试的服务器进程退出。测试想确认服务器真的结束时会用它。
数据流:进去的是这个 TestAppServer 保存的子进程 → 它调用系统等待接口 → 出来的是进程退出状态,比如正常退出还是被杀掉。
调用关系:它直接等 process 结束,不再发协议消息;通常用于测试服务器生命周期或关闭行为。
调用图:外部调用 1 个(wait)。
TestAppServer::new138–140 ↗
async fn new(codex_home: &Path) -> anyhow::Result<Self>
作用:用默认测试方式启动一个 app server。默认会关掉插件启动任务,让测试更快、更稳定。
数据流:进去的是测试用的 codex_home 目录 → 它补上默认参数 → 交给更底层的启动函数创建子进程和输入输出管道。
调用关系:很多普通集成测试从这里开始;它只是便捷入口,真正启动工作交给 TestAppServer::new_with_env_and_args。
调用图:被 417 处调用(get_auth_status_with_api_key, get_auth_status_with_api_key_no_include_token, get_auth_status_with_api_key_refresh_requested, get_auth_status_with_api_key_when_auth_not_required, login_api_key_rejected_when_forced_chatgpt, get_conversation_summary_by_relative_rollout_path_resolves_from_codex_home, get_conversation_summary_by_thread_id_reads_rollout, initialized_mcp, test_fuzzy_file_search_accepts_cancellation_token, test_fuzzy_file_search_sorts_and_includes_indices (+15 more));外部调用 1 个(new_with_env_and_args)。
TestAppServer::new_without_managed_config142–144 ↗
async fn new_without_managed_config(codex_home: &Path) -> anyhow::Result<Self>
作用:启动服务器时禁用托管配置。这样测试不会被机器上的外部配置影响。
数据流:进去的是 codex_home → 它设置 CODEX_APP_SERVER_DISABLE_MANAGED_CONFIG=1 → 返回一个隔离好的 TestAppServer。
调用关系:需要验证配置、插件、线程策略时常用;它把活交给 TestAppServer::new_with_env。
调用图:被 19 处调用(list_apps_returns_empty_when_workspace_codex_plugins_disabled, experimental_feature_list_marks_apps_and_plugins_disabled_by_workspace_policy, experimental_feature_list_resolves_thread_project_config, skills_list_excludes_plugin_skills_when_workspace_codex_plugins_disabled, thread_fork_tracks_thread_initialized_analytics, thread_goal_get_rejects_unmaterialized_thread, thread_goal_lifecycle_emits_analytics_and_clear_deletes_goal, thread_goal_set_edits_objective_without_resetting_usage, thread_goal_set_persists_resumable_stopped_statuses, thread_goal_set_preserves_budget_limited_same_objective (+9 more));外部调用 1 个(new_with_env)。
TestAppServer::new_without_managed_config_with_env146–153 ↗
async fn new_without_managed_config_with_env(
codex_home: &Path,
env_overrides: &[(&str, Option<&str>)],
) -> anyhow::Result<Self>
作用:在禁用托管配置的同时,再额外改一些环境变量。适合测试不同环境开关下的行为。
数据流:进去的是 codex_home 和一组环境变量覆盖项 → 它先加入禁用托管配置的项,再拼上调用者给的项 → 启动服务器。
调用关系:插件策略相关测试会用它;它整理环境后交给 TestAppServer::new_with_env。
调用图:被 2 处调用(plugin_list_returns_empty_when_workspace_codex_plugins_disabled, plugin_list_reuses_cached_workspace_codex_plugins_setting);外部调用 2 个(new_with_env, vec!)。
TestAppServer::new_with_plugin_startup_tasks155–157 ↗
async fn new_with_plugin_startup_tasks(codex_home: &Path) -> anyhow::Result<Self>
作用:启动服务器时允许插件启动任务运行。用于专门测试启动时插件预热、下载等行为。
数据流:进去的是 codex_home → 它不加禁用插件启动任务的参数 → 创建一个带完整启动流程的服务器。
调用关系:少数需要真实插件启动流程的测试使用;底层仍由 TestAppServer::new_with_env_and_args 完成。
调用图:被 1 处调用(plugin_list_uses_warmed_featured_plugin_ids_cache_on_first_request);外部调用 1 个(new_with_env_and_args)。
TestAppServer::new_with_env_and_plugin_startup_tasks159–164 ↗
async fn new_with_env_and_plugin_startup_tasks(
codex_home: &Path,
env_overrides: &[(&str, Option<&str>)],
) -> anyhow::Result<Self>
作用:带自定义环境变量启动服务器,同时允许插件启动任务运行。
数据流:进去的是目录和环境覆盖项 → 它保留插件启动任务、不加默认禁用参数 → 返回测试客户端。
调用关系:用于测试服务器启动阶段会受环境影响的插件行为;实际启动交给 TestAppServer::new_with_env_and_args。
调用图:被 1 处调用(app_server_startup_sync_downloads_remote_installed_plugin_bundles);外部调用 1 个(new_with_env_and_args)。
TestAppServer::new_with_args166–170 ↗
async fn new_with_args(codex_home: &Path, args: &[&str]) -> anyhow::Result<Self>
作用:用额外命令行参数启动服务器,同时默认关闭插件启动任务。
数据流:进去的是 codex_home 和参数列表 → 它先放入测试默认参数,再拼上额外参数 → 启动子进程。
调用关系:远程控制、插件限制等需要命令行开关的测试会用;最后交给 TestAppServer::new_with_env_and_args。
调用图:被 4 处调用(plugin_install_returns_invalid_request_for_disallowed_product_plugin, listen_off_exits_without_persisted_remote_control_enable, listen_off_honors_persisted_remote_control_enable, listen_off_ignores_persisted_enable_when_disabled_by_requirements);外部调用 2 个(new_with_env_and_args, vec!)。
TestAppServer::new_with_env177–187 ↗
async fn new_with_env(
codex_home: &Path,
env_overrides: &[(&str, Option<&str>)],
) -> anyhow::Result<Self>
作用:用自定义环境变量启动服务器。可以设置某个变量,也可以从子进程环境里移除某个变量。
数据流:进去的是目录和环境覆盖项 → 它套上默认测试参数 → 生成可通信的 TestAppServer。
调用关系:认证、外部环境、配置类测试经常用;它是 TestAppServer::new_with_env_and_args 的便捷包装。
调用图:被 83 处调用(get_auth_status_no_auth, get_auth_status_omits_token_after_permanent_refresh_failure, get_auth_status_omits_token_after_proactive_refresh_failure, get_auth_status_returns_token_after_proactive_refresh_recovery, get_auth_status_with_personal_access_token_omits_token, account_read_refresh_token_is_noop_in_external_mode, external_auth_refresh_error_fails_turn, external_auth_refresh_invalid_access_token_fails_turn, external_auth_refresh_mismatched_workspace_fails_turn, external_auth_refreshes_on_unauthorized (+15 more));外部调用 1 个(new_with_env_and_args)。
TestAppServer::new_with_program_and_env189–201 ↗
async fn new_with_program_and_env(
codex_home: &Path,
program: &Path,
env_overrides: &[(&str, Option<&str>)],
) -> anyhow::Result<Self>
作用:指定要运行的服务器程序路径,并带自定义环境启动。适合测试替代程序或脚本包装器。
数据流:进去的是 codex_home、程序路径和环境覆盖项 → 它加上默认测试参数 → 启动指定程序。
调用关系:create_zsh_test_mcp_process 会用它;真正启动逻辑在 TestAppServer::new_with_program_env_and_args。
调用图:被 1 处调用(create_zsh_test_mcp_process);外部调用 1 个(new_with_program_env_and_args)。
TestAppServer::new_with_env_and_args203–211 ↗
async fn new_with_env_and_args(
codex_home: &Path,
env_overrides: &[(&str, Option<&str>)],
args: &[&str],
) -> anyhow::Result<Self>
作用:找到 codex-app-server 这个测试二进制文件,再用指定环境和参数启动它。
数据流:进去的是目录、环境覆盖、命令行参数 → 它先定位编译出的程序路径 → 交给通用启动函数。
调用关系:多个 new* 入口都会汇到这里;它负责找程序,TestAppServer::new_with_program_env_and_args 负责真正拉起进程。
调用图:外部调用 2 个(new_with_program_env_and_args, cargo_bin)。
TestAppServer::new_with_program_env_and_args213–277 ↗
async fn new_with_program_env_and_args(
codex_home: &Path,
program: &Path,
env_overrides: &[(&str, Option<&str>)],
args: &[&str],
) -> anyhow::Result<Self>
作用:真正创建服务器子进程,并把它包装成可收发消息的测试客户端。
数据流:进去的是程序路径、工作目录、环境变量、参数 → 它配置 stdin/stdout/stderr 管道和测试隔离环境,启动进程,保存输入输出句柄 → 出来是 TestAppServer。
调用关系:这是所有启动入口最后都会调用的核心函数;它还把子进程 stderr 转发出来,方便测试失败时看到服务器报错。
调用图:外部调用 8 个(new, new, join, piped, new, new, eprintln!, spawn)。
TestAppServer::initialize280–292 ↗
async fn initialize(&mut self) -> anyhow::Result<()>
作用:完成默认初始化握手。服务器启动后,测试通常要先调用它,告诉服务器“客户端已准备好”。
数据流:进去的是当前测试客户端 → 它使用默认客户端信息发送 initialize → 确认收到的是响应,然后返回空结果。
调用关系:它调用 TestAppServer::initialize_with_client_info;如果结果不是响应,会触发不可达错误,说明协议流程坏了。
调用图:调用 1 个内部函数(initialize_with_client_info);外部调用 1 个(unreachable!)。
TestAppServer::initialize_with_client_info295–307 ↗
async fn initialize_with_client_info(
&mut self,
client_info: ClientInfo,
) -> anyhow::Result<JSONRPCMessage>
作用:用指定客户端信息初始化服务器。适合测试不同客户端名字、版本会不会影响行为。
数据流:进去的是 ClientInfo → 它附上默认实验能力开关 → 发起初始化。
调用关系:TestAppServer::initialize 会调用它;它再交给 TestAppServer::initialize_with_capabilities。
调用图:调用 1 个内部函数(initialize_with_capabilities);被 1 处调用(initialize);外部调用 1 个(default)。
TestAppServer::initialize_with_capabilities309–319 ↗
async fn initialize_with_capabilities(
&mut self,
client_info: ClientInfo,
capabilities: Option<InitializeCapabilities>,
) -> anyhow::Result<JSONRPCMessage>
作用:用指定客户端信息和能力声明初始化服务器。能力声明就是客户端告诉服务器“我支持哪些功能”。
数据流:进去的是客户端信息和可选能力 → 它组装 InitializeParams → 继续发送初始化请求。
调用关系:它是初始化链条中间层;最终由 TestAppServer::initialize_with_params 发 JSON-RPC 请求。
调用图:调用 1 个内部函数(initialize_with_params);被 1 处调用(initialize_with_client_info)。
TestAppServer::initialize_with_params321–361 ↗
async fn initialize_with_params(
&mut self,
params: InitializeParams,
) -> anyhow::Result<JSONRPCMessage>
作用:发送真正的 initialize 请求,并检查响应编号是否匹配。这样能防止把别的请求回复误当成初始化回复。
数据流:进去的是初始化参数 → 它转成 JSON、发送请求、读取一条消息、核对 request id;成功时再发 initialized 通知 → 出来是响应或错误消息。
调用关系:它调用 TestAppServer::send_request、TestAppServer::read_jsonrpc_message 和 TestAppServer::send_notification,是初始化握手的底层实现。
调用图:调用 3 个内部函数(read_jsonrpc_message, send_notification, send_request);被 1 处调用(initialize_with_capabilities);外部调用 5 个(bail!, Error, Response, Integer, to_value)。
TestAppServer::send_get_auth_status_request364–370 ↗
async fn send_get_auth_status_request(
&mut self,
params: GetAuthStatusParams,
) -> anyhow::Result<i64>
作用:发送读取登录状态的请求。测试用它检查服务器认为用户是否已登录、令牌是否可用。
数据流:进去的是认证状态参数 → 转成 JSON → 发送 getAuthStatus 请求并返回请求编号。
调用关系:它是认证测试的协议按钮;实际写入由 TestAppServer::send_request 完成。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_get_conversation_summary_request373–379 ↗
async fn send_get_conversation_summary_request(
&mut self,
params: GetConversationSummaryParams,
) -> anyhow::Result<i64>
作用:请求读取某个会话摘要。测试用它确认服务器能从线程或 rollout 文件中提取摘要。
数据流:进去的是摘要查询参数 → 转成 JSON → 发 getConversationSummary 并返回请求编号。
调用关系:它只负责发请求;测试随后通常用 read_stream_until_response_message 等回复。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_get_account_rate_limits_request382–385 ↗
async fn send_get_account_rate_limits_request(&mut self) -> anyhow::Result<i64>
作用:请求读取账号限额信息。限额就是账号还能用多少额度、何时重置。
数据流:没有业务参数 → 发送 account/rateLimits/read → 返回请求编号。
调用关系:账号额度测试通过它触发服务器接口;底层走 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_consume_account_rate_limit_reset_credit_request388–397 ↗
async fn send_consume_account_rate_limit_reset_credit_request(
&mut self,
params: ConsumeAccountRateLimitResetCreditParams,
) -> anyhow::Result<i64>
作用:请求消耗一次限额重置信用。测试用它验证相关账号额度流程。
数据流:进去的是消耗参数 → 转成 JSON → 发 account/rateLimitResetCredit/consume,返回请求编号。
调用关系:send_consume_reset_credit 等测试辅助会调用它;真正发送由 TestAppServer::send_request 做。
调用图:调用 1 个内部函数(send_request);被 1 处调用(send_consume_reset_credit);外部调用 1 个(to_value)。
TestAppServer::send_add_credits_nudge_email_request400–407 ↗
async fn send_add_credits_nudge_email_request(
&mut self,
params: SendAddCreditsNudgeEmailParams,
) -> anyhow::Result<i64>
作用:请求发送“加额度提醒”邮件。测试用它确认提醒邮件接口的协议行为。
数据流:进去的是邮件提醒参数 → 转成 JSON → 发送 account/sendAddCreditsNudgeEmail。
调用关系:它是账号邮件相关接口的薄包装;后续由读取函数等待服务器回复。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_get_account_request410–416 ↗
async fn send_get_account_request(
&mut self,
params: GetAccountParams,
) -> anyhow::Result<i64>
作用:请求读取账号信息。测试用它检查账号资料、登录方式或刷新行为。
数据流:进去的是账号读取参数 → 转成 JSON → 发 account/read 并返回编号。
调用关系:认证和账号测试会调用它;公共发送逻辑在 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_chatgpt_auth_tokens_login_request419–432 ↗
async fn send_chatgpt_auth_tokens_login_request(
&mut self,
access_token: String,
chatgpt_account_id: String,
chatgpt_plan_type: Option<String>,
) -> anyhow::Result
作用:用 ChatGPT 的现成认证令牌发起登录。适合测试服务器如何接收外部给来的登录凭证。
数据流:进去的是 access token、账号 id、套餐类型 → 组装成登录参数并转 JSON → 发送 account/login/start。
调用关系:它是登录接口的一种专用按钮;底层还是 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_feedback_upload_request435–441 ↗
async fn send_feedback_upload_request(
&mut self,
params: FeedbackUploadParams,
) -> anyhow::Result<i64>
作用:请求上传反馈。测试用它验证反馈接口是否能接收指定内容。
数据流:进去的是反馈上传参数 → 转成 JSON → 发送 feedback/upload。
调用关系:反馈相关测试调用它;它不等待结果,只返回请求编号给后续读取使用。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_start_request444–450 ↗
async fn send_thread_start_request(
&mut self,
params: ThreadStartParams,
) -> anyhow::Result<i64>
作用:请求创建新线程。这里的线程可以理解为一段对话或工作会话。
数据流:进去的是启动线程参数 → 转成 JSON → 发送 thread/start,返回请求编号。
调用关系:start_thread、start_turn 等测试辅助常用它;它负责触发流程,回复由读取函数拿。
调用图:调用 1 个内部函数(send_request);被 9 处调用(start_thread, start_thread, start_turn, start_plan_mode_turn, start_default_thread, start_thread, start_thread, start_thread, run_environment_selection_case);外部调用 1 个(to_value)。
TestAppServer::send_thread_resume_request453–459 ↗
async fn send_thread_resume_request(
&mut self,
params: ThreadResumeParams,
) -> anyhow::Result<i64>
作用:请求恢复已有线程。用于测试重新打开旧会话的行为。
数据流:进去的是恢复参数 → 转成 JSON → 发 thread/resume。
调用关系:它是线程生命周期测试的一环;发送工作交给 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_fork_request462–468 ↗
async fn send_thread_fork_request(
&mut self,
params: ThreadForkParams,
) -> anyhow::Result<i64>
作用:请求从已有线程分叉出新线程。类似从一个文档版本复制出新分支。
数据流:进去的是分叉参数 → 转 JSON → 发送 thread/fork,返回编号。
调用关系:fork_fake_rollout_thread 会调用它;测试再读取响应确认新线程信息。
调用图:调用 1 个内部函数(send_request);被 1 处调用(fork_fake_rollout_thread);外部调用 1 个(to_value)。
TestAppServer::send_thread_archive_request471–477 ↗
async fn send_thread_archive_request(
&mut self,
params: ThreadArchiveParams,
) -> anyhow::Result<i64>
作用:请求归档线程。归档就是先收起来,不一定删除。
数据流:进去的是归档参数 → 转 JSON → 发送 thread/archive。
调用关系:线程管理测试用它触发归档接口;底层由 TestAppServer::send_request 统一写消息。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_delete_request480–486 ↗
async fn send_thread_delete_request(
&mut self,
params: ThreadDeleteParams,
) -> anyhow::Result<i64>
作用:请求删除线程。测试用它确认删除接口和后续列表变化。
数据流:进去的是删除参数 → 转 JSON → 发 thread/delete。
调用关系:它是线程删除流程的发送入口;回复读取由调用方决定。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_set_name_request489–495 ↗
async fn send_thread_set_name_request(
&mut self,
params: ThreadSetNameParams,
) -> anyhow::Result<i64>
作用:请求修改线程名称。测试用它检查重命名是否被保存和返回。
数据流:进去的是新名称等参数 → 转 JSON → 发送 thread/name/set。
调用关系:线程元数据类测试会调用它;它只负责发协议请求。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_metadata_update_request498–504 ↗
async fn send_thread_metadata_update_request(
&mut self,
params: ThreadMetadataUpdateParams,
) -> anyhow::Result<i64>
作用:请求更新线程元数据。元数据就是名字之外的附加信息。
数据流:进去的是要更新的字段 → 转 JSON → 发 thread/metadata/update。
调用关系:它和线程设置、名称更新接口类似,都是 TestAppServer::send_request 的包装。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_settings_update_request507–513 ↗
async fn send_thread_settings_update_request(
&mut self,
params: ThreadSettingsUpdateParams,
) -> anyhow::Result<i64>
作用:请求修改线程设置。比如某个线程自己的模型、权限或运行选项。
数据流:进去的是设置更新参数 → 转 JSON → 发 thread/settings/update。
调用关系:send_thread_settings_update 等辅助会调用它;结果通过响应读取函数确认。
调用图:调用 1 个内部函数(send_request);被 1 处调用(send_thread_settings_update);外部调用 1 个(to_value)。
TestAppServer::send_thread_unsubscribe_request516–522 ↗
async fn send_thread_unsubscribe_request(
&mut self,
params: ThreadUnsubscribeParams,
) -> anyhow::Result<i64>
作用:请求取消订阅线程消息。用于测试客户端不再接收某线程更新的情况。
数据流:进去的是取消订阅参数 → 转 JSON → 发送 thread/unsubscribe。
调用关系:它触发服务器订阅管理逻辑;发送仍统一走 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_unarchive_request525–531 ↗
async fn send_thread_unarchive_request(
&mut self,
params: ThreadUnarchiveParams,
) -> anyhow::Result<i64>
作用:请求取消归档线程。也就是把收起来的线程重新放回可见列表。
数据流:进去的是取消归档参数 → 转 JSON → 发 thread/unarchive。
调用关系:归档相关测试会配合 archive 使用它;它只负责发请求。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_compact_start_request534–540 ↗
async fn send_thread_compact_start_request(
&mut self,
params: ThreadCompactStartParams,
) -> anyhow::Result<i64>
作用:请求开始压缩线程上下文。压缩就是把长对话整理短,避免内容太多。
数据流:进去的是压缩参数 → 转 JSON → 发 thread/compact/start。
调用关系:上下文压缩测试会调用它,然后等待 started/completed 之类通知。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_shell_command_request543–549 ↗
async fn send_thread_shell_command_request(
&mut self,
params: ThreadShellCommandParams,
) -> anyhow::Result<i64>
作用:请求在线程里执行 shell 命令。shell 命令就是系统命令行里的命令。
数据流:进去的是命令参数 → 转 JSON → 发 thread/shellCommand。
调用关系:命令执行相关测试用它触发旧版线程命令接口;底层发送通道相同。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_rollback_request552–558 ↗
async fn send_thread_rollback_request(
&mut self,
params: ThreadRollbackParams,
) -> anyhow::Result<i64>
作用:请求把线程回滚到较早状态。类似撤销到之前的版本。
数据流:进去的是回滚目标参数 → 转 JSON → 发 thread/rollback。
调用关系:线程历史测试会用它;随后读取响应或通知验证回滚效果。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_list_request561–567 ↗
async fn send_thread_list_request(
&mut self,
params: ThreadListParams,
) -> anyhow::Result<i64>
作用:请求列出线程。测试用它查看当前有哪些会话,以及排序和筛选是否正确。
数据流:进去的是列表参数 → 转 JSON → 发 thread/list。
调用关系:list_threads 等辅助会调用它;它发请求,辅助函数通常负责读响应并解析。
调用图:调用 1 个内部函数(send_request);被 3 处调用(list_threads, list_threads_for_parent, list_threads_with_sort);外部调用 1 个(to_value)。
TestAppServer::send_thread_search_request570–576 ↗
async fn send_thread_search_request(
&mut self,
params: ThreadSearchParams,
) -> anyhow::Result<i64>
作用:请求搜索线程。用于测试按关键字找会话的功能。
数据流:进去的是搜索参数 → 转 JSON → 发 thread/search。
调用关系:它触发搜索接口;结果由后续读取函数拿。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_loaded_list_request579–585 ↗
async fn send_thread_loaded_list_request(
&mut self,
params: ThreadLoadedListParams,
) -> anyhow::Result<i64>
作用:请求列出已经加载到服务器内存里的线程。用于区分磁盘上有和当前已打开的会话。
数据流:进去的是查询参数 → 转 JSON → 发 thread/loaded/list。
调用关系:加载状态相关测试会用它;底层统一走 send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_read_request588–594 ↗
async fn send_thread_read_request(
&mut self,
params: ThreadReadParams,
) -> anyhow::Result<i64>
作用:请求读取一个线程的详细信息。测试用它确认线程内容是否保存正确。
数据流:进去的是线程读取参数 → 转 JSON → 发 thread/read。
调用关系:read_thread_with_turns 会调用它;响应通过 read_stream_until_response_message 拿到。
调用图:调用 1 个内部函数(send_request);被 1 处调用(read_thread_with_turns);外部调用 1 个(to_value)。
TestAppServer::send_thread_turns_list_request597–603 ↗
async fn send_thread_turns_list_request(
&mut self,
params: ThreadTurnsListParams,
) -> anyhow::Result<i64>
作用:请求列出线程里的轮次。轮次可以理解为一次用户提问和系统处理的单元。
数据流:进去的是轮次列表参数 → 转 JSON → 发 thread/turns/list。
调用关系:read_single_turn_items_view 会调用它;它只负责触发服务器查询。
调用图:调用 1 个内部函数(send_request);被 1 处调用(read_single_turn_items_view);外部调用 1 个(to_value)。
TestAppServer::send_thread_turns_items_list_request606–612 ↗
async fn send_thread_turns_items_list_request(
&mut self,
params: ThreadTurnsItemsListParams,
) -> anyhow::Result<i64>
作用:请求列出某些轮次里的具体条目。条目可能是消息、工具调用、结果等。
数据流:进去的是条目列表参数 → 转 JSON → 发 thread/turns/items/list。
调用关系:线程内容细节测试使用它;后续读取响应检查条目。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_list_models_request615–621 ↗
async fn send_list_models_request(
&mut self,
params: ModelListParams,
) -> anyhow::Result<i64>
作用:请求列出可用模型。模型就是服务器能调用的 AI 能力选项。
数据流:进去的是模型列表参数 → 转 JSON → 发 model/list。
调用关系:模型选择和配置测试用它;发送逻辑由 TestAppServer::send_request 提供。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_model_provider_capabilities_read_request624–631 ↗
async fn send_model_provider_capabilities_read_request(
&mut self,
params: ModelProviderCapabilitiesReadParams,
) -> anyhow::Result<i64>
作用:请求读取模型提供方的能力。比如某提供方是否支持某种输入或功能。
数据流:进去的是能力读取参数 → 转 JSON → 发 modelProvider/capabilities/read。
调用关系:模型提供方能力测试会调用它;它返回请求编号供等待响应。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_experimental_feature_list_request634–640 ↗
async fn send_experimental_feature_list_request(
&mut self,
params: ExperimentalFeatureListParams,
) -> anyhow::Result<i64>
作用:请求列出实验功能。实验功能就是还在灰度或试用的开关。
数据流:进去的是列表参数 → 转 JSON → 发 experimentalFeature/list。
调用关系:实验功能策略测试会用它;后续读取结果检查启用状态。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_permission_profile_list_request643–649 ↗
async fn send_permission_profile_list_request(
&mut self,
params: PermissionProfileListParams,
) -> anyhow::Result<i64>
作用:请求列出权限配置档。权限配置档说明服务器允许或限制哪些操作。
数据流:进去的是列表参数 → 转 JSON → 发 permissionProfile/list。
调用关系:权限相关测试通过它检查服务器暴露的可选档位。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_experimental_feature_enablement_set_request652–659 ↗
async fn send_experimental_feature_enablement_set_request(
&mut self,
params: codex_app_server_protocol::ExperimentalFeatureEnablementSetParams,
) -> anyhow::Result<i64>
作用:请求设置实验功能是否启用。测试用它模拟用户打开或关闭某个试验开关。
数据流:进去的是功能开关参数 → 转 JSON → 发 experimentalFeature/enablement/set。
调用关系:set_experimental_feature_enablement 会调用它;底层发送由 send_request 完成。
调用图:调用 1 个内部函数(send_request);被 1 处调用(set_experimental_feature_enablement);外部调用 1 个(to_value)。
TestAppServer::send_remote_control_enable_request662–665 ↗
async fn send_remote_control_enable_request(&mut self) -> anyhow::Result<i64>
作用:请求开启远程控制。远程控制就是允许其他客户端连接控制本服务器。
数据流:没有参数 → 发 remoteControl/enable → 返回请求编号。
调用关系:远程控制测试用它触发持久启用流程;响应由读取函数等待。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_remote_control_ephemeral_enable_request668–674 ↗
async fn send_remote_control_ephemeral_enable_request(&mut self) -> anyhow::Result<i64>
作用:请求临时开启远程控制。临时的意思是只对当前运行有效,不一定写入配置。
数据流:它构造 {ephemeral: true} 参数 → 发 remoteControl/enable → 返回编号。
调用关系:运行期远程控制测试使用它;和普通 enable 共用同一个服务器方法。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(json!)。
TestAppServer::send_remote_control_disable_request677–680 ↗
async fn send_remote_control_disable_request(&mut self) -> anyhow::Result<i64>
作用:请求关闭远程控制。测试用它确认禁用后状态和连接行为。
数据流:没有参数 → 发 remoteControl/disable → 返回请求编号。
调用关系:远程控制关闭测试调用它;底层只负责发 JSON-RPC。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_remote_control_ephemeral_disable_request683–689 ↗
async fn send_remote_control_ephemeral_disable_request(&mut self) -> anyhow::Result<i64>
作用:请求临时关闭远程控制。用于只改变当前运行状态的测试。
数据流:它构造 {ephemeral: true} 参数 → 发 remoteControl/disable → 返回编号。
调用关系:和临时开启配套使用;发送由 TestAppServer::send_request 完成。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(json!)。
TestAppServer::send_remote_control_status_read_request692–695 ↗
async fn send_remote_control_status_read_request(&mut self) -> anyhow::Result<i64>
作用:请求读取远程控制当前状态。测试用它判断远程控制是开、关,还是受策略限制。
数据流:没有参数 → 发 remoteControl/status/read → 返回请求编号。
调用关系:远程控制状态验证时使用;后续读取响应检查状态内容。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_remote_control_pairing_start_request698–705 ↗
async fn send_remote_control_pairing_start_request(
&mut self,
params: RemoteControlPairingStartParams,
) -> anyhow::Result<i64>
作用:请求开始远程控制配对。配对就是让新客户端获得连接授权。
数据流:进去的是配对参数 → 转 JSON → 发 remoteControl/pairing/start。
调用关系:远程控制配对测试用它启动流程,再等待配对状态或响应。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_remote_control_pairing_status_request708–715 ↗
async fn send_remote_control_pairing_status_request(
&mut self,
params: RemoteControlPairingStatusParams,
) -> anyhow::Result<i64>
作用:请求读取远程控制配对状态。用于确认配对是否完成、过期或失败。
数据流:进去的是状态查询参数 → 转 JSON → 发 remoteControl/pairing/status。
调用关系:它通常跟 pairing/start 配套;结果由响应读取函数拿。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_remote_control_clients_list_request718–724 ↗
async fn send_remote_control_clients_list_request(
&mut self,
params: RemoteControlClientsListParams,
) -> anyhow::Result<i64>
作用:请求列出已授权的远程控制客户端。测试用它检查授权列表。
数据流:进去的是列表参数 → 转 JSON → 发 remoteControl/client/list。
调用关系:远程客户端管理测试使用它;公共发送逻辑由 send_request 完成。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_remote_control_clients_revoke_request727–734 ↗
async fn send_remote_control_clients_revoke_request(
&mut self,
params: RemoteControlClientsRevokeParams,
) -> anyhow::Result<i64>
作用:请求撤销某个远程控制客户端的授权。撤销后对方不该再能控制。
数据流:进去的是撤销参数 → 转 JSON → 发 remoteControl/client/revoke。
调用关系:远程控制安全测试会调用它;后续读取响应验证撤销结果。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_apps_list_request737–740 ↗
async fn send_apps_list_request(&mut self, params: AppsListParams) -> anyhow::Result<i64>
作用:请求列出应用。这里的 app 是服务器可识别或可展示的应用条目。
数据流:进去的是应用列表参数 → 转 JSON → 发 app/list。
调用关系:warm_app_directory_cache 会调用它来预热缓存;它是应用目录测试的入口。
调用图:调用 1 个内部函数(send_request);被 1 处调用(warm_app_directory_cache);外部调用 1 个(to_value)。
TestAppServer::send_mcp_resource_read_request743–749 ↗
async fn send_mcp_resource_read_request(
&mut self,
params: McpResourceReadParams,
) -> anyhow::Result<i64>
作用:请求读取 MCP 服务器资源。MCP 是一种让模型连接外部工具和资源的协议。
数据流:进去的是资源读取参数 → 转 JSON → 发 mcpServer/resource/read。
调用关系:MCP 资源测试调用它;响应里应包含资源内容或错误。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_mcp_server_tool_call_request752–758 ↗
async fn send_mcp_server_tool_call_request(
&mut self,
params: McpServerToolCallParams,
) -> anyhow::Result<i64>
作用:请求调用 MCP 服务器工具。工具可以理解为外部服务提供给模型调用的动作。
数据流:进去的是工具调用参数 → 转 JSON → 发 mcpServer/tool/call。
调用关系:MCP 工具测试用它触发实际调用;结果由响应读取函数检查。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_skills_list_request761–767 ↗
async fn send_skills_list_request(
&mut self,
params: SkillsListParams,
) -> anyhow::Result<i64>
作用:请求列出技能。技能是系统或插件提供的一组可用能力。
数据流:进去的是技能列表参数 → 转 JSON → 发 skills/list。
调用关系:技能和插件策略测试会调用它;发送由公共函数完成。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_skills_extra_roots_set_request770–776 ↗
async fn send_skills_extra_roots_set_request(
&mut self,
params: SkillsExtraRootsSetParams,
) -> anyhow::Result<i64>
作用:请求设置技能额外搜索目录。这样服务器能从更多位置找技能。
数据流:进去的是目录设置参数 → 转 JSON → 发 skills/extraRoots/set。
调用关系:技能发现测试使用它;随后通常再调用 skills/list 看变化。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_hooks_list_request779–785 ↗
async fn send_hooks_list_request(
&mut self,
params: HooksListParams,
) -> anyhow::Result<i64>
作用:请求列出 hooks。hook 是在某些时机自动触发的小动作或脚本。
数据流:进去的是 hook 列表参数 → 转 JSON → 发 hooks/list。
调用关系:hook 配置测试用它检查服务器识别到哪些 hook。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_marketplace_add_request788–794 ↗
async fn send_marketplace_add_request(
&mut self,
params: MarketplaceAddParams,
) -> anyhow::Result<i64>
作用:请求从市场添加内容。市场通常指插件或应用的来源列表。
数据流:进去的是添加参数 → 转 JSON → 发 marketplace/add。
调用关系:市场相关测试通过它触发添加流程;后续读响应确认结果。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_marketplace_remove_request797–803 ↗
async fn send_marketplace_remove_request(
&mut self,
params: MarketplaceRemoveParams,
) -> anyhow::Result<i64>
作用:请求从市场移除内容。测试用它验证移除接口和状态变化。
数据流:进去的是移除参数 → 转 JSON → 发 marketplace/remove。
调用关系:它是 marketplace/remove 的测试按钮;底层写消息由 send_request 做。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_marketplace_upgrade_request806–812 ↗
async fn send_marketplace_upgrade_request(
&mut self,
params: MarketplaceUpgradeParams,
) -> anyhow::Result<i64>
作用:请求升级市场内容。用于测试插件或应用从市场更新的流程。
数据流:进去的是升级参数 → 转 JSON → 发 marketplace/upgrade。
调用关系:send_marketplace_upgrade 会调用它;后续测试读取响应和通知。
调用图:调用 1 个内部函数(send_request);被 1 处调用(send_marketplace_upgrade);外部调用 1 个(to_value)。
TestAppServer::send_plugin_install_request815–821 ↗
async fn send_plugin_install_request(
&mut self,
params: PluginInstallParams,
) -> anyhow::Result<i64>
作用:请求安装插件。插件是给服务器增加能力的扩展包。
数据流:进去的是安装参数 → 转 JSON → 发 plugin/install。
调用关系:send_remote_plugin_install_request 会调用它;安装结果通过响应或通知判断。
调用图:调用 1 个内部函数(send_request);被 1 处调用(send_remote_plugin_install_request);外部调用 1 个(to_value)。
TestAppServer::send_plugin_uninstall_request824–830 ↗
async fn send_plugin_uninstall_request(
&mut self,
params: PluginUninstallParams,
) -> anyhow::Result<i64>
作用:请求卸载插件。测试用它确认插件被移除后不再出现在列表或能力中。
数据流:进去的是卸载参数 → 转 JSON → 发 plugin/uninstall。
调用关系:插件生命周期测试会用它;发送通道仍是公共 send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_plugin_list_request833–839 ↗
async fn send_plugin_list_request(
&mut self,
params: PluginListParams,
) -> anyhow::Result<i64>
作用:请求列出插件。测试用它查看可用、已安装或被策略过滤的插件。
数据流:进去的是插件列表参数 → 转 JSON → 发 plugin/list。
调用关系:插件列表和缓存测试常用它;响应由调用方读取。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_plugin_installed_request842–848 ↗
async fn send_plugin_installed_request(
&mut self,
params: PluginInstalledParams,
) -> anyhow::Result<i64>
作用:请求列出已安装插件。和插件市场列表不同,它关注本地已经装上的内容。
数据流:进去的是查询参数 → 转 JSON → 发 plugin/installed。
调用关系:插件安装状态测试使用它;它只是发送协议请求。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_plugin_read_request851–857 ↗
async fn send_plugin_read_request(
&mut self,
params: PluginReadParams,
) -> anyhow::Result<i64>
作用:请求读取某个插件详情。测试用它确认插件元信息和内容是否正确。
数据流:进去的是插件读取参数 → 转 JSON → 发 plugin/read。
调用关系:插件详情测试通过它向服务器要单个插件数据。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_plugin_skill_read_request860–866 ↗
async fn send_plugin_skill_read_request(
&mut self,
params: PluginSkillReadParams,
) -> anyhow::Result<i64>
作用:请求读取插件里的某个技能。用于验证插件技能内容能被正确暴露。
数据流:进去的是插件技能读取参数 → 转 JSON → 发 plugin/skill/read。
调用关系:插件技能测试会调用它;响应里应有技能详情或错误。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_list_mcp_server_status_request869–875 ↗
async fn send_list_mcp_server_status_request(
&mut self,
params: ListMcpServerStatusParams,
) -> anyhow::Result<i64>
作用:请求列出 MCP 服务器状态。测试用它看外部工具服务器是否启动、可用或报错。
数据流:进去的是状态列表参数 → 转 JSON → 发 mcpServerStatus/list。
调用关系:mcp_server_names 会调用它;它触发状态查询,后续解析响应。
调用图:调用 1 个内部函数(send_request);被 1 处调用(mcp_server_names);外部调用 1 个(to_value)。
TestAppServer::send_raw_request878–884 ↗
async fn send_raw_request(
&mut self,
method: &str,
params: Option<serde_json::Value>,
) -> anyhow::Result<i64>
作用:发送原始方法名和原始参数。适合故意发错格式,测试协议校验是否严格。
数据流:进去的是 method 字符串和 JSON 参数 → 不做类型包装 → 直接发送并返回请求编号。
调用关系:它绕过那些强类型 send_* 包装;底层仍调用 TestAppServer::send_request。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_list_collaboration_modes_request886–892 ↗
async fn send_list_collaboration_modes_request(
&mut self,
params: CollaborationModeListParams,
) -> anyhow::Result<i64>
作用:请求列出协作模式。协作模式表示服务器支持哪些人机协同工作方式。
数据流:进去的是列表参数 → 转 JSON → 发 collaborationMode/list。
调用关系:协作模式测试使用它;它只负责发请求。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_mock_experimental_method_request895–901 ↗
async fn send_mock_experimental_method_request(
&mut self,
params: MockExperimentalMethodParams,
) -> anyhow::Result<i64>
作用:请求一个测试用的实验方法。它主要用于验证实验 API 的开关和协议路径。
数据流:进去的是 mock 参数 → 转 JSON → 发 mock/experimentalMethod。
调用关系:实验 API 测试会调用它;发送通过公共 send_request。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_memory_mode_set_request904–910 ↗
async fn send_thread_memory_mode_set_request(
&mut self,
params: ThreadMemoryModeSetParams,
) -> anyhow::Result<i64>
作用:请求设置线程记忆模式。记忆模式决定线程如何保留或使用上下文。
数据流:进去的是记忆模式参数 → 转 JSON → 发 thread/memoryMode/set。
调用关系:v2 实验线程测试使用它;它发请求,调用方等响应。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_turn_start_request913–919 ↗
async fn send_turn_start_request(
&mut self,
params: TurnStartParams,
) -> anyhow::Result<i64>
作用:请求开始一个 turn。turn 是一次具体的模型处理或对话推进。
数据流:进去的是 turn 启动参数 → 转 JSON → 发 turn/start。
调用关系:send_turn_and_wait、start_turn 等测试辅助会调用它,然后等待完成通知。
调用图:调用 1 个内部函数(send_request);被 6 处调用(send_turn_and_wait, start_turn, start_plan_mode_turn, materialize_thread_rollout, start_text_turn, run_environment_selection_case);外部调用 1 个(to_value)。
TestAppServer::send_thread_inject_items_request922–928 ↗
async fn send_thread_inject_items_request(
&mut self,
params: ThreadInjectItemsParams,
) -> anyhow::Result<i64>
作用:请求往线程里注入条目。测试用它人为塞入消息或事件,构造特定场景。
数据流:进去的是注入参数 → 转 JSON → 发 thread/inject_items。
调用关系:线程内容构造测试会用它;它是 v2 接口的发送包装。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_command_exec_request931–937 ↗
async fn send_command_exec_request(
&mut self,
params: CommandExecParams,
) -> anyhow::Result<i64>
作用:请求执行命令。这个 v2 接口用于启动一条命令执行任务。
数据流:进去的是命令执行参数 → 转 JSON → 发 command/exec。
调用关系:命令执行测试通过它启动命令,之后读取增量通知或响应。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_process_spawn_request940–946 ↗
async fn send_process_spawn_request(
&mut self,
params: ProcessSpawnParams,
) -> anyhow::Result<i64>
作用:请求启动一个进程。进程就是操作系统里正在运行的程序实例。
数据流:进去的是进程启动参数 → 转 JSON → 发 process/spawn。
调用关系:进程控制测试用它启动程序;写入、调整大小、杀掉进程的接口会跟它配套。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_process_write_stdin_request949–955 ↗
async fn send_process_write_stdin_request(
&mut self,
params: ProcessWriteStdinParams,
) -> anyhow::Result<i64>
作用:请求向某个进程的标准输入写数据。相当于测试里往命令行程序里打字。
数据流:进去的是进程 id 和输入内容等参数 → 转 JSON → 发 process/writeStdin。
调用关系:它通常在 process/spawn 后使用;底层仍走统一发送函数。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_process_resize_pty_request958–964 ↗
async fn send_process_resize_pty_request(
&mut self,
params: ProcessResizePtyParams,
) -> anyhow::Result<i64>
作用:请求调整伪终端大小。伪终端可以理解为给命令行程序用的虚拟窗口。
数据流:进去的是进程和行列大小参数 → 转 JSON → 发 process/resizePty。
调用关系:终端交互测试会在进程运行时调用它,确认服务器转发窗口变化。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_process_kill_request967–973 ↗
async fn send_process_kill_request(
&mut self,
params: ProcessKillParams,
) -> anyhow::Result<i64>
作用:请求杀掉进程。测试用它清理或验证强制停止行为。
数据流:进去的是要终止的进程参数 → 转 JSON → 发 process/kill。
调用关系:它和 process/spawn 配套;发送后测试通常等退出通知。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_command_exec_write_request976–982 ↗
async fn send_command_exec_write_request(
&mut self,
params: CommandExecWriteParams,
) -> anyhow::Result<i64>
作用:请求向 command/exec 启动的命令写输入。用于交互式命令测试。
数据流:进去的是命令执行会话和写入内容 → 转 JSON → 发 command/exec/write。
调用关系:它服务于 command/exec 流程;公共 send_request 负责实际发送。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_command_exec_resize_request985–991 ↗
async fn send_command_exec_resize_request(
&mut self,
params: CommandExecResizeParams,
) -> anyhow::Result<i64>
作用:请求调整 command/exec 会话的终端大小。
数据流:进去的是会话 id 和新尺寸 → 转 JSON → 发 command/exec/resize。
调用关系:命令终端测试会在执行中调用它;结果通过服务器通知或响应验证。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_command_exec_terminate_request994–1000 ↗
async fn send_command_exec_terminate_request(
&mut self,
params: CommandExecTerminateParams,
) -> anyhow::Result<i64>
作用:请求终止 command/exec 会话。用于测试命令停止和清理。
数据流:进去的是要终止的执行参数 → 转 JSON → 发 command/exec/terminate。
调用关系:它和 command/exec 启动接口配套;后续通常等待完成或退出消息。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_turn_interrupt_request1003–1009 ↗
async fn send_turn_interrupt_request(
&mut self,
params: TurnInterruptParams,
) -> anyhow::Result<i64>
作用:请求中断正在运行的 turn。也就是让当前模型处理停下来。
数据流:进去的是线程 id 和 turn id 等参数 → 转 JSON → 发 turn/interrupt。
调用关系:TestAppServer::interrupt_turn_and_wait_for_aborted 会调用它,并继续等待响应和完成通知。
调用图:调用 1 个内部函数(send_request);被 1 处调用(interrupt_turn_and_wait_for_aborted);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_start_request1012–1018 ↗
async fn send_thread_realtime_start_request(
&mut self,
params: ThreadRealtimeStartParams,
) -> anyhow::Result<i64>
作用:请求开启线程实时会话。实时会话通常用于音频、语音或流式交互。
数据流:进去的是实时启动参数 → 转 JSON → 发 thread/realtime/start。
调用关系:start_webrtc_realtime_with_codex_responses_as_items 会调用它;后续追加音频或文本。
调用图:调用 1 个内部函数(send_request);被 1 处调用(start_webrtc_realtime_with_codex_responses_as_items);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_append_audio_request1021–1028 ↗
async fn send_thread_realtime_append_audio_request(
&mut self,
params: ThreadRealtimeAppendAudioParams,
) -> anyhow::Result<i64>
作用:向实时线程追加音频数据。测试用它模拟用户继续说话。
数据流:进去的是音频片段参数 → 转 JSON → 发 thread/realtime/appendAudio。
调用关系:append_audio 辅助会调用它;它属于实时会话的数据输入接口。
调用图:调用 1 个内部函数(send_request);被 1 处调用(append_audio);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_append_text_request1031–1038 ↗
async fn send_thread_realtime_append_text_request(
&mut self,
params: ThreadRealtimeAppendTextParams,
) -> anyhow::Result<i64>
作用:向实时线程追加文本。用于模拟实时会话里的文字输入。
数据流:进去的是文本追加参数 → 转 JSON → 发 thread/realtime/appendText。
调用关系:append_text 会调用它;它和实时 start/stop 接口配套。
调用图:调用 1 个内部函数(send_request);被 1 处调用(append_text);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_append_speech_request1041–1048 ↗
async fn send_thread_realtime_append_speech_request(
&mut self,
params: ThreadRealtimeAppendSpeechParams,
) -> anyhow::Result<i64>
作用:向实时线程追加语音识别后的 speech 数据。用于测试语音输入通道。
数据流:进去的是语音片段参数 → 转 JSON → 发 thread/realtime/appendSpeech。
调用关系:append_speech 会调用它;实时语音测试用它推进输入。
调用图:调用 1 个内部函数(send_request);被 1 处调用(append_speech);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_stop_request1051–1057 ↗
async fn send_thread_realtime_stop_request(
&mut self,
params: ThreadRealtimeStopParams,
) -> anyhow::Result<i64>
作用:请求停止实时线程会话。测试用它结束音频或实时交互流程。
数据流:进去的是停止参数 → 转 JSON → 发 thread/realtime/stop。
调用关系:它通常在 realtime/start 之后调用;后续读取响应确认停止。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_thread_realtime_list_voices_request1059–1066 ↗
async fn send_thread_realtime_list_voices_request(
&mut self,
params: ThreadRealtimeListVoicesParams,
) -> anyhow::Result<i64>
作用:请求列出实时语音可用的声音。测试用它检查语音功能暴露了哪些 voice。
数据流:进去的是查询参数 → 转 JSON → 发 thread/realtime/listVoices。
调用关系:实时语音测试会调用它;发送后由响应读取函数拿列表。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::interrupt_turn_and_wait_for_aborted1078–1124 ↗
async fn interrupt_turn_and_wait_for_aborted(
&mut self,
thread_id: String,
turn_id: String,
read_timeout: std::time::Duration,
) -> anyhow::Result<()>
作用:安全中断一个还在运行的 turn,并等到服务器发出终止信号。这样测试结束时不会留下后台任务乱跑。
数据流:进去的是 thread_id、turn_id 和超时时间 → 它发送 turn/interrupt,等待对应响应,再等待 turn/completed 通知;如果通知已经缓存在本地,也算清理完成 → 成功返回空结果,失败返回带上下文的错误。
调用关系:它串起 TestAppServer::send_turn_interrupt_request、read_stream_until_response_message、read_stream_until_notification_message 和 pending_turn_completed_notification,专门解决测试 teardown 前的竞态问题。
调用图:调用 4 个内部函数(pending_turn_completed_notification, read_stream_until_notification_message, read_stream_until_response_message, send_turn_interrupt_request);外部调用 2 个(Integer, timeout)。
TestAppServer::send_turn_steer_request1127–1133 ↗
async fn send_turn_steer_request(
&mut self,
params: TurnSteerParams,
) -> anyhow::Result<i64>
作用:请求引导正在进行的 turn。可以理解为在模型处理中途追加方向或指令。
数据流:进去的是 steer 参数 → 转 JSON → 发 turn/steer。
调用关系:turn 控制测试会调用它;它只负责把指令发给服务器。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_review_start_request1136–1142 ↗
async fn send_review_start_request(
&mut self,
params: ReviewStartParams,
) -> anyhow::Result<i64>
作用:请求开始 review。review 通常是让系统检查或评审某些内容。
数据流:进去的是 review 启动参数 → 转 JSON → 发 review/start。
调用关系:评审流程测试通过它启动服务器端 review。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_windows_sandbox_setup_start_request1144–1150 ↗
async fn send_windows_sandbox_setup_start_request(
&mut self,
params: WindowsSandboxSetupStartParams,
) -> anyhow::Result<i64>
作用:请求开始 Windows 沙箱设置。沙箱是隔离运行环境,避免影响真实系统。
数据流:进去的是沙箱设置参数 → 转 JSON → 发 windowsSandbox/setupStart。
调用关系:Windows 沙箱相关测试会调用它;结果通过响应或通知观察。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_config_read_request1152–1158 ↗
async fn send_config_read_request(
&mut self,
params: ConfigReadParams,
) -> anyhow::Result<i64>
作用:请求读取配置。配置就是影响服务器行为的一组设置。
数据流:进去的是读取参数 → 转 JSON → 发 config/read。
调用关系:read_config 会调用它;配置测试通过响应确认当前值。
调用图:调用 1 个内部函数(send_request);被 1 处调用(read_config);外部调用 1 个(to_value)。
TestAppServer::send_config_requirements_read_request1160–1163 ↗
async fn send_config_requirements_read_request(&mut self) -> anyhow::Result<i64>
作用:请求读取配置要求。配置要求说明哪些设置被策略强制或限制。
数据流:没有参数 → 发 configRequirements/read → 返回请求编号。
调用关系:策略和配置约束测试会用它;底层走 send_request。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_config_value_write_request1165–1171 ↗
async fn send_config_value_write_request(
&mut self,
params: ConfigValueWriteParams,
) -> anyhow::Result<i64>
作用:请求写入单个配置值。测试用它模拟用户改一个设置。
数据流:进去的是配置键和值 → 转 JSON → 发 config/value/write。
调用关系:配置写入测试会调用它;后续读取配置验证变化。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_config_batch_write_request1173–1179 ↗
async fn send_config_batch_write_request(
&mut self,
params: ConfigBatchWriteParams,
) -> anyhow::Result<i64>
作用:请求批量写入多个配置值。比一个个写更适合测试成组配置变更。
数据流:进去的是一批配置变更 → 转 JSON → 发 config/batchWrite。
调用关系:批量配置测试使用它;发送后等服务器响应确认。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_read_file_request1181–1187 ↗
async fn send_fs_read_file_request(
&mut self,
params: FsReadFileParams,
) -> anyhow::Result<i64>
作用:请求读取文件内容。fs 是 file system,意思是文件系统。
数据流:进去的是文件路径等参数 → 转 JSON → 发 fs/readFile。
调用关系:文件系统接口测试用它读测试目录里的文件。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_write_file_request1189–1195 ↗
async fn send_fs_write_file_request(
&mut self,
params: FsWriteFileParams,
) -> anyhow::Result<i64>
作用:请求写文件。测试用它确认服务器能按协议创建或修改文件。
数据流:进去的是路径和内容参数 → 转 JSON → 发 fs/writeFile。
调用关系:文件写入测试调用它;随后可能读取文件或响应验证。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_create_directory_request1197–1203 ↗
async fn send_fs_create_directory_request(
&mut self,
params: FsCreateDirectoryParams,
) -> anyhow::Result<i64>
作用:请求创建目录。用于测试文件系统目录操作。
数据流:进去的是目录路径参数 → 转 JSON → 发 fs/createDirectory。
调用关系:目录操作测试会调用它;公共发送函数负责写消息。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_get_metadata_request1205–1211 ↗
async fn send_fs_get_metadata_request(
&mut self,
params: FsGetMetadataParams,
) -> anyhow::Result<i64>
作用:请求读取文件或目录的元数据。元数据包括是否目录、大小、修改时间等。
数据流:进去的是路径参数 → 转 JSON → 发 fs/getMetadata。
调用关系:文件信息测试用它检查服务器返回的文件属性。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_read_directory_request1213–1219 ↗
async fn send_fs_read_directory_request(
&mut self,
params: FsReadDirectoryParams,
) -> anyhow::Result<i64>
作用:请求读取目录内容。也就是列出一个文件夹里有哪些东西。
数据流:进去的是目录路径参数 → 转 JSON → 发 fs/readDirectory。
调用关系:目录列表测试通过它触发服务器文件枚举。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_remove_request1221–1224 ↗
async fn send_fs_remove_request(&mut self, params: FsRemoveParams) -> anyhow::Result<i64>
作用:请求删除文件或目录。测试用它确认删除接口是否按预期工作。
数据流:进去的是删除参数 → 转 JSON → 发 fs/remove。
调用关系:文件清理和删除测试会调用它;发送逻辑由 send_request 提供。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_copy_request1226–1229 ↗
async fn send_fs_copy_request(&mut self, params: FsCopyParams) -> anyhow::Result<i64>
作用:请求复制文件或目录。测试用它验证复制行为。
数据流:进去的是来源和目标参数 → 转 JSON → 发 fs/copy。
调用关系:文件系统复制测试调用它;后续读取目标确认结果。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_watch_request1231–1234 ↗
async fn send_fs_watch_request(&mut self, params: FsWatchParams) -> anyhow::Result<i64>
作用:请求监听文件变化。监听就是服务器在文件变动时发通知。
数据流:进去的是监听路径等参数 → 转 JSON → 发 fs/watch。
调用关系:文件监听测试用它启动 watch,之后等待通知。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fs_unwatch_request1236–1242 ↗
async fn send_fs_unwatch_request(
&mut self,
params: FsUnwatchParams,
) -> anyhow::Result<i64>
作用:请求取消文件监听。用于测试停止接收文件变化通知。
数据流:进去的是取消监听参数 → 转 JSON → 发 fs/unwatch。
调用关系:它通常跟 fs/watch 配套;发送后测试确认不再收到相关通知。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_logout_account_request1245–1247 ↗
async fn send_logout_account_request(&mut self) -> anyhow::Result<i64>
作用:请求账号登出。测试用它确认登录状态会被清掉。
数据流:没有参数 → 发 account/logout → 返回请求编号。
调用关系:登录流程测试会在清理或状态验证时调用它。
调用图:调用 1 个内部函数(send_request)。
TestAppServer::send_login_account_api_key_request1250–1259 ↗
async fn send_login_account_api_key_request(
&mut self,
api_key: &str,
) -> anyhow::Result<i64>
作用:用 API key 发起登录。API key 是一串密钥,用来证明用户身份。
数据流:进去的是 api_key 字符串 → 它构造 JSON 参数 {type: apiKey, apiKey} → 发 account/login/start。
调用关系:login_with_api_key_via_request、login_with_api_key 等辅助会调用它。
调用图:调用 1 个内部函数(send_request);被 4 处调用(login_with_api_key_via_request, login_with_api_key, login_with_api_key, login_with_api_key);外部调用 1 个(json!)。
TestAppServer::send_login_account_chatgpt_request1262–1267 ↗
async fn send_login_account_chatgpt_request(&mut self) -> anyhow::Result<i64>
作用:发起普通 ChatGPT 登录流程。测试用它触发需要浏览器或外部认证的登录路径。
数据流:没有额外输入 → 构造 {type: chatgpt} → 发 account/login/start。
调用关系:认证流程测试用它启动 ChatGPT 登录;后续可能读取服务器请求或响应。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(json!)。
TestAppServer::send_login_account_chatgpt_device_code_request1270–1275 ↗
async fn send_login_account_chatgpt_device_code_request(&mut self) -> anyhow::Result<i64>
作用:发起 ChatGPT 设备码登录。设备码登录通常让用户在另一个页面输入代码完成认证。
数据流:没有额外输入 → 构造 {type: chatgptDeviceCode} → 发 account/login/start。
调用关系:设备码登录测试调用它;结果通过响应读取。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(json!)。
TestAppServer::send_cancel_login_account_request1278–1284 ↗
async fn send_cancel_login_account_request(
&mut self,
params: CancelLoginAccountParams,
) -> anyhow::Result<i64>
作用:请求取消正在进行的登录。用于测试用户中途放弃登录时服务器如何收尾。
数据流:进去的是取消登录参数 → 转 JSON → 发 account/login/cancel。
调用关系:登录取消测试会调用它;底层发送由 send_request 完成。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(to_value)。
TestAppServer::send_fuzzy_file_search_request1287–1301 ↗
async fn send_fuzzy_file_search_request(
&mut self,
query: &str,
roots: Vec<String>,
cancellation_token: Option<String>,
) -> anyhow::Result<i64>
作用:请求模糊搜索文件。模糊搜索就是不必完全输入文件名,也能找相近匹配。
数据流:进去的是查询词、搜索根目录、可选取消令牌 → 组装 JSON → 发 fuzzyFileSearch。
调用关系:文件搜索测试用它发一次性搜索请求;取消令牌用于测试中断搜索。
调用图:调用 1 个内部函数(send_request);外部调用 1 个(json!)。
TestAppServer::send_fuzzy_file_search_session_start_request1303–1314 ↗
async fn send_fuzzy_file_search_session_start_request(
&mut self,
session_id: &str,
roots: Vec<String>,
) -> anyhow::Result<i64>
作用:请求开始一个模糊文件搜索会话。会话式搜索适合边输入边更新结果。
数据流:进去的是 session_id 和根目录 → 组装 JSON → 发 fuzzyFileSearch/sessionStart。
调用关系:TestAppServer::start_fuzzy_file_search_session 会调用它并立刻等响应。
调用图:调用 1 个内部函数(send_request);被 1 处调用(start_fuzzy_file_search_session);外部调用 1 个(json!)。
TestAppServer::start_fuzzy_file_search_session1316–1326 ↗
async fn start_fuzzy_file_search_session(
&mut self,
session_id: &str,
roots: Vec<String>,
) -> anyhow::Result<JSONRPCResponse>
作用:开始模糊搜索会话,并直接等到服务器响应。比只发送请求更省事。
数据流:进去的是 session_id 和根目录 → 它发送 sessionStart,拿到请求编号,再等待对应响应 → 返回 JSONRPCResponse。
调用关系:它串起 send_fuzzy_file_search_session_start_request 和 read_stream_until_response_message。
调用图:调用 2 个内部函数(read_stream_until_response_message, send_fuzzy_file_search_session_start_request);外部调用 1 个(Integer)。
TestAppServer::send_fuzzy_file_search_session_update_request1328–1339 ↗
async fn send_fuzzy_file_search_session_update_request(
&mut self,
session_id: &str,
query: &str,
) -> anyhow::Result<i64>
作用:请求更新模糊搜索会话的查询词。模拟用户继续打字。
数据流:进去的是 session_id 和 query → 组装 JSON → 发 fuzzyFileSearch/sessionUpdate。
调用关系:update_fuzzy_file_search_session 和缺失会话错误测试会调用它。
调用图:调用 1 个内部函数(send_request);被 2 处调用(update_fuzzy_file_search_session, assert_update_request_fails_for_missing_session);外部调用 1 个(json!)。
TestAppServer::update_fuzzy_file_search_session1341–1351 ↗
async fn update_fuzzy_file_search_session(
&mut self,
session_id: &str,
query: &str,
) -> anyhow::Result<JSONRPCResponse>
作用:更新模糊搜索会话,并直接等到对应响应。
数据流:进去的是 session_id 和 query → 发送 update 请求 → 等待同一请求编号的响应并返回。
调用关系:它把发送和等待封装在一起,内部调用 send_fuzzy_file_search_session_update_request。
调用图:调用 2 个内部函数(read_stream_until_response_message, send_fuzzy_file_search_session_update_request);外部调用 1 个(Integer)。
TestAppServer::send_fuzzy_file_search_session_stop_request1353–1362 ↗
async fn send_fuzzy_file_search_session_stop_request(
&mut self,
session_id: &str,
) -> anyhow::Result<i64>
作用:请求停止模糊搜索会话。用于测试搜索结束和资源清理。
数据流:进去的是 session_id → 组装 JSON → 发 fuzzyFileSearch/sessionStop。
调用关系:TestAppServer::stop_fuzzy_file_search_session 会调用它。
调用图:调用 1 个内部函数(send_request);被 1 处调用(stop_fuzzy_file_search_session);外部调用 1 个(json!)。
TestAppServer::stop_fuzzy_file_search_session1364–1373 ↗
async fn stop_fuzzy_file_search_session(
&mut self,
session_id: &str,
) -> anyhow::Result<JSONRPCResponse>
作用:停止模糊搜索会话,并直接等到服务器确认。
数据流:进去的是 session_id → 发送 stop 请求 → 等待对应响应 → 返回 JSONRPCResponse。
调用关系:它串起 send_fuzzy_file_search_session_stop_request 和 read_stream_until_response_message。
调用图:调用 2 个内部函数(read_stream_until_response_message, send_fuzzy_file_search_session_stop_request);外部调用 1 个(Integer)。
TestAppServer::send_request1375–1390 ↗
async fn send_request(
&mut self,
method: &str,
params: Option<serde_json::Value>,
) -> anyhow::Result<i64>
作用:发送一条 JSON-RPC 请求,并自动分配请求编号。请求编号像取餐号,用来把回复和请求对上。
数据流:进去的是方法名和可选 JSON 参数 → 它递增 next_request_id,组装 JSONRPCRequest,写到服务器 stdin → 返回这次请求编号。
调用关系:几乎所有 send_* 请求函数最终都会调用它;它再把实际写入交给 TestAppServer::send_jsonrpc_message。
调用图:调用 1 个内部函数(send_jsonrpc_message);被 104 处调用(initialize_with_params, send_add_credits_nudge_email_request, send_apps_list_request, send_cancel_login_account_request, send_chatgpt_auth_tokens_login_request, send_command_exec_request, send_command_exec_resize_request, send_command_exec_terminate_request, send_command_exec_write_request, send_config_batch_write_request (+15 more));外部调用 3 个(fetch_add, Request, Integer)。
TestAppServer::send_response1392–1399 ↗
async fn send_response(
&mut self,
id: RequestId,
result: serde_json::Value,
) -> anyhow::Result<()>
作用:向服务器发送一条响应。用于服务器反过来请求测试客户端时,测试客户端给答复。
数据流:进去的是请求 id 和结果 JSON → 组装 JSONRPCResponse → 写入 stdin。
调用关系:respond_to_refresh_request 会调用它;底层由 TestAppServer::send_jsonrpc_message 发送。
调用图:调用 1 个内部函数(send_jsonrpc_message);被 1 处调用(respond_to_refresh_request);外部调用 1 个(Response)。
TestAppServer::send_error1401–1408 ↗
async fn send_error(
&mut self,
id: RequestId,
error: JSONRPCErrorError,
) -> anyhow::Result<()>
作用:向服务器发送一条错误响应。用于模拟客户端拒绝或处理失败。
数据流:进去的是请求 id 和错误内容 → 组装 JSONRPCError → 写入服务器 stdin。
调用关系:它和 send_response 对应,都是服务器主动请求客户端时的回复方式。
调用图:调用 1 个内部函数(send_jsonrpc_message);外部调用 1 个(Error)。
TestAppServer::send_notification1410–1424 ↗
async fn send_notification(
&mut self,
notification: ClientNotification,
) -> anyhow::Result<()>
作用:向服务器发送通知。通知是不需要对方回复的消息。
数据流:进去的是 ClientNotification → 转成 JSON,取出 method 和 params → 组装 JSONRPCNotification 并发送。
调用关系:初始化成功后 initialize_with_params 会用它发送 Initialized 通知;实际写入由 send_jsonrpc_message 完成。
调用图:调用 1 个内部函数(send_jsonrpc_message);被 1 处调用(initialize_with_params);外部调用 2 个(Notification, to_value)。
TestAppServer::send_jsonrpc_message1426–1436 ↗
async fn send_jsonrpc_message(&mut self, message: JSONRPCMessage) -> anyhow::Result<()>
作用:把一条 JSON-RPC 消息真正写进服务器标准输入。所有请求、响应、通知最终都从这里出去。
数据流:进去的是 JSONRPCMessage → 序列化成一行 JSON,加换行,写入 stdin 并 flush → 成功返回空结果;如果 stdin 已关就报错。
调用关系:send_request、send_response、send_error、send_notification 都调用它;它是出站通信的最底层。
调用图:被 4 处调用(send_error, send_notification, send_request, send_response);外部调用 3 个(bail!, eprintln!, to_string)。
TestAppServer::read_jsonrpc_message1438–1444 ↗
async fn read_jsonrpc_message(&mut self) -> anyhow::Result<JSONRPCMessage>
作用:从服务器标准输出读一行,并解析成 JSON-RPC 消息。
数据流:进去的是当前 stdout reader → 读一行文本,按 JSONRPCMessage 反序列化 → 返回结构化消息。
调用关系:initialize_with_params 和 read_stream_until_message 会调用它;它是入站通信的最底层。
调用图:被 2 处调用(initialize_with_params, read_stream_until_message);外部调用 3 个(read_line, new, eprintln!)。
TestAppServer::read_stream_until_request_message1446–1459 ↗
async fn read_stream_until_request_message(&mut self) -> anyhow::Result<ServerRequest>
作用:一直读服务器输出,直到遇到服务器发来的请求。用于测试服务器需要客户端配合的场景。
数据流:没有额外输入 → 它读取消息流,跳过并缓存不匹配消息 → 找到 Request 后转成 ServerRequest 返回。
调用关系:respond_to_refresh_request 会调用它;内部依赖 read_stream_until_message 做筛选和缓存。
调用图:调用 1 个内部函数(read_stream_until_message);被 1 处调用(respond_to_refresh_request);外部调用 2 个(eprintln!, unreachable!)。
TestAppServer::read_stream_until_response_message1461–1477 ↗
async fn read_stream_until_response_message(
&mut self,
request_id: RequestId,
) -> anyhow::Result<JSONRPCResponse>
作用:一直读消息,直到找到指定请求编号的成功响应。这样不会被别的通知或响应打乱。
数据流:进去的是 request_id → 它在缓存和新消息中寻找同 id 消息 → 找到 Response 后返回。
调用关系:大量测试辅助用它等待请求结果;内部用 message_request_id 匹配编号,筛选逻辑在 read_stream_until_message。
调用图:调用 1 个内部函数(read_stream_until_message);被 37 处调用(interrupt_turn_and_wait_for_aborted, start_fuzzy_file_search_session, stop_fuzzy_file_search_session, update_fuzzy_file_search_session, login_with_api_key_via_request, fork_fake_rollout_thread, send_turn_and_wait, start_thread, mcp_server_names, start_thread (+15 more));外部调用 2 个(eprintln!, unreachable!)。
TestAppServer::read_stream_until_error_message1479–1493 ↗
async fn read_stream_until_error_message(
&mut self,
request_id: RequestId,
) -> anyhow::Result<JSONRPCError>
作用:一直读消息,直到找到指定请求编号的错误响应。用于测试服务器应该报错的情况。
数据流:进去的是 request_id → 从缓存或 stdout 找同 id 消息 → 确认它是 Error 并返回。
调用关系:expect_error_message、read_error_response 等错误断言辅助会调用它。
调用图:调用 1 个内部函数(read_stream_until_message);被 4 处调用(assert_update_request_fails_for_missing_session, expect_error_message, read_error_response, assert_remote_control_disabled_by_requirements);外部调用 1 个(unreachable!)。
TestAppServer::read_stream_until_notification_message1495–1514 ↗
async fn read_stream_until_notification_message(
&mut self,
method: &str,
) -> anyhow::Result<JSONRPCNotification>
作用:一直读消息,直到遇到指定方法名的通知。通知常表示后台事件,比如任务完成。
数据流:进去的是通知 method → 它筛选消息流,缓存不相关消息 → 找到对应 Notification 后返回。
调用关系:等待 turn 完成、命令输出、app 列表更新等测试都会用它;底层筛选由 read_stream_until_message 做。
调用图:调用 1 个内部函数(read_stream_until_message);被 25 处调用(interrupt_turn_and_wait_for_aborted, assert_no_session_updates_for, read_app_list_updated_notification, read_command_exec_delta, wait_for_context_compaction_completed, wait_for_context_compaction_started, wait_for_turn_completed, wait_for_dynamic_tool_completed, wait_for_dynamic_tool_started, maybe_fs_changed_notification (+15 more));外部调用 2 个(eprintln!, unreachable!)。
TestAppServer::read_stream_until_matching_notification1516–1539 ↗
async fn read_stream_until_matching_notification(
&mut self,
description: &str,
predicate: F,
) -> anyhow::Result<JSONRPCNotification>
作用:一直读消息,直到遇到满足自定义条件的通知。比只按方法名查更灵活。
数据流:进去的是描述文字和判断函数 predicate → 它读消息并把通知交给 predicate 判断 → 返回第一个匹配通知。
调用关系:wait_for_session_completed、wait_for_session_updated 会调用它;不匹配消息会被缓存,避免丢失。
调用图:调用 1 个内部函数(read_stream_until_message);被 2 处调用(wait_for_session_completed, wait_for_session_updated);外部调用 2 个(eprintln!, unreachable!)。
TestAppServer::read_next_message1541–1543 ↗
async fn read_next_message(&mut self) -> anyhow::Result<JSONRPCMessage>
作用:读取下一条可用消息,不挑类型。用于测试想自己收集并判断消息流的场景。
数据流:没有筛选条件 → 调用 read_stream_until_message 且条件永远为真 → 返回缓存中或新读到的第一条消息。
调用关系:collect_turn_notifications 等收集型测试会调用它。
调用图:调用 1 个内部函数(read_stream_until_message);被 4 处调用(collect_turn_notifications, collect_cyber_policy_error_and_validate_no_reroute, collect_model_verification_notifications_and_validate_no_warning_item, collect_turn_notifications_and_validate_no_warning_item)。
TestAppServer::clear_message_buffer1549–1551 ↗
fn clear_message_buffer(&mut self)
作用:清空已经缓存但还没消费的消息。用于让后续断言只看新的消息。
数据流:进去的是当前 TestAppServer → 清空 pending_messages 队列 → 不返回数据。
调用关系:run_environment_selection_case 会调用它,避免旧 turn 的消息影响新 turn 判断。
调用图:被 1 处调用(run_environment_selection_case);外部调用 1 个(clear)。
TestAppServer::pending_notification_methods1553–1561 ↗
fn pending_notification_methods(&self) -> Vec<String>
作用:查看当前缓存里有哪些通知方法名。主要用于调试或断言消息是否已经到达。
数据流:读取 pending_messages → 过滤出 Notification → 收集它们的 method 字符串并返回。
调用关系:它不读新消息,只检查缓冲区;适合辅助测试定位等待失败原因。
调用图:外部调用 1 个(iter)。
TestAppServer::read_stream_until_message1565–1580 ↗
async fn read_stream_until_message(&mut self, predicate: F) -> anyhow::Result<JSONRPCMessage>
作用:按条件读取消息,并把暂时不需要的消息先存起来。它保证测试不会因为等 A 消息而丢掉 B 消息。
数据流:进去的是判断函数 predicate → 先查 pending_messages,有匹配就取出;否则循环读 stdout,匹配则返回,不匹配就放进队列 → 返回匹配消息。
调用关系:所有 read_stream_until_* 函数都依赖它;它是入站消息等待和缓存的核心。
调用图:调用 2 个内部函数(read_jsonrpc_message, take_pending_message);被 6 处调用(read_next_message, read_stream_until_error_message, read_stream_until_matching_notification, read_stream_until_notification_message, read_stream_until_request_message, read_stream_until_response_message);外部调用 1 个(push_back)。
TestAppServer::take_pending_message1582–1590 ↗
fn take_pending_message(&mut self, predicate: &F) -> Option<JSONRPCMessage>
作用:从缓存消息里取出第一条满足条件的消息。用于优先消费之前暂存的消息。
数据流:进去的是判断函数 → 在 pending_messages 中找位置 → 找到就移除并返回,找不到返回 None。
调用关系:read_stream_until_message 在读新 stdout 前先调用它,避免已经缓存的匹配消息被忽略。
调用图:被 1 处调用(read_stream_until_message);外部调用 2 个(iter, remove)。
TestAppServer::pending_turn_completed_notification1592–1609 ↗
fn pending_turn_completed_notification(&self, thread_id: &str, turn_id: &str) -> bool
作用:检查缓存里是否已经有某个 turn 的完成通知。它用于处理“通知先到、响应后等超时”的竞态情况。
数据流:进去的是 thread_id 和 turn_id → 遍历缓存通知,找 method 为 turn/completed 的消息并解析参数 → 如果线程和 turn 都匹配就返回 true。
调用关系:interrupt_turn_and_wait_for_aborted 用它判断是否已经清理完成,从而避免测试因为时序差异变 flaky。
调用图:被 1 处调用(interrupt_turn_and_wait_for_aborted);外部调用 1 个(iter)。
TestAppServer::message_request_id1611–1618 ↗
fn message_request_id(message: &JSONRPCMessage) -> Option<&RequestId>
作用:从一条 JSON-RPC 消息里取请求编号。通知没有编号,所以会返回空。
数据流:进去的是一条 JSONRPCMessage → 如果是 Request、Response 或 Error 就取它的 id;如果是 Notification 就返回 None。
调用关系:read_stream_until_response_message 和 read_stream_until_error_message 用它把回复和原请求对上。
TestAppServer::drop1622–1662 ↗
fn drop(&mut self)
作用:测试客户端被丢弃时,尽量把服务器子进程关干净。没有它,测试结束后可能残留进程,造成随机失败或资源泄漏。
数据流:进去的是即将销毁的 TestAppServer → 先关闭 stdin 请求服务器优雅退出,短暂轮询;如果还没退,就发 kill,再最多等几秒 → 不返回数据,只做清理。
调用关系:Rust 自动在对象生命周期结束时调用它;它补强 Tokio kill_on_drop 的“尽力而为”,让集成测试收尾更稳定。
调用图:外部调用 6 个(start_kill, try_wait, sleep, from_millis, from_secs, now)。
集成套件索引
这些顶层测试模块将共享框架收集到编译后的集成二进制文件中,并组织特定功能的套件树,包括大型 v2 分支。
app-server/tests/all.rs源码 ↗
这个文件像一本考试卷的目录页:真正的题目不在这里,而是在 tests/suite/ 目录下。Rust 的集成测试会把 tests/ 里的文件当成测试程序来编译运行,所以这里做成一个统一入口,把 suite 模块加载进来。这样项目可以把很多测试按主题拆到不同文件里,保持整洁,又能通过这一个测试二进制一起执行。开头的 #![allow(clippy::expect_used)] 是告诉代码检查工具 Clippy:在测试代码里允许使用 expect。expect 通常是在出错时直接中断并给出说明,生产代码里要谨慎,但测试里很常见,因为测试失败时直接报错反而更清楚。
app-server/tests/suite/mod.rs源码 ↗
这个文件本身不写具体测试,而是用几行 mod 声明把测试分组接到一起。可以把它理解成一本练习册的总目录:auth 放登录和权限相关测试,conversation_summary 放会话摘要测试,fuzzy_file_search 放模糊文件搜索测试,strict_config 放严格配置检查测试,v2 放新版接口或流程的测试。Rust 语言里,mod 是“模块”的意思,也就是把另一个文件或文件夹里的代码纳入当前编译范围。这样做的好处是测试可以按主题拆开,文件不会越写越乱;同时测试入口仍然清楚,测试工具运行时能顺着这个总目录找到各个测试组。
app-server/tests/suite/v2/mod.rs源码 ↗
可以把这个文件理解成一本测试手册的目录。真正的测试分别写在 account、thread_start、web_search、plugin_install 等子文件里;这个文件用 Rust 的 mod 声明告诉编译器:“这些测试模块也属于同一个测试套件,请一起纳入。”这样做的好处是测试按功能拆开,查问题时不用在一个超大文件里翻。这里还有一些条件开关,比如 #[cfg(unix)] 表示只在 Unix 类系统上启用某些测试,#[cfg(debug_assertions)] 表示只在调试构建里启用某些测试。没有这些条件,某些只适合特定系统或特定构建方式的测试可能会在不支持的环境里失败。