danswer(Onyx)Box 连接器每日集成测试环境搭建与运行指南
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
本文以仓库 backend/tests/daily/connectors/box/README.md 为核心,系统讲解如何在真实 Box 企业环境中搭建一套可复现的测试语料库(corpus),使 test_box_basic.py 中的每日集成测试全部通过。读完本文,你将掌握 Box 开发者账号申请、CCG(Client Credentials Grant)应用配置、测试用户与协作矩阵搭建、密钥注入方式,以及从测试断言反推 BoxConnector 文档索引与权限同步原理的完整链路。
一、背景:为什么需要一套"真实"的 Box 测试企业
danswer(现品牌为 Onyx)通过连接器把第三方数据源接入统一检索。Box 连接器的实现位于 backend/onyx/connectors/box/,它依赖 Box 的 CCG 认证、用户模拟(impersonation)、协作/共享链接权限解析等一系列需要真实企业租户才能触发的 API 行为,因此仓库中除了可离线运行的单元测试外,还专门保留了backend/tests/daily/connectors/box/目录下的每日测试(daily tests),它们直接对着真实 Box 企业发起请求。
README.md开篇就强调了两类测试的分工:
- 每日测试(test_box_basic.py)需要一套按文档精确搭建的测试企业;测试会逐字符断言语料库中的文件名与文件内容(names and file contents must match character-for-character),任何偏差都会导致断言失败。
- 单元测试(位于
backend/tests/unit/onyx/connectors/box/)不需要任何上述环境,完全离线运行。
这套语料库并非随意造几个文件,而是刻意覆盖了多种共享级别(文件夹级 Viewer、文件级 Editor、文件夹级 Uploader、开放共享链接、完全无共享),用于验证连接器的权限同步(permission sync)逻辑——这正是文档强调"语料即断言、断言即语料"的原因。
二、前置条件:Box 开发者账号与普通账号的区别
文档第 1 步指出,搭建测试企业的前提是拥有一个Box 开发者账号,而不是普通用户账号:
- 通过 https://developer.box.com 免费注册即可获得一个sandbox 企业(sandbox enterprise),你同时是管理员,拥有 Admin Console 与 Developer Console 的访问权限。
- 普通的 box.comIndividual/Free 账号不是开发者账号,也无法升级转换——它背后没有企业(enterprise),因此
App + Enterprise Access、CCG、Admin Console、托管用户(managed users)、群组等能力全部不可用(Developer Console 会报 "some of your settings could not be saved")。
三种可行的注册路径:用全新邮箱走开发者注册流程、注册 Box Business 试用版、或使用由付费 Box 企业签发的开发者沙箱。这一前置差异直接决定了后续步骤能否进行,是最常见的"卡在第一步"的原因。
三、创建 CCG Platform App
3.1 创建应用
- 进入 Developer Console,创建Platform App → Custom App。
- 认证方式选择Server Authentication (Client Credentials Grant)——即 CCG,这是连接器底层 BoxCCGAuth / CCGConfig 使用的服务端认证方式,无需用户交互式授权。
- 在应用的Configuration标签页完成三组关键设置:
| 配置项 | 取值 | 作用(结合源码说明) |
|---|---|---|
| App Access Level | App + Enterprise Access | 使应用能以企业身份访问整个企业的内容,是 CCG 与用户模拟的前提 |
| Application Scopes | Read all files and folders stored in Box | 读取文件/文件夹内容与元数据(索引必需) |
| Application Scopes | Manage users | 群组同步需要枚举企业用户;也是"按邮箱解析用户 ID 实现模拟"的必要权限(见下方 3.3) |
| Application Scopes | Manage groups | 权限同步需要枚举群组及成员关系 |
| Advanced Features | Generate user access tokens | 连接器通过box_user_email模拟(impersonate)指定用户,语料就存放在该用户的 "All Files" 中 |
- 保存后在Authorization标签页点击Review and Submit提交审核。
- 到Admin Console → Apps → Custom Apps Manager中批准待处理的应用授权。每次修改 scope 后都必须重新批准——scope 的变更在重新授权前不会生效,这是排障时极易忽略的点。
3.2 收集凭据
从 Configuration 标签页收集三样信息:
- Client ID:应用的公开标识。
- Client Secret:注意"Fetch Secret"按钮要求 Box 账号先开启双重认证(2FA),否则会静默失效,底层请求返回
two_fa_enrollment_required。需先在账号 Settings 中启用 2FA,再回来获取。 - Enterprise ID:也在应用的 General Settings 标签页可见。
这三项与box_user_email一起,构成连接器load_credentials所需的四类凭据(connector.py 定义了box_client_id、box_client_secret、box_enterprise_id、box_user_email四个键名)。
3.3 凭据在源码中的落地方式
从源码看,BoxConnector.load_credentials(connector.py)会校验 client id / secret / enterprise id 三项必填,box_user_email可选。构建认证对象后:
enterprise_client:以企业(服务账号)身份访问,用于群组/用户枚举等管理级 API;content_client:读取内容时使用——若配置了box_user_email,会先通过_resolve_user_id_from_email(connector.py)借助Manage users scope将邮箱解析为数字用户 ID,再以auth.with_user_subject(user_id)模拟该用户读取其 "All Files";未配置时退化为服务账号。
这也解释了为什么 README 要求开启Manage usersscope:模拟用户依赖它;若未开启,源码会抛出InsufficientPermissionsError并提示补上该 scope 后重新授权。
四、创建测试用户
文档第 3 步定义了两种用户角色:
- 主用户(primary user):就是你自己的管理员账号。它的登录邮箱即
BOX_USER_EMAIL——连接器会模拟这个用户,因此测试语料库必须存放在该用户的 "All Files"下。 - 协作用户(collaborator):通过
POST /users(提供login+name)或 Admin Console → Users & Groups → Add User 创建的第二个托管用户。它只需存在、无需登录,其登录邮箱即BOX_COLLABORATOR_EMAIL,用于验证权限同步矩阵。
一个细节:Box 会拒绝向已停用(deactivated)用户发起协作,错误为cannot_invite_deactivated_user。若你想要的邮箱已被某个停用账号占用,改用同一收件箱的+alias形式即可(例如oauth+box@onyx.app)。
五、搭建测试语料库
5.1 目录结构(必须逐字符一致)
在主用户的 "All Files" 中按如下结构创建文件与文件夹,文件名、文件内容必须与文档/测试中的常量完全一致(test_box_basic.py 中定义了全部内容的字面量):
Onyx Connector Test Folder/ ├── root_doc.txt content: box root doc for onyx connector tests ├── public_doc.txt content: public doc for onyx connector tests ├── editor_doc.txt content: editor doc for onyx connector tests ├── Onyx Example Link web link (bookmark) -> https://www.onyx.app │ description: example bookmark for onyx connector tests ├── Subfolder A/ │ └── alpha.txt content: alpha doc for onyx connector tests ├── Shared Folder/ │ └── shared.txt content: shared doc for onyx connector tests └── Uploader Folder/ └── uploader_doc.txt content: uploader doc for onyx connector tests对应到测试期望:6 个文档(EXPECTED_DOC_NAMES)与 4 个文件夹层级节点(EXPECTED_FOLDER_NAMES,含根测试文件夹)。
5.2 共享矩阵:语料即权限断言
这套语料的关键在于为协作用户构造多个共享级别,这正是权限同步断言的核心(test_perm_sync_external_access):
| 条目 | 协作/链接方式 | 协作方可读? |
|---|---|---|
Shared Folder | 文件夹级Viewer | 是(→shared.txt) |
editor_doc.txt | 文件级Editor | 是 |
Uploader Folder | 文件夹级Uploader | 否(仅上传) |
public_doc.txt | 开放共享链接 | 公开(public) |
| 其余所有条目 | 无 | 仅所有者(owner-only) |
几个"必须注意"的细节:
- 必须上传真实
.txt文件(本地写好再拖拽上传)。Box Notes 是另一种文件类型,无法抽取为期望文本。文件末尾允许有一个换行符,测试会去除。 - Uploader 是刻意构造的负例:
uploader是 Box 仅上传(upload-only)角色,连接器不得为其授予读权限。uploader_doc.txt仍会被索引(所有者可读),但协作用户必须不在该文档的访问集合中(test_box_basic.py)。 - 同一企业内的托管用户邀请会自动接受(auto-accept),需逐个确认协作状态是active而非 pending。
public_doc.txt的共享链接访问级别选"People with the link"(即open级别),不设密码。- 不要给
Onyx Connector Test Folder本身、Subfolder A、root_doc.txt、alpha.txt添加任何共享链接或协作——测试断言它们为仅所有者可见。 Onyx Example Link是一个web link(书签)而非文件:在根测试文件夹中创建,指向https://www.onyx.app,并填写上述描述。它仅在连接器开启include_web_links时被索引,产出一个"薄"书签文档(名称 + 描述作为文本,不会抓取链接指向的页面内容)。
5.3 群组
再创建一个名称完全等于Onyx Test Group的群组,并把协作用户加为成员。不要把这个群组协作到任何文件夹上——它存在的唯一目的是验证群组同步(group sync)。
5.4 快速搭建建议
文档给出的最快捷方式是使用 Box API + Developer Token(Developer Console → 你的应用 →Developer Token):
POST /folders:建目录POST /files/content(上传主机):传文件POST /web_links:建书签POST /collaborations:每种共享级别各一次PUT /files/:id附带shared_link.access=open:开放共享链接POST /groups+POST /group_memberships:建群与加成员
六、密钥注入方式
6.1 解析顺序与命名
测试密钥按以下顺序解析:进程环境变量 → 仓库.vscode/.env→ AWS Secrets Manager(CI 使用)。该逻辑实现在 backend/tests/utils/aws_secrets.py,其中_get_local_secrets明确先读os.environ、再读.vscode/.env,_get_aws_secrets则按前缀批量拉取 Secrets Manager。名称对照如下:
| 本地环境变量 | AWS Secrets Manager 键(CI) | 值 |
|---|---|---|
BOX_CLIENT_ID | test/box-client-id | 应用 Client ID |
BOX_CLIENT_SECRET | test/box-client-secret | 应用 Client Secret |
BOX_ENTERPRISE_ID | test/box-enterprise-id | Enterprise ID |
BOX_USER_EMAIL | test/box-user-email | 主(管理员)用户邮箱 |
BOX_COLLABORATOR_EMAIL | test/box-collaborator-email | 协作用户邮箱 |
五个密钥名在 backend/tests/utils/secret_names.py 的TestSecret枚举中定义(如BOX_CLIENT_ID = "box-client-id"),测试通过pytest.mark.secrets(...)声明所需密钥(test_box_basic.py),不满足时会自动跳过。
6.2 写入 AWS Secrets Manager
CI 场景下,在 us-east-2 区域创建test/前缀下的五个密钥,例如:
aws secretsmanager create-secret --region us-east-2 \ --name test/box-client-id --secret-string "<client id>"七、运行测试
7.1 每日测试命令
source .venv/bin/activate export BOX_CLIENT_ID=... BOX_CLIENT_SECRET=... BOX_ENTERPRISE_ID=... \ BOX_USER_EMAIL=... BOX_COLLABORATOR_EMAIL=... pytest -xv backend/tests/daily/connectors/box7.2 连接器开发入口点
不想走 pytest、只想手动探查时,连接器自带 dev 入口(connector.py):脚本读取上述环境变量,通过ConnectorRunner从 1970 年至今遍历并打印文档与文件夹描述。可用BOX_FOLDER_IDS=<id>限定遍历范围(多个 id 用逗号分隔,否则默认从根文件夹"0"开始):
PYTHONPATH=backend python backend/onyx/connectors/box/connector.py八、测试断言详解:六个用例逐个拆解
test_box_basic.py 中的断言与 README 语料一一对应,是理解连接器行为的"活的说明书":
test_load_documents:遍历连接器全部文档,断言——文档集合恰好等于 6 个期望文档;层级节点集合等于 4 个文件夹;每个文档的抽取文本与预期内容逐字符一致;metadata["path"]正确反映目录层级(如alpha.txt的 path 为Onyx Connector Test Folder/Subfolder A);文档 id 以box-file-开头;时间戳存在且为 UTC(tzinfo == timezone.utc);主所有者邮箱等于被模拟用户(get_user_me().login);首节链接以https://app.box.com/file/开头(对应 box_file_link 的实现)。test_web_links:开启include_web_links=True后,书签被索引为 id 以box-weblink-开头的文档,其 section 链接指向目标 URLhttps://www.onyx.app,文本中携带名称 + 描述(对应 _convert_web_link 的f"{name}\n{description}"拼接逻辑)。test_poll_window_filters_documents_but_not_hierarchy:把轮询窗口设为 1970 年(时间戳 1_000_000 秒内不含任何真实文件),断言文档为空、但文件夹层级树仍然完整输出——因为文件夹节点不依赖修改时间,验证了 BFS 遍历与时间窗口过滤的分离设计。test_perm_sync_external_access(需要 EE 模块,enable_eefixture):在include_permissions=True下逐项验证共享矩阵——shared.txt(文件夹 Viewer)与editor_doc.txt(文件 Editor)把协作用户列入external_user_emails且is_public=False;uploader_doc.txt(文件夹 Uploader)不包含协作用户但所有者仍在;root_doc.txt仅所有者;public_doc.txt的is_public=True。同时断言Shared Folder这个层级节点自身也携带该访问集合。注意测试默认对连接器关闭include_web_links,因此该用例中文档集合仍精确等于 6 个。test_group_sync:调用box_group_sync(backend/ee/onyx/external_permissions/box/group_sync.py)断言两个层面的群:一是真实群组Onyx Test Group以box-group-<id>为 id 同步且包含协作用户;二是合成群box-enterprise-all-users-<enterprise_id>(box_all_enterprise_users_group_id)包含企业内每一个托管用户——它支撑的是企业内"公司范围"共享链接的权限语义。该实现还包含一个值得注意的防御逻辑:Box 群成员端点仅支持 offset 分页且上限约 1 万,超限时抛BoxGroupTooLargeError并跳过该群(而不是用不完整的成员集替换旧成员,避免误撤销权限)。test_validate_connector_settings:合法凭据 + 真实文件夹 id 应通过validate_connector_settings()与probe_group_listing_permission()(后者探测Manage groups/Manage usersscope,缺少时抛InsufficientPermissionsError,见 connector.py);而指向不存在的文件夹 id(如"999999999999999")必须抛出ConnectorValidationError——对应 validate_connector_settings 中 403/404 的分支处理。
九、连接器实现要点(源码级佐证)
- 遍历模型:连接器采用带检查点(checkpoint)的 BFS 爬取。
BoxConnectorCheckpoint(models.py)持有待处理文件夹队列todo、当前分页文件夹current与 Box 不透明 markercurrent_marker、以及已访问文件夹集合seen_folder_ids(用于去重——同时配置某文件夹及其祖先作为入口时避免重复索引)。每次_load_one_page只推进一个单位(播种入口、开始下一文件夹或翻一页),单页_BOX_PAGE_SIZE = 200。 - 时间窗口:
_in_time_window按modified_at过滤文件与 web link;无修改时间的条目保守视为在窗口内,避免误删。 - 大小与类型门槛:
BOX_CONNECTOR_SIZE_THRESHOLD默认 20 MB(见 app_configs.py),超限文件在下载/索引时被跳过;不支持的文件扩展名直接跳过。 - 文档 id 约定:文件为
box-file-<file_id>、web link 为box-weblink-<web_link_id>、群组为box-group-<group_id>、企业全员合成群为box-enterprise-all-users-<enterprise_id>(按企业 id 隔离,避免同一租户下两个 Box 连接器因共享群 id 造成"公司"链接文档串权)。normalize_box_login会把登录邮箱小写化,保证与 Onyx 内部统一小写的用户身份在访问过滤时精确匹配。 - 权限解析的版本化设计:
access.py中的resolve_box_*_access均通过fetch_versioned_implementation_with_fallback动态加载onyx.external_permissions.box.access的实现,开源默认走 noop,EE 版本(backend/ee/onyx/external_permissions/box/access.py)提供真正的协作/共享链接解析。
十、常见问题速查
| 现象 | 可能原因与处理 |
|---|---|
| Developer Console 报 "some of your settings could not be saved" | 用的是个人免费账号而非开发者/企业账号,需重新走开发者注册 |
| Fetch Secret 按钮无反应 | 账号未开启 2FA;先到账号 Settings 启用,再回来获取 |
| 改了 scope 后行为未变 | scope 变更需在 Admin Console 重新批准应用授权 |
邀请协作报cannot_invite_deactivated_user | 目标邮箱被停用账号占用,改用+alias邮箱 |
| 索引结果缺少文档或文本不符 | 文件名/内容必须与测试常量逐字符一致;Box Notes 不被识别为文本文件,需真实.txt上传 |
| 权限断言中协作用户出现在不应出现的位置 | 检查是否误给根测试文件夹/Subfolder A/root_doc.txt/alpha.txt添加了共享;Uploader 角色为仅上传,连接器必须不授读权限 |
| 验证报 401/404 | 凭据无效或被模拟用户不存在,见 validate_connector_settings 的区分逻辑 |
| 验证报 403 | 缺少所需 scope(读内容 /Manage users/Manage groups),开启后重新授权 |
结语
Box 连接器的每日测试是一套"用真实语料说话"的集成验证:目录结构与共享矩阵既是测试输入,也是权限同步契约。按本文流程完成开发者账号、CCG 应用、双用户、六文件四文件夹一链接一群组的搭建后,即可在本地或 CI(AWS Secrets Manager)中稳定运行 test_box_basic.py,并为深入阅读 BoxConnector 的 BFS 遍历、检查点恢复、权限解析与群组同步实现提供可对照的活样本。
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考