diff --git a/AGENTS.md b/AGENTS.md index 973e1b59..3d3bcb75 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ - `doc/test-plan/ai-test.md`:涉及 AI 测试范围、正式或 live case 设计、执行摘要或 suite 覆盖维护时阅读。 - `doc/test-plan/settings-test.md`:涉及 Settings 测试范围或测试边界时阅读。 -- `doc/test.md`:涉及测试体系、runner、renderer 辅助测试命令或正式测试流程时阅读。 +- `doc/test.md`:涉及测试体系、runner 或正式测试流程时阅读。 ### 1.5 prd产品文档相关 @@ -39,7 +39,7 @@ - 禁止使用 `Modal.confirm`、`Modal.info` 等静态方法 ### 代码实现准则 - +0. 允许重构且不兼容旧逻辑 1. 存在重复逻辑、重复类型,且适合抽象后提高阅读性、简单性、后期维护性的,要进行抽象封装,视情况看是否拆分文件 2. 代码逻辑实现过于复杂,有更简单的实现,且有助提高阅读性、简单性、后期维护性的,要进行简单实现 3. 文件拆得太碎,且拆后的小文件并不会继续在该文件扩展,那这些文件需要进行整合,有助提高阅读性、简单性、后期维护性 diff --git a/doc/project-structure.md b/doc/project-structure.md index 93a19b06..a15744ad 100644 --- a/doc/project-structure.md +++ b/doc/project-structure.md @@ -76,10 +76,10 @@ tests/ # 自动化测试 - `tests/harness/**` 只放可复用的测试基础设施,不承载旧 suite 治理逻辑或旧分析文档契约。 - `tests/fixtures/**` 只放长期复用的输入样本,不混入运行产物。 - `tests/fixtures/workspace/` 是共享工作区输入目录;工作区语义测试统一从这里取样本。 -- `tests/ai/**` 只放 AI formal tests。 -- `tests/unit/**` 放长期保留、但不进入 formal test-plan 的 unit / helper / schema / prompt 类测试。 -- `tests/settings/**` 按 settings PRD 模块组织正式测试。 -- `tests/kb-search/**` 预留给检索链路测试;`tests/temp/**` 仅放按需临时排查用例。 +- `tests/case/ai/**` 只放 AI formal tests。 +- `tests/case/unit/**` 放长期保留、但不进入 formal test-plan 的 unit / helper / schema / prompt 类测试。 +- `tests/case/settings/**` 按 settings PRD 模块组织正式测试。 +- `tests/case/kb-search/**` 预留给检索链路测试。 - `tests/logs/**` 只放每次运行的原始日志;raw logs 之外不再维护测试专用正式分析目录。 - 测试目录统一使用 kebab-case,运行产物不得反向成为测试发现入口。 @@ -93,4 +93,4 @@ tests/ # 自动化测试 - 用户可见功能行为变更时,应优先同步 `doc/prd/**`,再同步代码实现与测试。 - `doc/test-plan/` 用于维护各类测试计划文档。 - 测试计划按测试域与产品面拆分,例如 `doc/test-plan/ai-test.md`、`doc/test-plan/settings-test.md`。 -- 长期维护的正式测试用例,必须先在 `doc/test-plan/**` 中定义,再落到 `tests/**` 正式目录。 +- 长期维护的正式测试用例,必须先在 `doc/test-plan/**` 中定义,再落到 `tests/case/**` 正式目录。 diff --git a/doc/test-plan/ai-test.md b/doc/test-plan/ai-test.md index 73afd067..96850723 100644 --- a/doc/test-plan/ai-test.md +++ b/doc/test-plan/ai-test.md @@ -3,7 +3,7 @@ ## 1. 总体目标 - 本计划用于定义 AI 域正式测试的范围与领域特有规则。 -- 正式 / temp 准入、runner、raw logs 与正式 case 共享字段,统一遵循 `doc/test.md`。 +- 正式 / 临时准入、runner、raw logs 与正式 case 共享字段,统一遵循 `doc/test.md`。 - AI 正式测试只围绕两类稳定对象治理: 1. `tool` 2. `human` @@ -13,29 +13,29 @@ ### 2.1 按 Suite / 分类汇总 - `tool / docx` - - 通过率:`43 / 43` - - 检测时间:`2026-04-29 16:39:22 +08:00` + - 通过率:`42 / 42` + - 检测时间:`2026-06-11 17:16:38 +08:00` - `tool / kb` - 通过率:`2 / 2` - - 检测时间:`2026-04-16 16:16:05 +08:00` + - 检测时间:`2026-06-11 17:16:38 +08:00` - `tool / terminal` - - 通过率:`6 / 6` - - 检测时间:`2026-06-06` + - 通过率:`5 / 5` + - 检测时间:`2026-06-11 17:16:38 +08:00` - 覆盖补充:terminal 正式测试按“安全指令集”语义维护;Windows 路径覆盖 PowerShell 语法、`EncodedCommand` 执行与 UTF-8 输出,macOS 路径覆盖 zsh 语法。 - `tool / web-browsing` - 通过率:`5 / 5` - - 检测时间:`2026-06-11` + - 检测时间:`2026-06-11 17:16:38 +08:00` - 覆盖补充:`web_crawl` 已通过冻结 sidecar bin 和内置 headless shell 覆盖当前页抓取、导航页 topic 跟进、HTTP 失败与结果过大;`browser_use` 已覆盖缺少内置浏览器服务时的真实 diagnostic。 - `tool / utility` - - 通过率:`未执行` - - 检测时间:`未执行` - - 覆盖计划:`wait_time` 等待时长与返回 `waitedMs`。 + - 通过率:`1 / 1` + - 检测时间:`2026-06-11 17:16:38 +08:00` + - 覆盖补充:`wait_time` 等待时长与返回 `waitedMs`。 - `tool / hand` - - 通过率:`4 / 4` - - 检测时间:`2026-04-16 16:16:05 +08:00` + - 通过率:`9 / 9` + - 检测时间:`2026-06-11 17:16:38 +08:00` - `human / mock-llm` - - 通过率:`15 / 15` - - 检测时间:`2026-04-29 17:02:09 +08:00` + - 通过率:`22 / 22` + - 检测时间:`2026-06-11 17:16:38 +08:00` - `human / live` - 通过率:`2 / 2`(2026-06-11 定向执行 `web_crawl` live case;2026-06-01 全量 `--runs 2` raw run 样本为 `10 / 31`,suite 最终失败) - 检测时间:`2026-06-11` @@ -95,7 +95,7 @@ - 凡是会进入 `human` 的正式 run,必须在进入 `human` 前完成 embedding 基础服务 preflight;不允许把 embedding 初始化推迟到某个工具调用时才暴露。 - KB fixture 必须走真实导入、真实索引、真实检索。 - embedding 模型缺失时,harness 应优先通过真实生产下载链自动检查并下载,不得引入测试专用 embedding 替身。 -- 旧式 fake API 一律不得进入 formal harness;一次性排查如果确实需要 fake,只允许写入 `tests/temp/**`。 +- 旧式 fake API 一律不得进入 formal harness;一次性排查不得进入 `tests/case/**` 正式测试树。 - backend/live/e2e 正式测试统一使用默认 `testTimeout=900_000`、`hookTimeout=900_000`;不再为单个正式 case 单独配置超时。 ### 4.2 `mock-llm` 与 `live` 设计口径 @@ -113,20 +113,19 @@ - case 设计必须使用真实用户风格输入,不得把内部固定工具顺序写进用户输入。 - 断言重点:完成态、关键工具调用、关键 artifact、关键事实是否成立。 - 不对最终文案做精确文本断言。 -- transport / structured output 兼容性探针类 case,应至少分别覆盖一次 `force_non_stream` 与 `force_stream` 路径。 - 每次 formal `human / live` 执行完成后,除更新通过率、检测时间、备注外,还要在执行摘要中补充本轮已执行样例的完成度评分及简短依据。 ### 4.3 目录与命令 正式 AI 测试目录: -- `tests/ai/tool//.test.ts` -- `tests/ai/human/mock-llm/.test.ts` -- `tests/ai/human/live/.test.ts` +- `tests/case/ai/tool//.test.ts` +- `tests/case/ai/human/mock-llm/.test.ts` +- `tests/case/ai/human/live/.test.ts` 长期非 formal 测试目录: -- `tests/unit/ai/.test.ts` +- `tests/case/unit/ai/.test.ts` 推荐命令: @@ -142,7 +141,6 @@ ### 5.1 `tool` - #### docx 类别 ##### `docx-minimal-edit-flow` @@ -365,14 +363,64 @@ - Oracle:各类 action 都能写入成功并返回最终文档状态下可继续消费的 `postWriteReadback`。 - Not:不验证上层 op 选择策略。 -##### `docx-edit-serial-actions-stability` +##### `docx-edit-insert-blocks-order` - 测试类型:`backend` - 实现状态:`已实现` -- What:验证单次 `docx_edit(actions[])` 内部按 action 顺序执行,并通过稳定 locator 避免前序写入造成后序 blockId 漂移。 -- Why:这是 docx 批量精细修改的核心写入契约,避免 AI 以读到的真实 blockId 连续编辑时被 blockIndex 改变误伤。 -- How:覆盖“先插入再替换原目标”、“replace 后继续引用同一原始 blockId 命中 successor”、“删除后再修改同一目标失败且不提交部分结果”。 -- Oracle:前两类成功并命中预期最终文本,失败类返回错误且 active 文档保持原状态。 +- What:验证 `docx_edit(insert_blocks)` 在同一 anchor 后插入多条段落与表格时保持输入顺序。 +- Why:结构插入顺序一旦漂移,后续 AI 读回确认和用户可见文档都会出现错位。 +- How:在段落 A 后一次插入 `P1 / P2 / Table / P3`,再读取完整 outline。 +- Oracle:最终顺序稳定为 `A / P1 / P2 / Table / P3 / B`,且返回 `createdBlocks` 与 `postWriteReadback`。 +- Not:不验证多 action 串行插入;该场景由 `docx-edit-serial-multiple-inserts-preserve-created-blocks` 覆盖。 + +##### `docx-edit-serial-multiple-inserts-preserve-created-blocks` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证一次 `docx_edit(actions[])` 内多个 `insert_blocks` 会按 action 顺序稳定写入,并分别返回新增 block。 +- Why:批量结构插入需要让后续读回和继续编辑能准确识别每个新增结构。 +- How:在同一文档里先在 A 后插入两个段落,再在 C 前插入段落和表格。 +- Oracle:最终顺序稳定,`createdBlocks` 能对应到本次新增的段落与表格,不误收尾部旧块。 +- Not:不验证 replace/delete 与 insert 的混合漂移;该场景由串行动作稳定性 case 覆盖。 + +##### `docx-structural-op-behavior` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证文本替换不会创建新结构,段落/表格对象编辑可以创建独立结构块。 +- Why:docx 工具必须清晰区分文本级修改与结构级修改,否则会污染后续 outline、read 和 createdBlocks 语义。 +- How:先执行 `replace_text`,再执行段落与表格 `insert_blocks`,分别读取 outline。 +- Oracle:`replace_text` 后 body 结构数量不变;对象插入后出现新增 paragraph/table 结构,且 changedBlocks 标记为结构插入。 +- Not:不验证 run 级样式保留。 + +##### `docx-edit-serial-actions-resolve-original-target-after-insert-drift` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证单次 `docx_edit(actions[])` 内前序插入导致 blockIndex 漂移后,后序 action 仍能命中最初读取到的原始 blockId。 +- Why:AI 常基于同一轮读取结果连续提交批量编辑,必须避免前序插入让后续目标误伤相邻旧块。 +- How:先在 A 后插入 A1,再继续用最初读取到的 C blockId 执行 `replace_block`。 +- Oracle:最终文档为 `A / A1 / B / C updated`,changedBlocks 同时记录插入与替换。 +- Not:不支持同一次 actions 内引用本次刚创建且尚未回读的新 blockId。 + +##### `docx-edit-serial-actions-follow-replacement-successor` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证同一次 `docx_edit(actions[])` 内 `replace_block` 后继续引用同一原始 blockId,会解析到替换后的 successor。 +- Why:批量编辑经常需要先替换整段,再继续对新段落做局部文字处理。 +- How:先把目标段落替换为含 `keyword` 的新段落,再用同一原始 blockId 执行 `replace_text`。 +- Oracle:最终文本包含二次修改后的 `styled-keyword`,并同时记录 `replace_block` 与 `replace_text`。 +- Not:不验证跨多轮引用新增 block 的策略。 + +##### `docx-edit-serial-actions-fail-after-delete-without-committing` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证同一次 `docx_edit(actions[])` 内删除后再修改同一原始 blockId 会失败,且不会提交部分结果。 +- Why:批量编辑失败必须保持 transaction 边界,不能留下半成功 working draft。 +- How:先删除目标 block,再尝试对同一原始 block 执行 `replace_block`。 +- Oracle:工具返回失败;active 文档仍保留原目标文本,不出现后续替换文本。 - Not:不支持同一次 actions 内引用本次刚创建且尚未回读的新 blockId。 ##### `docx-format-variants-contract` @@ -445,16 +493,6 @@ - Oracle:未修改部分样式保留,修改目标样式正确。 - Not:不验证 UI。 -##### `docx-replay-open-and-continue-edit` - -- 测试类型:`backend` -- 实现状态:`已实现` -- What:验证同一 run 内 docx 自动 open 会基于 latest replay session 继续编辑。 -- Why:多轮编辑能否累计,依赖 replay session 的真实行为。 -- How:先执行一次 docx 写入,再执行 `docx_read_blocks / docx_edit(replace_text)`。 -- Oracle:读取内容来自最新 replay session,新增修改叠加在当前 revision 上。 -- Not:不验证 UI 与最终 direct apply。 - ##### `docx-working-draft-hides-deleted-content` - 测试类型:`backend` @@ -467,16 +505,6 @@ #### docx hand 类别 -##### `hand-serial-docx-chain` - -- 测试类型:`backend` -- 实现状态:`已实现` -- What:验证 hand 在 `serial` 模式下会按顺序执行 `docx` 写入与后续读取。 -- Why:这是 hand 承接 docx 写工具返回复核信息后继续执行后续读取的最小基线。 -- How:真实执行 `docx_edit(replace_text) -> docx_read_blocks`。 -- Oracle:工具结果按顺序成功返回,写工具结果包含 `postWriteReadback.regions`,且不产生旧写后阻断 decision hint。 -- Not:不验证复杂定位恢复。 - ##### `hand-docx-single-file-multi-write-allowed` - 测试类型:`backend` @@ -537,36 +565,6 @@ - Oracle:新增段落与新增 cell 都可继续被定位和修改,最新 working draft 只暴露最新有效内容。 - Not:不验证 live 模型如何规划多轮编辑。 -##### `docx-committed-view-projection` - -- 测试类型:`backend` -- 实现状态:`已实现` -- What:验证 direct apply 使用 committed view projection,且 projected docx 使用稳定表格布局。 -- Why:committed view 格式稳定性是 docx direct apply 的核心工程约束。 -- How:真实执行结构化编辑并 materialize committed view,再直接检查生成的 docx XML。 -- Oracle:committed view 不包含原生 `w:ins/w:del`,且插入表格带 `tblGrid / tblLayout / tcW`。 -- Not:不验证 live 渲染外观。 - -##### `docx-direct-apply-uses-committed-view` - -- 测试类型:`backend` -- 实现状态:`已实现` -- What:验证 direct apply 时按 committed view 语义落盘,而不是以 working artifact 直接覆盖源文件。 -- Why:最终落盘必须来自 committed view,不能把仅供 AI 继续编辑的 working draft 直接写回源文件。 -- How:构造 replay session 后执行 `applyDocxRunEdits`,检查源文件采用 committed view 内容。 -- Oracle:写回后 source 文件采用 committed view 内容,不包含 working-only 展示痕迹。 -- Not:不验证冲突处理。 - -##### `docx-risk-report-direct-apply` - -- 测试类型:`backend` -- 实现状态:`已实现` -- What:验证风险周报场景能直接把最终内容落盘到源 `docx`,并生成可回退 checkpoint。 -- Why:通用 direct apply 契约之外,还需要一条贴近真实业务内容的 docx 写回样例。 -- How:构造与 live 风险周报 case 相同的模板;通过真实 docx 工具执行两处占位符替换,随后执行 `applyDocxRunEdits` 并检查源文档最终内容。 -- Oracle:源模板中两处占位符被最终内容替换,且不再保留 `RISK_TODO / OWNER_TODO`;生成 `DocxApplyRecord(status=applied)`。 -- Not:不覆盖 live 模型执行路径,不要求经过 KB 或 terminal。 - ##### `docx-history-and-rollback` - 测试类型:`backend` @@ -737,15 +735,35 @@ #### human hand 类别 -##### `hand-tool-call-order-contract` +##### `hand-call-limit` - 测试类型:`backend` - 实现状态:`已实现` -- What:验证 `hand` 的多工具顺序执行、总调用数约束与结果聚合守卫。 -- Why:工具执行器本身必须在真实结果下稳定收敛,且不能再暴露并行分支复杂度。 -- How:通过真实 `hand` 执行多工具顺序调用、达到调用上限的批次,以及超过上限的批次。 -- Oracle:多工具调用会按输入顺序真实执行并正确聚合;超过 `HUMAN_HAND_CALL_LIMIT` 的批次会在执行前被 `hand_call_limit_exceeded` 拦截。 -- Not:不验证 `brain` 决策来源。 +- What:验证 `hand` 会在执行前拦截超过单轮调用上限的 `calls`。 +- Why:`calls` 数量上限是工具执行失控前的第一道硬边界,不能等到部分工具已执行后才发现超限。 +- How:直接构造 `HUMAN_HAND_CALL_LIMIT + 1` 个安全 terminal 调用交给真实 `hand` node。 +- Oracle:返回 `hand_call_limit_exceeded` decision hint,detail 中包含 limit 与 callCount,且不会产生 `lastToolResults`。 +- Not:不验证 hardActionLimit 的执行后收敛;该场景由 `hand-tool-limit-and-artifact-merge` 覆盖。 + +##### `hand-schema-preflight-invalid-input` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证 `hand` preflight schema 失败时稳定返回 `invalid_input`,并保留具体 diagnostic。 +- Why:模型常见错误是把工具参数包错层级,preflight 必须在真实工具执行前给出可恢复的结构化失败。 +- How:向真实 `hand` node 提交双层嵌套的坏 `docx_edit` 参数。 +- Oracle:工具结果为失败,diagnostic 指向 `calls[0].args`,并提升为 `hand_call_invalid` decision hint。 +- Not:不验证 brain 如何根据该 hint 重写计划。 + +##### `hand-docx-edit-invalid-blockid-failed` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证 `docx_edit` 使用不存在或过期 blockId 时失败,并返回统一 `hand_docx_edit_failed`。 +- Why:无效 blockId 是 docx 多轮编辑的高频失败入口,必须稳定进入可恢复 hint,而不是静默写错位置。 +- How:创建真实 docx 后,通过 `hand` 调用 `docx_edit(insert_blocks)`,anchorBlockId 使用不存在的 `b:99`。 +- Oracle:`docx_edit` 返回失败;pending decision hint 为 `hand_docx_edit_failed`,detail 包含 toolName、sourcePath、action 与具体错误。 +- Not:不验证 human 最终交付话术。 ##### `hand-terminal-approval-batch` @@ -773,15 +791,14 @@ - 实现状态:`已实现` - What:验证 `hand` 的工具次数限制与 artifact 合并行为。 - Why:这是工具执行层的关键边界。 -- How:通过真实工具结果触发 limit 与 artifact merge 分支。 -- Oracle:超限被守卫拦截,artifact 合并结果稳定。 +- How:先执行会产生 file artifact 的 terminal 工具,再在保留既有 artifact 的情况下执行不产生 artifact 的 docx read,并触发 hardActionLimit 收敛。 +- Oracle:已有 artifact 不被后续无 artifact 工具覆盖;超过 hardActionLimit 后产生 `hand_tool_limit_exceeded` decision hint、交付说明和当前工具结果。 - Not:不验证 `human` 全链路。 ### 5.2 `human` #### mock-llm 类别 - ##### `ai-run-service-human-only` - 测试类型:`backend` @@ -796,10 +813,20 @@ - 测试类型:`backend` - 实现状态:`已实现` -- What:验证 `AiRunService` 会在进入 `human` 前完成 embedding preflight,且 preflight 失败时会直接失败收口。 +- What:验证 `AiRunService` 会在进入 `human` 前完成 embedding preflight,并支撑后续 semantic docx 搜索。 - Why:embedding 已被定义为 AI run 的基础设施,不应等到 semantic 工具调用时才第一次暴露未就绪问题。 -- How:分别覆盖 preflight 成功后执行真实 `docx_read_blocks`,以及 preflight 直接失败的分支。 -- Oracle:成功分支中 `docx_read_blocks` 正常返回结果;失败分支中 run 直接失败且不会进入 `human` stage。 +- How:通过 `AiRunService.getInstance().run(...)` 启动真实 run,在 preflight 成功后执行 `docx_search(mode=semantic)`。 +- Oracle:preflight 被调用一次;任务完成;semantic 搜索返回带 score 的真实命中。 +- Not:不验证 preflight 失败收口;该场景由 `ai-run-service-embedding-preflight-failure` 覆盖。 + +##### `ai-run-service-embedding-preflight-failure` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证 embedding preflight 失败时,`AiRunService` 会在进入 `human` 前直接失败收口。 +- Why:基础服务不可用时继续进入 human graph 会制造后续工具层噪音,应该在入口阶段明确失败。 +- How:注入会抛错的 `ensureEmbeddingReady`,启动一次真实 `AiRunService` run。 +- Oracle:run 状态为 `failed`,错误包含 preflight 失败原因,事件流中没有任何 `human_step`。 - Not:不验证模型下载进度展示。 ##### `learning-hand-mouth-smoke` @@ -832,6 +859,16 @@ - Oracle:事件流中真实出现 source 为 `learning` 的 `decision_hint`,并且在它之后仍发生真实 terminal 工具调用,任务最终完成。 - Not:不验证 live 模型是否会自然产生这类重复决策。 +##### `brain-output-schema-invalid-retry` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证 `brain` 输出 hand plan schema 不合法时会产出 `brain_output_schema_invalid` decision hint,并在纠偏后继续完成任务。 +- Why:结构化输出偶发坏参数是 LLM 链路的真实风险,graph 必须把它转成可恢复 hint。 +- How:scripted brain 先返回带非法字段的 `docx_edit` hand plan,再返回完成任务决策。 +- Oracle:任务最终完成;事件流出现 source 为 `brain` 的 `brain_output_schema_invalid` decision hint,且 detail 中包含 `nextAction.calls` 相关 issue。 +- Not:不验证具体工具执行,因为坏 plan 应在执行前被拦截。 + ##### `decision-hint-contract-and-recovery` - 测试类型:`backend` @@ -932,6 +969,16 @@ - Oracle:真实 `docx_edit(replace_text)` 只产生一处 `changedBlocks`,真实 `docx_search` 与 `docx_read_blocks` 都能命中新文本,任务最终完成。 - Not:不验证 UI 或最终落盘流程。 +##### `complex-docx-insert-structured-content` + +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证 mock-llm 下 `human` 可以按对象类型提交段落与表格编辑参数。 +- Why:复杂 docx 编辑不能只覆盖文本替换,还要保护 structured content 插入这类高价值写作场景。 +- How:先读取真实 docx outline,定位 anchor 后通过真实 `docx_edit(insert_blocks)` 插入段落和表格,再读回确认。 +- Oracle:任务最终完成;docx apply artifact 与 file artifact 正常生成;写入内容包含新增段落和表格结构。 +- Not:不验证 live 模型是否自然选择同样的结构化参数。 + ##### `docx-read-fact-exposure` - 测试类型:`backend` diff --git a/doc/test-plan/settings-test.md b/doc/test-plan/settings-test.md index 418bc472..0c788649 100644 --- a/doc/test-plan/settings-test.md +++ b/doc/test-plan/settings-test.md @@ -4,7 +4,7 @@ - 本计划用于定义 Settings 用户可见能力的正式测试范围。 - Settings 正式测试统一对齐 [settings.md](/Users/test/work-space/NCurator-Desktop/doc/prd/settings.md),新增、调整或验收结论都应引用对应的 `SET-*` 需求编号与验收编号。 -- 正式 / temp 准入、runner、raw logs 与正式 case 共享字段,统一遵循 `doc/test.md`。 +- 正式 / 临时准入、runner、raw logs 与正式 case 共享字段,统一遵循 `doc/test.md`。 ## 2. 执行摘要 @@ -12,28 +12,29 @@ - `model` - 登记 case:`8` - - 通过率:`7 / 7`(e2e) - - 检测时间:`2026-06-02 18:32:06 +08:00` - - 备注:本轮完整 Settings e2e 已覆盖 model e2e;`model-config-list-contract` 未在本轮单独执行。 + - 通过率:`1 / 1`(backend,2026-06-11) / `7 / 7`(e2e,2026-06-02) + - 检测时间:`2026-06-11 17:16:38 +08:00` + - 备注:本轮已通过 `model-config-list-contract` backend;model e2e 结论沿用 2026-06-02 完整 Settings e2e。 - `workspace / core` - 登记 case:`6` - - 通过率:`6 / 6` - - 检测时间:`2026-06-03 13:12:07 +08:00` + - 通过率:`1 / 1`(backend,2026-06-11) / `5 / 5`(e2e,2026-06-03) + - 检测时间:`2026-06-11 17:16:38 +08:00` + - 备注:本轮已通过 `workspace-remove-source-contract` backend;workspace core e2e 结论沿用 2026-06-03。 - `workspace / files` - 登记 case:`12` - 通过率:`12 / 12` - - 检测时间:`2026-06-04 13:51:28 +08:00` - - 备注:本轮新增并通过 `workspace-index-worker-contract`;重跑通过 `workspace-prepare-real-fixture-refresh`、`workspace-prepare-failed-retry`、`workspace-prepare-file-failure-isolated`、`workspace-detail-status-and-error` 四条关键 E2E,其余 7 条沿用上一轮通过结论。 + - 检测时间:`2026-06-11 17:16:38 +08:00` + - 备注:本轮已通过 `workspace-prepare-contract`、`workspace-scan-contract`、`workspace-index-job-store-contract`、`workspace-index-worker-contract`、`workspace-interrupted-job-recovery-contract` 五条 backend;其余 E2E 结论沿用上一轮通过结论。 - `workspace / web` - 登记 case:`7` - 通过率:`7 / 7` - 检测时间:`2026-06-03 13:12:07 +08:00` - - 备注:本轮 `workspace` 范围 E2E 与 Settings backend 已通过;完整 `settings:e2e` 在当前机器上仍因 Electron 原生启动 `SIGABRT` 中断,未作为通过结论记录。 + - 备注:本轮未重跑 `workspace / web` E2E;完整 `settings:e2e` 未作为本轮通过结论记录。 - `vector` - - 登记 case:`4` - - 通过率:`3 / 3`(e2e) - - 检测时间:`2026-06-02 18:32:06 +08:00` - - 备注:本轮完整 Settings e2e 已覆盖 vector e2e;`vector-download-single-task-contract` 未在本轮单独执行。 + - 登记 case:`7` + - 通过率:`2 / 2`(backend,2026-06-11) / `5 / 5`(e2e,2026-06-06) + - 检测时间:`2026-06-11 17:16:38 +08:00` + - 备注:本轮已通过 `vector-download-single-task-contract`、`vector-download-startup-resume-contract` backend;vector e2e 结论沿用 2026-06-06。 ## 3. Suite 设计 @@ -47,7 +48,7 @@ Settings 的正式测试顶层 suite 直接对齐 PRD 模块: - `model` 验证大模型连接配置的加载、创建、编辑、启用、删除和远端模型列表刷新。 - `workspace` 验证工作空间容器及其下属 `files`、`web` 两类子区域的页面行为和 backend / IPC 支撑契约。 -- `vector` 属于 Settings 产品域,但页面位于独立路由 `/model-download`,测试目录与 runner 仍统一归入 `tests/settings/vector/**`。 +- `vector` 属于 Settings 产品域,但页面位于独立路由 `/model-download`,测试目录与 runner 仍统一归入 `tests/case/settings/vector/**`。 - `workspace` 在这里是 Settings 下的 suite,不是独立测试 domain;其下统一按 `core / files / web` 组织子区域。 - `workspace` 当前测试语义基于“具名工作空间容器 + 文件 tab + 网页 tab”,不再保留旧的“创建即绑定目录 / 全局状态 / 三种索引处理模式 / 打开路径 / 网页本地筛选”模型。 - `workspace` 相关正式测试默认复用 `tests/fixtures/workspace` 作为共享输入目录。 @@ -435,60 +436,32 @@ Settings 的正式测试顶层 suite 直接对齐 PRD 模块: - Oracle:下载完成后,对应模型显示为已安装,对齐 `SET-VECTOR-003-AC1`;下载失败时界面提示失败原因并退出下载中状态,对齐 `SET-VECTOR-003-AC2`。 - Not:不验证真实 provider 侧文件列表变化,也不验证跨会话缓存复用。 -#### `vector-download-navigation-resume` - -- ²âÊÔÀàÐͣº`e2e` -- ʵÏÖ״̬£º`ÒÑʵÏÖ` -- What£ºÑé֤Óû§À뿪ÏòÁ¿ģÐÍҳÃæºó£¬ÔٴνøÈëʱÈԿɻָ´ºǫ́ÕýÔڽøÐеÄÏÂÔؽø¶ȡ£ -- Why£ºÈç¹ûÏÂÔØÈÎÎñÒÀÀµҳÃæÉúÃüÖÜÆڣ¬Óû§·µ»غó»áÎóÒÔΪÈÎÎñÖжϻòÖØÐ¿ªʼ£¬ӰÏì¶Գ¤ʱ¼äÏÂÔØÈÎÎñµÄÐÅÐġ£ -- How£ºԤÖÃһ¸öºǫ́½øÐÐÖеÄÏÂÔؿìÕգ»½øÈëÏòÁ¿ģÐÍҳ¹۲ì½ø¶ȣ»Çе½ÆäËü Settings ×ÓҳºóÔٷµ»ØÏòÁ¿ģÐÍҳ¡£ -- Oracle£ºÔٴνøÈëҳÃæʱ£¬µ±ǰÎļþ¡¢Íê³ÉÊýºÍ×ܽø¶ȶ¼Äָܻ´ÏÔʾ£¬¶ÔÆë `SET-VECTOR-002-AC3`¡£ -- Not£º²»Ñé֤ӦÓÃÖØÆôºóµÄ×Զ¯»ָ´£»ÕâÀïֻ¸²¸ÇҳÃæÇл»Ïµĺǫ́¼ÌÐøÏÂÔØÌåÑ顣 - -#### `vector-delete-feedback` - -- ²âÊÔÀàÐͣº`e2e` -- ʵÏÖ״̬£º`ÒÑʵÏÖ` -- What£ºÑé֤ÒѰ²װģÐÍɾ³ý³ɹ¦ºóÇлؿÉÏÂÔØ״̬£¬ɾ³ýʧ°Üʱ±£ÁôÒѰ²װ״̬²¢¸ø³ö´íÎó·´À¡¡£ -- Why£ºɾ³ý½á¹ûÊÇÓû§ȷÈÏģÐÍÒѴӱ¾µØÒƳýµÄֱ½ÓÒÀ¾ݣ»Èôɾ³ýʧ°ܵ«½çÃæδÊÕÁ²´íÎó£¬Óû§»áÎóÅб¾µØģÐÍ״̬¡£ -- How£º·ֱ𸲸ÇÒѰ²װģÐÍɾ³ý³ɹ¦ºÍɾ³ýʧ°ÜÁ½Ìõ·¾¶£¬¹۲쿨Ƭ²Ù×÷ÇøºÍÌáʾ·´À¡¡£ -- Oracle£ºɾ³ý³ɹ¦º󣬶ÔӦģÐͻص½¿ÉÏÂÔØ״̬£¬¶ÔÆë `SET-VECTOR-004-AC1`£»ɾ³ýʧ°ÜʱÌáʾʧ°ÜԭÒ򲢱£ÁôÒѰ²װ״̬£¬¶ÔÆë `SET-VECTOR-004-AC2`¡£ -- Not£º²»Ñé֤ɾ³ýºó´ÅÅÌռÓõÄÕæʵ±仯£»ÕâÀïֻ¸²¸Ç Settings ҳÃæ¿ɼû״̬Ó뷴À¡¡£ - -## 5. ÏòÁ¿Ì׼þִÐÐժҪ¸üÕý£¨2026-06-06£© - -- `vector` - - µǼÇ case£º`7` - - ͨ¹ýÂʣº`5 / 5`£¨e2e£© - - ¼ì²âʱ¼䣺`2026-06-06 14:21:23 +08:00` - - ±¸ע£º±¾ÂÖÐÂÔö²¢ͨ¹ý `vector-download-startup-resume-contract`¡¢`vector-download-navigation-resume`¡¢`vector-delete-feedback`£»`vector-download-single-task-contract` backend ҲÒѵ¥¶Àͨ¹ý¡£ - #### `vector-download-startup-resume-contract` -- ²âÊÔÀàÐͣº`backend` -- ʵÏÖ״̬£º`ÒÑʵÏÖ` -- What£ºÑé֤ӦÓÃÆô¶¯ʱÈô´æÔڽøÐÐÖеÄÏòÁ¿ģÐÍÏÂÔؿìÕգ¬ºǫ́·þÎñ»á×Զ¯»ָ´¸ÃÏÂÔØÈÎÎñ¡£ -- Why£ºÈç¹ûӦÓÃÖؿªºóδÄָܻ´ÏÂÔأ¬Óû§»áÎóÒÔΪ³¤ʱ¼äÏÂÔØÈÎÎñÒѾ­¶ªʧ£¬ÇÒҳÃæÖØÐ½øÈëʱÎ޷¨¿´µ½һÖµĽø¶È״̬¡£ -- How£ºԤÖÃһ¸ö½øÐÐÖеÄÏÂÔؿìÕգ»Æô¶¯ÏòÁ¿ģÐÍÏÂÔطþÎñ³õʼ»¯£»¹۲ìÊǷñ»ָ´ͬһģÐÍÏÂÔء¢¼ÌÐøÉϱ¨½ø¶ȣ¬²¢ÔÚÍê³ɺóÇåÀí¿ìÕա£ -- Oracle£ºÆô¶¯³õʼ»¯ºó»á×Զ¯»ָ´ͬһģÐÍÏÂÔز¢¼ÌÐøÉϱ¨½ø¶ȣ¬¶ÔÆë `SET-VECTOR-002-AC4`¡£ -- Not£º²»Ñé֤ÕæʵÍøÂ绷¾³ÏµĿç°汾¼æÈݣ»ÕâÀïֻ¸²¸DZ¾µؿìÕջָ´ºÏͬ¡£ +- 测试类型:`backend` +- 实现状态:`已实现` +- What:验证应用启动时遇到进行中的向量模型下载快照,会恢复后台下载并继续上报进度。 +- Why:如果启动恢复失效,用户会误以为长时间下载已经丢失,重新进入页面时也无法看到一致的进度状态。 +- How:预置一条进行中的下载快照,初始化向量模型下载服务,观察是否恢复同一模型下载、继续上报进度,并在完成后清理快照。 +- Oracle:服务初始化后自动恢复同一模型下载并持续上报进度,对齐 `SET-VECTOR-002-AC4`。 +- Not:不验证真实网络环境下的跨版本兼容;这里只覆盖本地快照恢复与同步。 #### `vector-download-navigation-resume` -- ²âÊÔÀàÐͣº`e2e` -- ʵÏÖ״̬£º`ÒÑʵÏÖ` -- What£ºÑé֤Óû§À뿪ÏòÁ¿ģÐÍҳÃæºó£¬ÔٴνøÈëʱÈԿɻָ´ºǫ́ÕýÔڽøÐеÄÏÂÔؽø¶ȡ£ -- Why£ºÈç¹ûÏÂÔØÈÎÎñÒÀÀµҳÃæÉúÃüÖÜÆڣ¬Óû§·µ»غó»áÎóÒÔΪÈÎÎñÖжϻòÖØÐ¿ªʼ£¬ӰÏì¶Գ¤ʱ¼äÏÂÔØÈÎÎñµÄÐÅÐġ£ -- How£ºԤÖÃһ¸öºǫ́½øÐÐÖеÄÏÂÔؿìÕգ»½øÈëÏòÁ¿ģÐÍҳ¹۲ì½ø¶ȣ»Çе½ÆäËü Settings ×ÓҳºóÔٷµ»ØÏòÁ¿ģÐÍҳ¡£ -- Oracle£ºÔٴνøÈëҳÃæʱ£¬µ±ǰÎļþ¡¢Íê³ÉÊýºÍ×ܽø¶ȶ¼Äָܻ´ÏÔʾ£¬¶ÔÆë `SET-VECTOR-002-AC3`¡£ -- Not£º²»Ñé֤ӦÓÃÖØÆôºóµÄ×Զ¯»ָ´£»¸ó¡¾°ÓÉ `vector-download-startup-resume-contract` ¸²¸ǡ£ +- 测试类型:`e2e` +- 实现状态:`已实现` +- What:验证用户离开向量模型页后再次进入时,仍可恢复后台进行中的下载进度。 +- Why:下载不能依赖页面生命周期;用户切页后返回不应误判下载中断或重新开始。 +- How:预置一条后台下载中的任务快照,进入向量模型页观察进度,切到其他 Settings 页面后再返回向量模型页。 +- Oracle:再次进入页面时,当前文件、完成数和总进度都能恢复展示,对齐 `SET-VECTOR-002-AC3`。 +- Not:不验证应用重启后的自动恢复;该场景由 `vector-download-startup-resume-contract` 覆盖。 #### `vector-delete-feedback` -- ²âÊÔÀàÐͣº`e2e` -- ʵÏÖ״̬£º`ÒÑʵÏÖ` -- What£ºÑé֤ÒѰ²װģÐÍɾ³ýǰÐèҪ¶þ´ÎȷÈϣ»ȡÏûɾ³ýʱ±£³Öԭ״̬£»ȷÈϺó³ɹ¦ÇлؿÉÏÂÔØ״̬£¬ʧ°Üʱ±£ÁôÒѰ²װ״̬²¢¸ø³ö´íÎó·´À¡¡£ -- Why£ºɾ³ý½á¹ûÊÇÓû§ȷÈÏģÐÍÒѴӱ¾µØÒƳýµÄֱ½ÓÒÀ¾ݣ»Èç¹ûûÓÐȷÈϲ½Öè»òʧ°ܷ´À¡δÊÕÁ²£¬Óû§»áÎóÅб¾µØģÐÍ״̬¡£ -- How£º·ֱ𸲸Çɾ³ýȷÈϵ¯¿ò³öÏ֡¢ȡÏûɾ³ý¡¢ȷÈÏɾ³ý³ɹ¦ºÍȷÈÏɾ³ýʧ°ÜËÄÌõ·¾¶£¬¹۲쿨Ƭ²Ù×÷ÇøºÍÌáʾ·´À¡¡£ -- Oracle£ºɾ³ý³ɹ¦º󣬶ÔӦģÐͻص½¿ÉÏÂÔØ״̬£¬¶ÔÆë `SET-VECTOR-004-AC1`£»ɾ³ýʧ°ÜʱÌáʾʧ°ÜԭÒ򲢱£ÁôÒѰ²װ״̬£¬¶ÔÆë `SET-VECTOR-004-AC2`£»µã»÷ɾ³ýÏȳöÏÖȷÈϵ¯¿򣬶ÔÆë `SET-VECTOR-004-AC3`£»ȡÏûȷÈϺ󱣳ÖÒѰ²װ״̬ÇҲ»½øÈëɾ³ý½á¹û̬£¬¶ÔÆë `SET-VECTOR-004-AC4`¡£ -- Not£º²»Ñé֤ɾ³ýºó´ÅÅÌռÓõÄÕæʵ±仯£»ÕâÀïֻ¸²¸Ç Settings ҳÃæ¿ɼû״̬Ó뷴À¡¡£ +- 测试类型:`e2e` +- 实现状态:`已实现` +- What:验证已安装模型删除前需要二次确认,取消删除时保持原状态,确认后成功切回可下载状态,失败时保留已安装状态并展示错误。 +- Why:删除操作需要给用户明确确认和结果反馈;取消确认或删除失败都不能让模型状态失真。 +- How:分别覆盖删除确认弹窗出现、取消删除、确认删除成功、确认删除失败四条路径,观察卡片状态和反馈提示。 +- Oracle:删除成功后,对应模型回到可下载状态,对齐 `SET-VECTOR-004-AC1`;删除失败时展示失败原因并保留已安装状态,对齐 `SET-VECTOR-004-AC2`;删除前出现确认弹窗,对齐 `SET-VECTOR-004-AC3`;取消确认后保持已安装状态且不进入删除中状态,对齐 `SET-VECTOR-004-AC4`。 +- Not:不验证删除后磁盘占用的真实变化;这里只覆盖 Settings 页面可见状态与反馈。 diff --git a/doc/test.md b/doc/test.md index 1899dbff..8dfaf5d2 100644 --- a/doc/test.md +++ b/doc/test.md @@ -23,10 +23,10 @@ - 必须进入正式测试闭环。 - 临时排查 case - 只用于一次性排查、验证猜想、临时复现或补上下文。 - - 固定放在 `tests/temp/**`。 + - 不进入 `tests/**`,也不作为长期测试资产提交。 - 默认策略 - - 用户没有明确要求“补正式长期 case”时,默认先写 `tests/temp/**`。 - - 只要还在排查、验证猜想、试运行 fixture 或尚未确定要长期维护,一律先放 `tests/temp/**`。 + - 用户没有明确要求“补正式长期 case”时,默认不新增正式测试文件。 + - 只要还在排查、验证猜想、试运行 fixture 或尚未确定要长期维护,一律不写入正式测试目录。 - 只有确认要长期维护、参与 gating 的 case,才进入正式测试目录。 ### 2.2 目录结构 @@ -35,25 +35,24 @@ tests/ harness/ fixtures/ - ai/ - unit/ - settings/ - kb-search/ - temp/ + case/ + ai/ + unit/ + settings/ + kb-search/ logs/ ``` 规则: - 目录统一使用 kebab-case。 -- 测试发现目录使用通用树形结构:`tests////.test.ts`。 -- e2e 测试文件使用 `tests////.e2e.test.ts`。 +- 测试发现目录使用通用树形结构:`tests/case////.test.ts`。 +- e2e 测试文件使用 `tests/case////.e2e.test.ts`。 - `suite` 是第一层稳定分类;`groups...` 是 suite 下可选的任意深度子目录。 - 各 domain 的 `suite` 列表与归属,只在对应 `doc/test-plan/**` 中定义。 - 当某个 `suite` 不需要继续细分时,正式 case 可以直接放在该 `suite` 目录下。 -- `tests/unit/**` 用于长期保留、但不进入 `doc/test-plan/**` 正式治理的 unit / helper / schema / prompt 类测试。 -- `tests/temp/**` 默认不参与 gating,也不沉淀正式长期产物。 -- 除 `tests/temp/**` 外,`tests/**` 中的正式测试目录只允许放已在 `doc/test-plan/**` 登记的 case。 +- `tests/case/unit/**` 用于长期保留、但不进入 `doc/test-plan/**` 正式治理的 unit / helper / schema / prompt 类测试。 +- `tests/case/**` 中除 `unit/**` 外,只允许放已在 `doc/test-plan/**` 登记的正式 case。 - `tests/fixtures/workspace/` 是共享工作区 fixture 根目录;凡是依赖“工作区里有什么文件”的测试,优先复用该目录。 - 当 case 直接传入 `tests/fixtures/workspace` 目录时,runner 应把该目录下的子内容原样复制到临时 workspace 根目录,而不是保留 `tests/fixtures/**` 中间层路径。 @@ -79,8 +78,8 @@ tests/ - `doc/test-plan/**` 下每个领域文档,都是该领域正式测试用例定义的唯一规格来源。 - 新增、修改或补齐正式 case 时,必须先更新对应 `doc/test-plan/**`,再修改测试代码。 -- 未登记的 case 不得直接写进 `tests/**` 非 `temp` 目录。 -- 当临时 case 需要转为正式长期 case 时,顺序必须是:先补 `doc/test-plan/**`,再迁入正式目录。 +- 未登记的 case 不得直接写进 `tests/case/**` 非 `unit` 目录。 +- 当临时验证需要转为正式长期 case 时,顺序必须是:先补 `doc/test-plan/**`,再迁入正式目录。 - 如果实现行为变更导致正式 case 预期变化,必须先更新对应 `doc/test-plan/**`,再修改测试代码。 - 每个正式 `caseId` 都必须稳定映射到唯一测试文件与唯一日志目录。 @@ -132,29 +131,28 @@ tests/ ### 3.4 长期价值与常见误区 - 进入正式 domain 目录的 case 默认都应具备长期回归价值。 -- `tests/unit/**` 默认具备长期维护价值,但不属于 formal test-plan case。 -- 一次性排查、临时验证、待定样例统一放在 `tests/temp/**`。 +- `tests/case/unit/**` 默认具备长期维护价值,但不属于 formal test-plan case。 +- 一次性排查、临时验证、待定样例不进入正式测试树。 常见反例: -- 为了排查某个猜想,直接在 `tests/ai/**` 或 `tests/settings/**` 新增正式 case。 +- 为了排查某个猜想,直接在 `tests/case/ai/**` 或 `tests/case/settings/**` 新增正式 case。 - 还没补 `doc/test-plan/**`,就先写正式测试代码。 - 正式 case 的 `How` 开始直接描述内部 route、内部工具顺序或固定 plan 条数。 常见正例: -- 为了确认某个上下文拼接问题是否真实存在,先写 `tests/temp/**` 做一次性验证。 - 某条真实用户场景已经确定要长期回归,先补对应 `doc/test-plan/**`,再补正式测试代码。 -- 某个 helper / schema / prompt 需要长期回归,但不属于 formal 业务 case,放进 `tests/unit/**`。 +- 某个 helper / schema / prompt 需要长期回归,但不属于 formal 业务 case,放进 `tests/case/unit/**`。 ## 4. 正式测试闭环 ### 4.1 固定顺序 1. 先判断这是正式长期回归 case,还是临时排查 case。 -2. 如果只是临时排查,直接写入 `tests/temp/**`,不进入正式闭环。 +2. 如果只是临时排查,不写入 `tests/**`,也不进入正式闭环。 3. 如果要进入正式目录,先更新对应 `doc/test-plan/**`。 -4. 再写或调整 `tests/**` 中的正式测试代码。 +4. 再写或调整 `tests/case/**` 中的正式测试代码。 5. 通过 `scripts/test-workflow.mjs` 执行并产生日志。 6. 先看 `timeline.jsonl`,不足时再看 `runtime-events.jsonl`、`meta.json` 与 `artifacts.json`。 7. 最后基于 raw logs 回流结论,决定是否需要更新 `doc/test-plan/**` 或实现代码。 @@ -175,7 +173,7 @@ runner 不负责: - 写失败原因分析。 - 写优化建议。 -当前常用命令按域收口如下。`workbench` 不再维护 formal test-plan,对应端到端体验验证改走 hand-test,不再提供 `test:workbench*` runner;Workbench renderer UI 的长期辅助契约可放入 `tests/renderer/**`,通过 `pnpm test:renderer` 执行;一次性视觉或交互排查放入 `tests/temp/**`,不得直接新增未登记的 formal workbench case。 +当前常用命令按域收口如下。`workbench` 不再维护 formal test-plan,对应端到端体验验证改走 hand-test,不再提供 `test:workbench*` 或 renderer 专用 runner;一次性视觉或交互排查不得直接新增未登记的 formal workbench case。 - AI - `pnpm test:ai` @@ -183,8 +181,6 @@ runner 不负责: - `pnpm test:ai:tool` - `pnpm test:ai:human:mock-llm` - `pnpm test:ai:human:live` -- Renderer - - `pnpm test:renderer` - Unit - `pnpm test:unit` - `pnpm test:unit:ai` @@ -413,7 +409,7 @@ tests/logs////// 常见误区: -- 把 `tests/unit/**` 当成 formal domain case 管理。 -- 把 `tests/temp/**` 的结果直接当成正式长期结论。 +- 把 `tests/case/unit/**` 当成 formal domain case 管理。 +- 把一次性排查结果直接当成正式长期结论。 - 在 `timeline.jsonl` 中倾倒底层噪音,而不是保留高价值语义事件。 - 在没有更新 `doc/test-plan/**` 的情况下直接修改正式 case 预期。 diff --git a/package.json b/package.json index df0cdf13..4e704245 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "test:ai:tool": "node scripts/test-workflow.mjs --domain ai --suite tool --type backend", "test:ai:human:mock-llm": "node scripts/test-workflow.mjs --domain ai --suite human --group mock-llm --type backend", "test:ai:human:live": "node scripts/test-workflow.mjs --domain ai --suite human --group live --type backend", - "test:renderer": "vitest run --config vitest.renderer.config.mts", "test:unit": "node scripts/test-workflow.mjs --domain unit --type backend", "test:unit:ai": "node scripts/test-workflow.mjs --domain unit --suite ai --type backend", "test:settings": "node scripts/test-workflow.mjs --domain settings", diff --git a/scripts/test-workflow.mjs b/scripts/test-workflow.mjs index 8aafa39d..09d4abc3 100644 --- a/scripts/test-workflow.mjs +++ b/scripts/test-workflow.mjs @@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process' const ROOT = process.cwd() const TESTS_ROOT = path.join(ROOT, 'tests') +const TEST_CASES_ROOT = path.join(TESTS_ROOT, 'case') const resolveLocalBin = (name) => path.join(ROOT, 'node_modules', '.bin', process.platform === 'win32' ? `${name}.cmd` : name) const PNPM_BIN = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm' @@ -91,7 +92,7 @@ const walk = async (target) => { } const resolveScopeDir = (input) => { - const parts = [TESTS_ROOT] + const parts = [TEST_CASES_ROOT] if (input.domain) parts.push(input.domain) if (input.suite) parts.push(input.suite) if (input.groups.length > 0) parts.push(...input.groups) diff --git a/tests/ai/tool/hand/hand-docx-write-post-write-readback.test.ts b/tests/ai/tool/hand/hand-docx-write-post-write-readback.test.ts deleted file mode 100644 index 9ed5830c..00000000 --- a/tests/ai/tool/hand/hand-docx-write-post-write-readback.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' - -describe('ai tool hand backend', () => { - it('hand-docx-write-produces-post-write-readback', async () => { - await runBackendCase({ - caseId: 'hand-docx-write-post-write-readback', - harnessMode: 'tool-real', - domain: 'ai', - suite: 'tool', - groups: ['hand'], - description: '验证一轮 hand 内发生 docx 写操作后,会返回 postWriteReadback 结果。', - fixtures: [], - runMode: 'single', - execute: async (runtime) => { - await writeTestDocxFile(runtime.workspaceRoot, 'post-write-readback.docx', [ - { type: 'paragraph', text: 'Original content.' } - ]) - - const ctx = runtime.createAiRunContext() - const cache = createHumanRuntimeCache() - - ensureSkillTools({ cache, ctx, runId: runtime.runId, taskId: runtime.taskId, skillId: 'docx-read' }) - ensureSkillTools({ cache, ctx, runId: runtime.runId, taskId: runtime.taskId, skillId: 'docx-write' }) - - const handNode = createHandNode({ ctx, cache }) - const state = await handNode({ - ...createInitialHumanState({ - taskId: runtime.taskId, - userInput: '请修改文档' - }), - skills: { - learnedSkillIds: ['docx-read', 'docx-write'] - }, - execution: { - ...createInitialHumanState({ - taskId: runtime.taskId, - userInput: '请修改文档' - }).execution, - currentThought: { - thought: { - next: '执行 docx 写入' - }, - nextAction: { - action: 'hand', - calls: [ - { - toolName: 'docx_edit', - args: { - sourcePath: 'post-write-readback.docx', - actions: [{ - type: 'replace_text', - query: 'Original content.', - replace: 'Updated content.' - }] - } - } - ] - } - } - } - }) - - return { - state - } - }, - assert: async (result, runtime) => { - runtime.assertThat( - result.state.execution.pendingDecisionHint === undefined && - result.state.execution.lastToolResults?.[0]?.toolName === 'docx_edit' && - result.state.execution.lastToolResults?.[0]?.ok === true && - typeof result.state.execution.lastToolResults?.[0]?.data === 'object' && - result.state.execution.lastToolResults?.[0]?.data !== null && - Array.isArray( - (result.state.execution.lastToolResults?.[0]?.data as { postWriteReadback?: { regions?: unknown[] } }).postWriteReadback?.regions - ), - { - name: 'docx-write-produces-post-write-readback', - message: '只要 hand 成功执行过 docx 写,就应返回 postWriteReadback,且不再产生旧写后阻断。', - data: { - lastToolResults: result.state.execution.lastToolResults - } - } - ) - } - }) - }) -}) diff --git a/tests/ai/tool/terminal/terminal-unsafe-requires-approval.test.ts b/tests/ai/tool/terminal/terminal-unsafe-requires-approval.test.ts deleted file mode 100644 index 5314ff51..00000000 --- a/tests/ai/tool/terminal/terminal-unsafe-requires-approval.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import fs from 'node:fs/promises' -import path from 'node:path' -import { describe, it } from 'vitest' -import { createTerminalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { filterEventsByType } from '../testHelpers' - -type TerminalRunCommandResult = { - reason?: 'approval_denied' | 'workspace_required' | 'invalid_input' | 'failed' -} - -describe('ai tool terminal backend', () => { - it('terminal-unsafe-requires-approval', async () => { - await runBackendCase({ - caseId: 'terminal-unsafe-requires-approval', - harnessMode: 'tool-real', - domain: 'ai', - suite: 'tool', - groups: ['terminal'], - description: '验证未命中安全指令集但并非高危的 terminal 命令仍必须审批。', - fixtures: [], - runMode: 'single', - execute: async (runtime) => { - const createdDir = path.join(runtime.workspaceRoot, 'blocked-dir') - const harness = createTerminalToolHarness(runtime, { - approvalPolicy: 'deny-all' - }) - - return { - mkdirDenied: await harness.invoke('terminal_run', { - command: 'mkdir blocked-dir' - }), - blockedDirExists: await fs - .access(createdDir) - .then(() => true) - .catch(() => false), - events: runtime.eventCollector.snapshot() - } - }, - assert: async (result, runtime) => { - const approvalEvents = filterEventsByType(result.events, 'approval_batch_required') - - runtime.assertThat( - result.mkdirDenied.reason === 'approval_denied' && - result.blockedDirExists === false && - approvalEvents.some((event) => - event.items.some((item) => item.command === 'mkdir blocked-dir') - ), - { - name: 'unsafe-command-requires-approval-even-without-risk-tiering', - message: '未命中安全指令集的 terminal 命令即使不是高危命令,也必须先审批。', - data: { - mkdirDenied: result.mkdirDenied, - blockedDirExists: result.blockedDirExists, - approvals: approvalEvents.map((event) => ({ - command: event.items[0]?.command, - reason: event.items[0]?.reason - })) - } - } - ) - } - }) - }) -}) diff --git a/tests/ai/human/.gitkeep b/tests/case/ai/human/.gitkeep similarity index 100% rename from tests/ai/human/.gitkeep rename to tests/case/ai/human/.gitkeep diff --git a/tests/ai/human/live/human-context-compaction-natural-trigger-live.test.ts b/tests/case/ai/human/live/human-context-compaction-natural-trigger-live.test.ts similarity index 97% rename from tests/ai/human/live/human-context-compaction-natural-trigger-live.test.ts rename to tests/case/ai/human/live/human-context-compaction-natural-trigger-live.test.ts index 5674b43b..a596f806 100644 --- a/tests/ai/human/live/human-context-compaction-natural-trigger-live.test.ts +++ b/tests/case/ai/human/live/human-context-compaction-natural-trigger-live.test.ts @@ -1,13 +1,13 @@ import path from 'node:path' import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { CONTEXT_COMPACTION_SHARED_WORKSPACE_FIXTURE_PATH, HUMAN_CONTEXT_COMPACTION_USER_INPUT, loadContextCompactionWorkspaceSeed -} from '../../../harness/backend/liveScenarios/contextCompactionScenario' +} from '../../../../harness/backend/liveScenarios/contextCompactionScenario' import { filterAiRunEventsByType, hasDocxApplyArtifact, diff --git a/tests/ai/human/live/human-conversation-memory-multi-run-live.test.ts b/tests/case/ai/human/live/human-conversation-memory-multi-run-live.test.ts similarity index 96% rename from tests/ai/human/live/human-conversation-memory-multi-run-live.test.ts rename to tests/case/ai/human/live/human-conversation-memory-multi-run-live.test.ts index 0992e442..769adfad 100644 --- a/tests/ai/human/live/human-conversation-memory-multi-run-live.test.ts +++ b/tests/case/ai/human/live/human-conversation-memory-multi-run-live.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import { updateConversationMemoryAfterRun } from '../../../../src/main/services/ai/memory' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { updateConversationMemoryAfterRun } from '../../../../../src/main/services/ai/memory' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { CONVERSATION_MEMORY_FIRST_RUN_USER_INPUT, CONVERSATION_MEMORY_SECOND_RUN_USER_INPUT, CONVERSATION_MEMORY_SHARED_WORKSPACE_FIXTURE_PATH, loadConversationMemoryWorkspaceSeed -} from '../../../harness/backend/liveScenarios/conversationMemoryScenario' +} from '../../../../harness/backend/liveScenarios/conversationMemoryScenario' import { filterAiRunEventsByType, runWithStructuredOutputRetry } from '../testUtils' const SESSION_ID = 'live-conversation-memory-multi-run-session' diff --git a/tests/ai/human/live/human-docx-cause-remedy-reading-live.test.ts b/tests/case/ai/human/live/human-docx-cause-remedy-reading-live.test.ts similarity index 95% rename from tests/ai/human/live/human-docx-cause-remedy-reading-live.test.ts rename to tests/case/ai/human/live/human-docx-cause-remedy-reading-live.test.ts index ffd22dda..5cfd4ea7 100644 --- a/tests/ai/human/live/human-docx-cause-remedy-reading-live.test.ts +++ b/tests/case/ai/human/live/human-docx-cause-remedy-reading-live.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildSelectedDocxFixtureReferences, DOCX_READING_FIXTURE_PATHS, HUMAN_DOCX_CAUSE_REMEDY_READING_USER_INPUT, SHARED_WORKSPACE_FIXTURE_PATH, resolveSelectedDocxFixtures -} from '../../../harness/backend/aiLiveTestData' +} from '../../../../harness/backend/aiLiveTestData' import { filterAiRunEventsByType, runWithStructuredOutputRetry diff --git a/tests/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts b/tests/case/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts similarity index 96% rename from tests/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts rename to tests/case/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts index 804bdafb..75b20e3c 100644 --- a/tests/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts +++ b/tests/case/ai/human/live/human-docx-cross-source-summary-draft-live.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildSelectedDocxReferences, captureDocxFixtureBytes, @@ -11,7 +11,7 @@ import { HUMAN_DOCX_CROSS_SOURCE_SUMMARY_USER_INPUT, SHARED_WORKSPACE_FIXTURE_PATH, resolveSelectedDocxFixtures -} from '../../../harness/backend/aiLiveTestData' +} from '../../../../harness/backend/aiLiveTestData' import { collectDocxApplyOpsContentKinds, collectStartedDocxEditSourcePaths, diff --git a/tests/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts b/tests/case/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts similarity index 96% rename from tests/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts rename to tests/case/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts index 75ce24d0..19109f18 100644 --- a/tests/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts +++ b/tests/case/ai/human/live/human-docx-positioned-structural-edit-fallback-live.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildSelectedDocxFixtureReferences, DOCX_POSITIONED_FIXTURE_PATHS, HUMAN_DOCX_POSITIONED_STRUCTURAL_EDIT_FALLBACK_USER_INPUT, SHARED_WORKSPACE_FIXTURE_PATH, resolveSelectedDocxFixtures -} from '../../../harness/backend/aiLiveTestData' +} from '../../../../harness/backend/aiLiveTestData' import { countDocxApplyArtifacts, countDocxFileArtifacts, diff --git a/tests/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts b/tests/case/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts similarity index 96% rename from tests/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts rename to tests/case/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts index 0b24a9a3..75f85e6c 100644 --- a/tests/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts +++ b/tests/case/ai/human/live/human-kb-guided-docx-comparison-draft-live.test.ts @@ -1,15 +1,15 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import type { AiRunEvent, JsonObject, JsonValue } from '../../../../src/shared/types' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import type { AiRunEvent, JsonObject, JsonValue } from '../../../../../src/shared/types' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildKbGuidedDocxWorkspaceSeed, HUMAN_KB_GUIDED_DOCX_COMPARISON_DRAFT_USER_INPUT, SHARED_WORKSPACE_FIXTURE_PATH -} from '../../../harness/backend/aiLiveTestData' +} from '../../../../harness/backend/aiLiveTestData' import { collectDocxApplyOpsContentKinds, collectStartedDocxEditSourcePaths, diff --git a/tests/ai/human/live/human-risk-report-draft-live.test.ts b/tests/case/ai/human/live/human-risk-report-draft-live.test.ts similarity index 97% rename from tests/ai/human/live/human-risk-report-draft-live.test.ts rename to tests/case/ai/human/live/human-risk-report-draft-live.test.ts index b4dd939e..f5a6b8da 100644 --- a/tests/ai/human/live/human-risk-report-draft-live.test.ts +++ b/tests/case/ai/human/live/human-risk-report-draft-live.test.ts @@ -4,15 +4,15 @@ import { describe, it } from 'vitest' import { buildBlockIndex, loadDocxBundle -} from '../../../../src/main/services/ai/human/tools/docx/adapters/ooxml/blockIndex' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/main/services/ai/human/tools/docx/adapters/ooxml/blockIndex' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { HUMAN_RISK_REPORT_DRAFT_USER_INPUT, loadRiskReportDraftWorkspaceSeed, RISK_REPORT_DRAFT_SHARED_WORKSPACE_FIXTURE_PATH -} from '../../../harness/backend/liveScenarios/riskReportDraftScenario' +} from '../../../../harness/backend/liveScenarios/riskReportDraftScenario' import { collectStartedDocxEditSourcePaths, collectStartedToolStringArgs, diff --git a/tests/ai/human/live/human-session-resume-with-new-selection-live.test.ts b/tests/case/ai/human/live/human-session-resume-with-new-selection-live.test.ts similarity index 95% rename from tests/ai/human/live/human-session-resume-with-new-selection-live.test.ts rename to tests/case/ai/human/live/human-session-resume-with-new-selection-live.test.ts index b4a7384b..de251d7c 100644 --- a/tests/ai/human/live/human-session-resume-with-new-selection-live.test.ts +++ b/tests/case/ai/human/live/human-session-resume-with-new-selection-live.test.ts @@ -1,21 +1,21 @@ import { describe, it } from 'vitest' -import { AiRunService } from '../../../../src/main/services/ai/AiRunService' +import { AiRunService } from '../../../../../src/main/services/ai/AiRunService' import { invokeStructuredChat, type AiLlmDriver, type AiLlmStructuredDriverResult, type AiLlmTextDriverResult -} from '../../../../src/main/services/ai/llmExecutor' -import type { AiRunDeps } from '../../../../src/main/services/ai/types' -import type { AiRunEvent, JsonObject } from '../../../../src/shared/types' -import { hasLiveLlmConfigInEnv, loadLiveLlmConfigFromEnv } from '../../../harness/backend/llmConfig' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import type { BackendTestRuntime } from '../../../harness/backend/test-types' +} from '../../../../../src/main/services/ai/llmExecutor' +import type { AiRunDeps } from '../../../../../src/main/services/ai/types' +import type { AiRunEvent, JsonObject } from '../../../../../src/shared/types' +import { hasLiveLlmConfigInEnv, loadLiveLlmConfigFromEnv } from '../../../../harness/backend/llmConfig' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import type { BackendTestRuntime } from '../../../../harness/backend/test-types' import { buildSelectedDocxFixtureReferences, DOCX_READING_FIXTURE_PATHS, SHARED_WORKSPACE_FIXTURE_PATH -} from '../../../harness/backend/aiLiveTestData' +} from '../../../../harness/backend/aiLiveTestData' import { filterAiRunEventsByType, runWithStructuredOutputRetry } from '../testUtils' const SESSION_ID = 'live-session-resume-with-new-selection' diff --git a/tests/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts b/tests/case/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts similarity index 92% rename from tests/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts rename to tests/case/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts index 69f79daf..d822a3d5 100644 --- a/tests/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts +++ b/tests/case/ai/human/live/human-web-crawl-multi-hop-cross-domain-reading-live.test.ts @@ -1,12 +1,12 @@ import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { createWebCrawlerService } from '../../../harness/backend/webCrawlerTestUtils' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { createMultiHopCrossDomainWebCrawlSite } from '../../../harness/backend/webCrawlLiveTestSites' -import { buildHumanWebCrawlMultiHopCrossDomainUserInput } from '../../../harness/backend/aiLiveTestData' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { createWebCrawlerService } from '../../../../harness/backend/webCrawlerTestUtils' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import { createMultiHopCrossDomainWebCrawlSite } from '../../../../harness/backend/webCrawlLiveTestSites' +import { buildHumanWebCrawlMultiHopCrossDomainUserInput } from '../../../../harness/backend/aiLiveTestData' import { filterAiRunEventsByType, runWithStructuredOutputRetry } from '../testUtils' -import type { JsonObject, JsonValue } from '../../../../src/shared/types' +import type { JsonObject, JsonValue } from '../../../../../src/shared/types' const normalizeUrl = (value: string): string => value.replace(/\/$/, '') diff --git a/tests/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts b/tests/case/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts similarity index 91% rename from tests/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts rename to tests/case/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts index 95c83d10..a31047fe 100644 --- a/tests/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts +++ b/tests/case/ai/human/live/human-web-crawl-url-inference-reading-live.test.ts @@ -1,9 +1,9 @@ import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { createWebCrawlerService } from '../../../harness/backend/webCrawlerTestUtils' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { HUMAN_WEB_CRAWL_URL_INFERENCE_USER_INPUT } from '../../../harness/backend/aiLiveTestData' +import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { createWebCrawlerService } from '../../../../harness/backend/webCrawlerTestUtils' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import { HUMAN_WEB_CRAWL_URL_INFERENCE_USER_INPUT } from '../../../../harness/backend/aiLiveTestData' import { filterAiRunEventsByType, runWithStructuredOutputRetry } from '../testUtils' const isPandasHost = (url: string): boolean => new URL(url).hostname.endsWith('pandas.pydata.org') diff --git a/tests/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts b/tests/case/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts similarity index 95% rename from tests/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts rename to tests/case/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts index bbbccd98..efab3182 100644 --- a/tests/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts +++ b/tests/case/ai/human/mock-llm/ai-run-service-embedding-preflight.test.ts @@ -1,8 +1,8 @@ import { describe, it } from 'vitest' -import { AiRunService } from '../../../../src/main/services/ai/AiRunService' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { createScriptedStructuredOutputDriver } from '../../../harness/backend/scriptedStructuredOutputDriver' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' +import { AiRunService } from '../../../../../src/main/services/ai/AiRunService' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import { createScriptedStructuredOutputDriver } from '../../../../harness/backend/scriptedStructuredOutputDriver' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' import type { DocxSearchResult } from '../../tool/docx/testUtils' import { buildCompleteTaskDecision, diff --git a/tests/ai/human/mock-llm/ai-run-service-human-only.test.ts b/tests/case/ai/human/mock-llm/ai-run-service-human-only.test.ts similarity index 97% rename from tests/ai/human/mock-llm/ai-run-service-human-only.test.ts rename to tests/case/ai/human/mock-llm/ai-run-service-human-only.test.ts index 00b61b4d..2e594d65 100644 --- a/tests/ai/human/mock-llm/ai-run-service-human-only.test.ts +++ b/tests/case/ai/human/mock-llm/ai-run-service-human-only.test.ts @@ -1,7 +1,7 @@ import { describe, it } from 'vitest' -import { AiRunService } from '../../../../src/main/services/ai/AiRunService' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { createScriptedStructuredOutputDriver } from '../../../harness/backend/scriptedStructuredOutputDriver' +import { AiRunService } from '../../../../../src/main/services/ai/AiRunService' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import { createScriptedStructuredOutputDriver } from '../../../../harness/backend/scriptedStructuredOutputDriver' import { buildCompleteTaskDecision, buildLearnSkillsDecision, diff --git a/tests/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts b/tests/case/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts similarity index 95% rename from tests/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts rename to tests/case/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts index 09b733c9..8f8397f3 100644 --- a/tests/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts +++ b/tests/case/ai/human/mock-llm/ai-run-service-resume-snapshot.test.ts @@ -1,8 +1,8 @@ import { describe, it } from 'vitest' -import { AiRunService } from '../../../../src/main/services/ai/AiRunService' -import type { BackendTestRuntime } from '../../../harness/backend/test-types' -import { createScriptedStructuredOutputDriver } from '../../../harness/backend/scriptedStructuredOutputDriver' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { AiRunService } from '../../../../../src/main/services/ai/AiRunService' +import type { BackendTestRuntime } from '../../../../harness/backend/test-types' +import { createScriptedStructuredOutputDriver } from '../../../../harness/backend/scriptedStructuredOutputDriver' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildLearnSkillsDecision } from '../testUtils' const SESSION_ID = 'resume-snapshot-session' diff --git a/tests/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts b/tests/case/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts similarity index 95% rename from tests/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts rename to tests/case/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts index e69a690b..b65ba72f 100644 --- a/tests/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts +++ b/tests/case/ai/human/mock-llm/approval-denied-restricted-delivery.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildToolDecision, diff --git a/tests/ai/human/mock-llm/brain-memory-write-boundary.test.ts b/tests/case/ai/human/mock-llm/brain-memory-write-boundary.test.ts similarity index 96% rename from tests/ai/human/mock-llm/brain-memory-write-boundary.test.ts rename to tests/case/ai/human/mock-llm/brain-memory-write-boundary.test.ts index fc5803d5..6639ca39 100644 --- a/tests/ai/human/mock-llm/brain-memory-write-boundary.test.ts +++ b/tests/case/ai/human/mock-llm/brain-memory-write-boundary.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision } from '../testUtils' describe('ai human mock-llm backend', () => { diff --git a/tests/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts b/tests/case/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts similarity index 95% rename from tests/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts rename to tests/case/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts index 067c97dc..12a72cb6 100644 --- a/tests/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts +++ b/tests/case/ai/human/mock-llm/brain-output-schema-invalid-retry.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, filterAiRunEventsByType diff --git a/tests/ai/human/mock-llm/complex-docx-edit.test.ts b/tests/case/ai/human/mock-llm/complex-docx-edit.test.ts similarity index 98% rename from tests/ai/human/mock-llm/complex-docx-edit.test.ts rename to tests/case/ai/human/mock-llm/complex-docx-edit.test.ts index 3caac892..cdab681a 100644 --- a/tests/ai/human/mock-llm/complex-docx-edit.test.ts +++ b/tests/case/ai/human/mock-llm/complex-docx-edit.test.ts @@ -1,7 +1,7 @@ import { describe, it } from 'vitest' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxReadResult, DocxSearchResult } from '../../tool/docx/testUtils' import { buildCompleteTaskDecision, diff --git a/tests/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts b/tests/case/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts similarity index 95% rename from tests/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts rename to tests/case/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts index a88ba17e..9fb65917 100644 --- a/tests/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts +++ b/tests/case/ai/human/mock-llm/context-compaction-ratio-and-fact-retention.test.ts @@ -3,13 +3,13 @@ import { appendRoundRecord, buildHandExecution, buildLearningExecution -} from '../../../../src/main/services/ai/human/memory/rounds' -import { createBrainNode } from '../../../../src/main/services/ai/human/node/brainNode' -import { createContextCompactionNode } from '../../../../src/main/services/ai/human/node/contextCompactionNode' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import type { BackendTestRuntime } from '../../../harness/backend/test-types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/main/services/ai/human/memory/rounds' +import { createBrainNode } from '../../../../../src/main/services/ai/human/node/brainNode' +import { createContextCompactionNode } from '../../../../../src/main/services/ai/human/node/contextCompactionNode' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import type { BackendTestRuntime } from '../../../../harness/backend/test-types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, filterAiRunEventsByType diff --git a/tests/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts b/tests/case/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts similarity index 95% rename from tests/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts rename to tests/case/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts index 7f534740..d0d96641 100644 --- a/tests/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts +++ b/tests/case/ai/human/mock-llm/conversation-memory-multi-run-recall.test.ts @@ -1,8 +1,8 @@ import { describe, it } from 'vitest' -import { updateConversationMemoryAfterRun } from '../../../../src/main/services/ai/memory' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import { createScriptedStructuredOutputDriver } from '../../../harness/backend/scriptedStructuredOutputDriver' +import { updateConversationMemoryAfterRun } from '../../../../../src/main/services/ai/memory' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import { createScriptedStructuredOutputDriver } from '../../../../harness/backend/scriptedStructuredOutputDriver' import { buildCompleteTaskDecision } from '../testUtils' const SESSION_ID = 'conversation-memory-multi-run-session' diff --git a/tests/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts b/tests/case/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts similarity index 97% rename from tests/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts rename to tests/case/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts index 7d4458da..55510e6a 100644 --- a/tests/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts +++ b/tests/case/ai/human/mock-llm/decision-hint-contract-and-recovery.test.ts @@ -1,8 +1,8 @@ import path from 'node:path' import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildLearnSkillsDecision, diff --git a/tests/ai/human/mock-llm/decision-hint-retry.test.ts b/tests/case/ai/human/mock-llm/decision-hint-retry.test.ts similarity index 95% rename from tests/ai/human/mock-llm/decision-hint-retry.test.ts rename to tests/case/ai/human/mock-llm/decision-hint-retry.test.ts index 78f33c0f..ba341cfd 100644 --- a/tests/ai/human/mock-llm/decision-hint-retry.test.ts +++ b/tests/case/ai/human/mock-llm/decision-hint-retry.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildLearnSkillsDecision, diff --git a/tests/ai/human/mock-llm/docx-read-fact-exposure.test.ts b/tests/case/ai/human/mock-llm/docx-read-fact-exposure.test.ts similarity index 96% rename from tests/ai/human/mock-llm/docx-read-fact-exposure.test.ts rename to tests/case/ai/human/mock-llm/docx-read-fact-exposure.test.ts index 8a1d53b0..b7c78b7c 100644 --- a/tests/ai/human/mock-llm/docx-read-fact-exposure.test.ts +++ b/tests/case/ai/human/mock-llm/docx-read-fact-exposure.test.ts @@ -1,7 +1,7 @@ import { describe, it } from 'vitest' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxReadResult, DocxSearchResult } from '../../tool/docx/testUtils' import { buildCompleteTaskDecision, diff --git a/tests/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts b/tests/case/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts similarity index 95% rename from tests/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts rename to tests/case/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts index a1463fd1..661eb0a3 100644 --- a/tests/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts +++ b/tests/case/ai/human/mock-llm/docx-search-alias-then-range-read.test.ts @@ -1,7 +1,7 @@ import { describe, it } from 'vitest' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxReadResult, DocxSearchResult } from '../../tool/docx/testUtils' import { buildCompleteTaskDecision, diff --git a/tests/ai/human/mock-llm/kb-answer-with-real-kb.test.ts b/tests/case/ai/human/mock-llm/kb-answer-with-real-kb.test.ts similarity index 95% rename from tests/ai/human/mock-llm/kb-answer-with-real-kb.test.ts rename to tests/case/ai/human/mock-llm/kb-answer-with-real-kb.test.ts index ea6c4b34..216ecbe6 100644 --- a/tests/ai/human/mock-llm/kb-answer-with-real-kb.test.ts +++ b/tests/case/ai/human/mock-llm/kb-answer-with-real-kb.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildToolDecision, diff --git a/tests/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts b/tests/case/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts similarity index 96% rename from tests/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts rename to tests/case/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts index 9e25e1a7..ff2881cc 100644 --- a/tests/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts +++ b/tests/case/ai/human/mock-llm/learning-hand-mouth-smoke.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildLearnSkillsDecision, diff --git a/tests/ai/human/mock-llm/learning-tool-budget-tracking.test.ts b/tests/case/ai/human/mock-llm/learning-tool-budget-tracking.test.ts similarity index 96% rename from tests/ai/human/mock-llm/learning-tool-budget-tracking.test.ts rename to tests/case/ai/human/mock-llm/learning-tool-budget-tracking.test.ts index 5c4744dc..f23e7590 100644 --- a/tests/ai/human/mock-llm/learning-tool-budget-tracking.test.ts +++ b/tests/case/ai/human/mock-llm/learning-tool-budget-tracking.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildLearnSkillsDecision, diff --git a/tests/ai/human/mock-llm/multi-tool-collaboration.test.ts b/tests/case/ai/human/mock-llm/multi-tool-collaboration.test.ts similarity index 95% rename from tests/ai/human/mock-llm/multi-tool-collaboration.test.ts rename to tests/case/ai/human/mock-llm/multi-tool-collaboration.test.ts index 61e86132..7869d5af 100644 --- a/tests/ai/human/mock-llm/multi-tool-collaboration.test.ts +++ b/tests/case/ai/human/mock-llm/multi-tool-collaboration.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, filterAiRunEventsByType diff --git a/tests/ai/human/mock-llm/todolist-memory-maintenance.test.ts b/tests/case/ai/human/mock-llm/todolist-memory-maintenance.test.ts similarity index 97% rename from tests/ai/human/mock-llm/todolist-memory-maintenance.test.ts rename to tests/case/ai/human/mock-llm/todolist-memory-maintenance.test.ts index 19a4a068..2d270501 100644 --- a/tests/ai/human/mock-llm/todolist-memory-maintenance.test.ts +++ b/tests/case/ai/human/mock-llm/todolist-memory-maintenance.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { filterAiRunEventsByType } from '../testUtils' import { buildToolDecision } from '../testUtils' diff --git a/tests/ai/human/mock-llm/tool-failure-recovery.test.ts b/tests/case/ai/human/mock-llm/tool-failure-recovery.test.ts similarity index 94% rename from tests/ai/human/mock-llm/tool-failure-recovery.test.ts rename to tests/case/ai/human/mock-llm/tool-failure-recovery.test.ts index 78683381..a903379a 100644 --- a/tests/ai/human/mock-llm/tool-failure-recovery.test.ts +++ b/tests/case/ai/human/mock-llm/tool-failure-recovery.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildToolDecision, diff --git a/tests/ai/human/mock-llm/tool-limit-converge.test.ts b/tests/case/ai/human/mock-llm/tool-limit-converge.test.ts similarity index 94% rename from tests/ai/human/mock-llm/tool-limit-converge.test.ts rename to tests/case/ai/human/mock-llm/tool-limit-converge.test.ts index 20c3f04b..c62bd1ef 100644 --- a/tests/ai/human/mock-llm/tool-limit-converge.test.ts +++ b/tests/case/ai/human/mock-llm/tool-limit-converge.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, buildToolDecision, diff --git a/tests/ai/human/testUtils.ts b/tests/case/ai/human/testUtils.ts similarity index 99% rename from tests/ai/human/testUtils.ts rename to tests/case/ai/human/testUtils.ts index 5f100698..5f0491ae 100644 --- a/tests/ai/human/testUtils.ts +++ b/tests/case/ai/human/testUtils.ts @@ -3,8 +3,8 @@ import type { HumanState, HumanThought, RuntimeSkillId -} from '../../../src/main/services/ai/human/types' -import type { AiArtifactRef, AiRunEvent, JsonArray, JsonObject, JsonValue } from '../../../src/shared/types' +} from '../../../../src/main/services/ai/human/types' +import type { AiArtifactRef, AiRunEvent, JsonArray, JsonObject, JsonValue } from '../../../../src/shared/types' export const filterAiRunEventsByType = ( events: readonly AiRunEvent[], diff --git a/tests/ai/tool/.gitkeep b/tests/case/ai/tool/.gitkeep similarity index 100% rename from tests/ai/tool/.gitkeep rename to tests/case/ai/tool/.gitkeep diff --git a/tests/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts b/tests/case/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts rename to tests/case/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts index 4d9c3cfc..607d4826 100644 --- a/tests/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts +++ b/tests/case/ai/tool/docx/docx-apply-ops-invalid-snapshot-guard.test.ts @@ -5,8 +5,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxFailureResult, type DocxMutationResult, diff --git a/tests/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts b/tests/case/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts similarity index 91% rename from tests/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts rename to tests/case/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts index ba131d67..44632273 100644 --- a/tests/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts +++ b/tests/case/ai/tool/docx/docx-auto-open-no-fake-failure.test.ts @@ -2,9 +2,9 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' -import type { AiRunEvent } from '../../../../src/shared/types' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' +import type { AiRunEvent } from '../../../../../src/shared/types' import type { DocxSearchResult } from './testUtils' const filterToolResults = (events: readonly AiRunEvent[], toolName: string) => diff --git a/tests/ai/tool/docx/docx-complex-ops-batch.test.ts b/tests/case/ai/tool/docx/docx-complex-ops-serial-actions.test.ts similarity index 98% rename from tests/ai/tool/docx/docx-complex-ops-batch.test.ts rename to tests/case/ai/tool/docx/docx-complex-ops-serial-actions.test.ts index 1ad1eda5..407c8971 100644 --- a/tests/ai/tool/docx/docx-complex-ops-batch.test.ts +++ b/tests/case/ai/tool/docx/docx-complex-ops-serial-actions.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-create-existing-path-guard.test.ts b/tests/case/ai/tool/docx/docx-create-existing-path-guard.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-create-existing-path-guard.test.ts rename to tests/case/ai/tool/docx/docx-create-existing-path-guard.test.ts index 45a4e703..20f34364 100644 --- a/tests/ai/tool/docx/docx-create-existing-path-guard.test.ts +++ b/tests/case/ai/tool/docx/docx-create-existing-path-guard.test.ts @@ -3,8 +3,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxFailureResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts b/tests/case/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts rename to tests/case/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts index a16bce7a..3a7c859e 100644 --- a/tests/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts +++ b/tests/case/ai/tool/docx/docx-create-target-appears-before-apply-becomes-update.test.ts @@ -4,8 +4,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxCreateResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-create-then-open-and-edit.test.ts b/tests/case/ai/tool/docx/docx-create-then-open-and-edit.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-create-then-open-and-edit.test.ts rename to tests/case/ai/tool/docx/docx-create-then-open-and-edit.test.ts index 01743d20..e48c15ac 100644 --- a/tests/ai/tool/docx/docx-create-then-open-and-edit.test.ts +++ b/tests/case/ai/tool/docx/docx-create-then-open-and-edit.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxCreateResult, DocxMutationResult, diff --git a/tests/ai/tool/docx/docx-create-with-content-direct-applies.test.ts b/tests/case/ai/tool/docx/docx-create-with-content-direct-applies.test.ts similarity index 95% rename from tests/ai/tool/docx/docx-create-with-content-direct-applies.test.ts rename to tests/case/ai/tool/docx/docx-create-with-content-direct-applies.test.ts index 830fb903..d681abae 100644 --- a/tests/ai/tool/docx/docx-create-with-content-direct-applies.test.ts +++ b/tests/case/ai/tool/docx/docx-create-with-content-direct-applies.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxCreateResult, DocxSearchResult } from './testUtils' const fileExists = async (filePath: string): Promise => { diff --git a/tests/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts b/tests/case/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts similarity index 98% rename from tests/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts rename to tests/case/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts index 6a0ae6a2..96b1cc26 100644 --- a/tests/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts +++ b/tests/case/ai/tool/docx/docx-direct-apply-checkpoint-revert.test.ts @@ -4,8 +4,8 @@ import JSZip from 'jszip' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxSearchResult } from './testUtils' const readDocumentXml = async (filePath: string): Promise => { diff --git a/tests/ai/tool/docx/docx-edit-insert-blocks-order.test.ts b/tests/case/ai/tool/docx/docx-edit-insert-blocks-order.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-edit-insert-blocks-order.test.ts rename to tests/case/ai/tool/docx/docx-edit-insert-blocks-order.test.ts index c033d822..9ebaa459 100644 --- a/tests/ai/tool/docx/docx-edit-insert-blocks-order.test.ts +++ b/tests/case/ai/tool/docx/docx-edit-insert-blocks-order.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-edit-serial-actions-stability.test.ts b/tests/case/ai/tool/docx/docx-edit-serial-actions-stability.test.ts similarity index 98% rename from tests/ai/tool/docx/docx-edit-serial-actions-stability.test.ts rename to tests/case/ai/tool/docx/docx-edit-serial-actions-stability.test.ts index 847dd30b..043adae9 100644 --- a/tests/ai/tool/docx/docx-edit-serial-actions-stability.test.ts +++ b/tests/case/ai/tool/docx/docx-edit-serial-actions-stability.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxFailureResult, type DocxMutationResult, diff --git a/tests/ai/tool/docx/docx-format-variants-contract.test.ts b/tests/case/ai/tool/docx/docx-format-variants-contract.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-format-variants-contract.test.ts rename to tests/case/ai/tool/docx/docx-format-variants-contract.test.ts index fe885611..787926fe 100644 --- a/tests/ai/tool/docx/docx-format-variants-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-format-variants-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-history-and-rollback.test.ts b/tests/case/ai/tool/docx/docx-history-and-rollback.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-history-and-rollback.test.ts rename to tests/case/ai/tool/docx/docx-history-and-rollback.test.ts index d003b7d2..43325f88 100644 --- a/tests/ai/tool/docx/docx-history-and-rollback.test.ts +++ b/tests/case/ai/tool/docx/docx-history-and-rollback.test.ts @@ -4,8 +4,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxHistoryResult, type DocxSearchResult, diff --git a/tests/ai/tool/docx/docx-large-document-navigation.test.ts b/tests/case/ai/tool/docx/docx-large-document-navigation.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-large-document-navigation.test.ts rename to tests/case/ai/tool/docx/docx-large-document-navigation.test.ts index 1fafcf7d..fa366654 100644 --- a/tests/ai/tool/docx/docx-large-document-navigation.test.ts +++ b/tests/case/ai/tool/docx/docx-large-document-navigation.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxOutlineResult, type DocxReadResult, diff --git a/tests/ai/tool/docx/docx-minimal-edit-flow.test.ts b/tests/case/ai/tool/docx/docx-minimal-edit-flow.test.ts similarity index 95% rename from tests/ai/tool/docx/docx-minimal-edit-flow.test.ts rename to tests/case/ai/tool/docx/docx-minimal-edit-flow.test.ts index fd8f6d8c..310a8721 100644 --- a/tests/ai/tool/docx/docx-minimal-edit-flow.test.ts +++ b/tests/case/ai/tool/docx/docx-minimal-edit-flow.test.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxReadResult, diff --git a/tests/ai/tool/docx/docx-mixed-format-preservation.test.ts b/tests/case/ai/tool/docx/docx-mixed-format-preservation.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-mixed-format-preservation.test.ts rename to tests/case/ai/tool/docx/docx-mixed-format-preservation.test.ts index 0e617ef4..87a6ca73 100644 --- a/tests/ai/tool/docx/docx-mixed-format-preservation.test.ts +++ b/tests/case/ai/tool/docx/docx-mixed-format-preservation.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeRichTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-multi-round-created-block-edit.test.ts b/tests/case/ai/tool/docx/docx-multi-round-created-block-edit.test.ts similarity index 98% rename from tests/ai/tool/docx/docx-multi-round-created-block-edit.test.ts rename to tests/case/ai/tool/docx/docx-multi-round-created-block-edit.test.ts index 4bae1fb0..b2b6f217 100644 --- a/tests/ai/tool/docx/docx-multi-round-created-block-edit.test.ts +++ b/tests/case/ai/tool/docx/docx-multi-round-created-block-edit.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-outline-pagination-contract.test.ts b/tests/case/ai/tool/docx/docx-outline-pagination-contract.test.ts similarity index 92% rename from tests/ai/tool/docx/docx-outline-pagination-contract.test.ts rename to tests/case/ai/tool/docx/docx-outline-pagination-contract.test.ts index 2b51feb5..bbd80da1 100644 --- a/tests/ai/tool/docx/docx-outline-pagination-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-outline-pagination-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxOutlineResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-read-does-not-create-replay-session.test.ts b/tests/case/ai/tool/docx/docx-outline-read-search-do-not-create-replay-session.test.ts similarity index 95% rename from tests/ai/tool/docx/docx-read-does-not-create-replay-session.test.ts rename to tests/case/ai/tool/docx/docx-outline-read-search-do-not-create-replay-session.test.ts index b37ad947..79dd17a3 100644 --- a/tests/ai/tool/docx/docx-read-does-not-create-replay-session.test.ts +++ b/tests/case/ai/tool/docx/docx-outline-read-search-do-not-create-replay-session.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxOutlineResult, DocxReadResult, DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-outline-truncation-contract.test.ts b/tests/case/ai/tool/docx/docx-outline-truncation-contract.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-outline-truncation-contract.test.ts rename to tests/case/ai/tool/docx/docx-outline-truncation-contract.test.ts index 14de0b38..cd8c4d13 100644 --- a/tests/ai/tool/docx/docx-outline-truncation-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-outline-truncation-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxOutlineResult } from './testUtils' import { filterAiRunEventsByType } from '../../human/testUtils' diff --git a/tests/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts b/tests/case/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts rename to tests/case/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts index 0f281165..a415e2b6 100644 --- a/tests/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-positioned-layout-read-only-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxFailureResult, DocxHistoryResult, diff --git a/tests/ai/tool/docx/docx-precondition-guard.test.ts b/tests/case/ai/tool/docx/docx-precondition-guard.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-precondition-guard.test.ts rename to tests/case/ai/tool/docx/docx-precondition-guard.test.ts index aa6a5c16..2e97e7e9 100644 --- a/tests/ai/tool/docx/docx-precondition-guard.test.ts +++ b/tests/case/ai/tool/docx/docx-precondition-guard.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createDocxToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createDocxToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxFailureResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-read-range-contract.test.ts b/tests/case/ai/tool/docx/docx-read-range-contract.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-read-range-contract.test.ts rename to tests/case/ai/tool/docx/docx-read-range-contract.test.ts index b0b0acc8..32e104e6 100644 --- a/tests/ai/tool/docx/docx-read-range-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-read-range-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxOutlineResult, DocxReadResult } from './testUtils' import { findParagraphBlockIndexByText } from './testUtils' diff --git a/tests/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts b/tests/case/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts rename to tests/case/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts index 70658bb3..ca99c9a8 100644 --- a/tests/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts +++ b/tests/case/ai/tool/docx/docx-read-then-edit-preserves-long-document-tail.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts b/tests/case/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts similarity index 95% rename from tests/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts rename to tests/case/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts index 94819445..c4f34d52 100644 --- a/tests/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts +++ b/tests/case/ai/tool/docx/docx-read-then-edit-upgrades-to-replay-session.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts b/tests/case/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts rename to tests/case/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts index 000f6abe..58e3ea57 100644 --- a/tests/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts +++ b/tests/case/ai/tool/docx/docx-read-then-history-upgrades-to-replay-session.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxHistoryResult, DocxOutlineResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts b/tests/case/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts rename to tests/case/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts index 5030df20..5faa7037 100644 --- a/tests/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts +++ b/tests/case/ai/tool/docx/docx-replay-stats-track-active-revision.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult } from './testUtils' import { findParagraphBlockIdByText } from './testUtils' diff --git a/tests/ai/tool/docx/docx-search-literal-alias-fallback.test.ts b/tests/case/ai/tool/docx/docx-search-literal-alias-fallback.test.ts similarity index 93% rename from tests/ai/tool/docx/docx-search-literal-alias-fallback.test.ts rename to tests/case/ai/tool/docx/docx-search-literal-alias-fallback.test.ts index 64ff69b8..5e766fbe 100644 --- a/tests/ai/tool/docx/docx-search-literal-alias-fallback.test.ts +++ b/tests/case/ai/tool/docx/docx-search-literal-alias-fallback.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-search-regex-contract.test.ts b/tests/case/ai/tool/docx/docx-search-regex-contract.test.ts similarity index 93% rename from tests/ai/tool/docx/docx-search-regex-contract.test.ts rename to tests/case/ai/tool/docx/docx-search-regex-contract.test.ts index e4a4b508..4e26338b 100644 --- a/tests/ai/tool/docx/docx-search-regex-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-search-regex-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxFailureResult, DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-search-scope-contract.test.ts b/tests/case/ai/tool/docx/docx-search-scope-contract.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-search-scope-contract.test.ts rename to tests/case/ai/tool/docx/docx-search-scope-contract.test.ts index 26a73f2e..8c116306 100644 --- a/tests/ai/tool/docx/docx-search-scope-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-search-scope-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-search-semantic-contract.test.ts b/tests/case/ai/tool/docx/docx-search-semantic-contract.test.ts similarity index 93% rename from tests/ai/tool/docx/docx-search-semantic-contract.test.ts rename to tests/case/ai/tool/docx/docx-search-semantic-contract.test.ts index ebc2b4f4..20bd95e9 100644 --- a/tests/ai/tool/docx/docx-search-semantic-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-search-semantic-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts b/tests/case/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts similarity index 94% rename from tests/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts rename to tests/case/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts index d948daa3..f9ce71a0 100644 --- a/tests/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts +++ b/tests/case/ai/tool/docx/docx-search-unicode-normalization-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxSearchResult } from './testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/docx-structural-op-behavior.test.ts b/tests/case/ai/tool/docx/docx-structural-op-behavior.test.ts similarity index 97% rename from tests/ai/tool/docx/docx-structural-op-behavior.test.ts rename to tests/case/ai/tool/docx/docx-structural-op-behavior.test.ts index b01f3eda..e7571555 100644 --- a/tests/ai/tool/docx/docx-structural-op-behavior.test.ts +++ b/tests/case/ai/tool/docx/docx-structural-op-behavior.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { type DocxMutationResult, type DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts b/tests/case/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts rename to tests/case/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts index 694a47e1..aa486d0d 100644 --- a/tests/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts +++ b/tests/case/ai/tool/docx/docx-working-draft-hides-deleted-content.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, diff --git a/tests/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts b/tests/case/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts similarity index 96% rename from tests/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts rename to tests/case/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts index 7d1c9e64..5474ce2e 100644 --- a/tests/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts +++ b/tests/case/ai/tool/docx/docx-write-then-outline-percent-sees-new-block.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult } from './testUtils' import { findParagraphBlockIdByText } from './testUtils' diff --git a/tests/ai/tool/docx/testUtils.ts b/tests/case/ai/tool/docx/testUtils.ts similarity index 99% rename from tests/ai/tool/docx/testUtils.ts rename to tests/case/ai/tool/docx/testUtils.ts index 5f8df96f..624208cd 100644 --- a/tests/ai/tool/docx/testUtils.ts +++ b/tests/case/ai/tool/docx/testUtils.ts @@ -1,4 +1,4 @@ -import type { DocxChangedBlock, DocxOutlineBlock } from '../../../../src/main/services/ai/human/tools/docx/types' +import type { DocxChangedBlock, DocxOutlineBlock } from '../../../../../src/main/services/ai/human/tools/docx/types' export type DocxOutlineResult = { sourcePath: string diff --git a/tests/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts b/tests/case/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts similarity index 96% rename from tests/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts rename to tests/case/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts index f6db8645..395c64ba 100644 --- a/tests/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts +++ b/tests/case/ai/tool/docx/write-stability/docx-created-blocks-resolve-inserted-range-not-shifted-tail.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../../harness/backend/run-backend-case' +} from '../../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult } from '../testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts b/tests/case/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts similarity index 96% rename from tests/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts rename to tests/case/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts index 3c14b272..5bf466f2 100644 --- a/tests/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts +++ b/tests/case/ai/tool/docx/write-stability/docx-insert-after-preserves-anchor-xml.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeRichTestDocxFile -} from '../../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../../harness/backend/run-backend-case' +} from '../../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult, diff --git a/tests/case/ai/tool/docx/write-stability/docx-insert-does-not-duplicate-downstream-blocks.test.ts b/tests/case/ai/tool/docx/write-stability/docx-insert-does-not-duplicate-downstream-blocks.test.ts new file mode 100644 index 00000000..4094695e --- /dev/null +++ b/tests/case/ai/tool/docx/write-stability/docx-insert-does-not-duplicate-downstream-blocks.test.ts @@ -0,0 +1,166 @@ +import fs from 'node:fs/promises' +import { describe, it } from 'vitest' +import { + buildBlockIndex, + loadDocxBundle +} from '../../../../../../src/main/services/ai/human/tools/docx/adapters/ooxml/blockIndex' +import { + createDocxToolHarness, + writeRichTestDocxFile +} from '../../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../../harness/backend/run-backend-case' +import type { DocxMutationResult, DocxOutlineResult, DocxReadResult } from '../testUtils' + +describe('ai tool docx backend', () => { + it('docx-insert-does-not-duplicate-downstream-blocks', async () => { + await runBackendCase({ + caseId: 'docx-insert-does-not-duplicate-downstream-blocks', + harnessMode: 'tool-real', + domain: 'ai', + suite: 'tool', + groups: ['docx', 'write-stability'], + description: '验证结构插入后的 review projection 不会重复下游旧段落。', + fixtures: [], + runMode: 'single', + execute: async (runtime) => { + const sourcePath = await writeRichTestDocxFile( + runtime.workspaceRoot, + 'insert-after-no-duplicate-tail.docx', + [ + { + type: 'paragraph', + paragraph: { + runs: [ + { text: '学生可以通过', bold: true }, + { text: '点击对话中的按钮', color: '2563EB' }, + { text: '进入引导流程。', underline: true } + ] + } + }, + { + type: 'paragraph', + paragraph: { + runs: [{ text: '学生端脚手架引导:' }] + } + }, + { + type: 'paragraph', + paragraph: { + runs: [{ text: '引导完成后进入正式练习。' }] + } + } + ] + ) + + const harness = createDocxToolHarness(runtime) + try { + harness.ctx.beginDocxApplyRun() + const outlineBefore = await harness.invoke('docx_outline', { + sourcePath: 'insert-after-no-duplicate-tail.docx', + ranges: [{ type: 'block_index', fromBlockIndex: 0, toBlockIndex: 49 }] + }) + const anchorBlockId = + outlineBefore.blocks.find( + (block) => + block.type === 'paragraph' && + block.text.includes('学生可以通过点击对话中的按钮进入引导流程。') + )?.blockId || '' + + const anchorBefore = await harness.invoke('docx_read_blocks', { + sourcePath: 'insert-after-no-duplicate-tail.docx', + range: { type: 'block_ids', blockIds: [anchorBlockId] } + }) + const insertedParagraph = await harness.invoke('docx_edit', { + sourcePath: 'insert-after-no-duplicate-tail.docx', + actions: [ + { + type: 'insert_blocks', + anchorBlockId, + position: 'after', + blocks: [ + { + type: 'paragraph', + text: '学生可以根据按钮提示逐步完成脚手架操作。' + } + ] + } + ] + }) + const insertedParagraphBlockId = + insertedParagraph.createdBlocks?.find((block) => block.type === 'paragraph')?.blockId || + '' + const insertedTable = await harness.invoke('docx_edit', { + sourcePath: insertedParagraph.sourcePath, + actions: [ + { + type: 'insert_blocks', + anchorBlockId: insertedParagraphBlockId, + position: 'after', + blocks: [ + { + type: 'table', + rows: [ + ['入口按钮', '对应动作'], + ['开始引导', '进入脚手架'] + ] + } + ] + } + ] + }) + const anchorAfter = await harness.invoke('docx_read_blocks', { + sourcePath: insertedTable.sourcePath, + range: { type: 'block_ids', blockIds: [anchorBlockId] } + }) + const records = await harness.ctx.applyDocxRunEdits() + if (records.length < 1) throw new Error('expected docx apply record') + + const reviewBundle = await loadDocxBundle(await fs.readFile(sourcePath)) + const reviewOutline = buildBlockIndex(reviewBundle.documentDom) + .outline.filter((block) => block.type !== 'table_cell') + .map((block) => ({ + type: block.type, + text: block.type === 'table' ? block.preview : block.text + })) + + return { + anchorBefore: anchorBefore.blocks[0], + anchorAfter: anchorAfter.blocks[0], + records, + reviewOutline + } + } finally { + await harness.release() + } + }, + assert: async (result, runtime) => { + runtime.assertThat( + result.anchorBefore?.type === 'paragraph' && + result.anchorAfter?.type === 'paragraph' && + JSON.stringify(result.anchorBefore.runs) === JSON.stringify(result.anchorAfter.runs), + { + name: 'anchor-runs-remain-identical-after-structural-insert', + message: '结构插入后,anchor 原段落的 run 样式不应发生漂移。', + data: { + anchorBefore: result.anchorBefore, + anchorAfter: result.anchorAfter + } + } + ) + + runtime.assertThat( + result.reviewOutline.filter((block) => + String(block.text || '').includes('学生端脚手架引导:') + ).length === 1, + { + name: 'review-does-not-duplicate-downstream-tail-block', + message: 'review artifact 中下游旧段落只应出现一次,不得因插入重绑错误而重复。', + data: { + reviewOutline: result.reviewOutline + } + } + ) + } + }) + }) +}) diff --git a/tests/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts b/tests/case/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts similarity index 95% rename from tests/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts rename to tests/case/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts index c4d5d536..5c118594 100644 --- a/tests/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts +++ b/tests/case/ai/tool/docx/write-stability/docx-inserted-table-created-blocks-include-table-and-cells.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import { createDocxToolHarness, writeTestDocxFile -} from '../../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../../harness/backend/run-backend-case' +} from '../../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../../harness/backend/run-backend-case' import type { DocxMutationResult, DocxOutlineResult } from '../testUtils' describe('ai tool docx backend', () => { diff --git a/tests/ai/tool/hand/hand-call-limit.test.ts b/tests/case/ai/tool/hand/hand-call-limit.test.ts similarity index 81% rename from tests/ai/tool/hand/hand-call-limit.test.ts rename to tests/case/ai/tool/hand/hand-call-limit.test.ts index 4442fa23..c6e0b0f4 100644 --- a/tests/ai/tool/hand/hand-call-limit.test.ts +++ b/tests/case/ai/tool/hand/hand-call-limit.test.ts @@ -1,10 +1,10 @@ import { describe, it } from 'vitest' -import { HUMAN_HAND_CALL_LIMIT } from '../../../../src/main/services/ai/human/constants' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { HUMAN_HAND_CALL_LIMIT } from '../../../../../src/main/services/ai/human/constants' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const isWindows = process.platform === 'win32' diff --git a/tests/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts b/tests/case/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts similarity index 84% rename from tests/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts rename to tests/case/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts index 44682619..d68353b5 100644 --- a/tests/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts +++ b/tests/case/ai/tool/hand/hand-docx-edit-invalid-blockid-failed.test.ts @@ -1,10 +1,10 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('ai tool hand backend', () => { it('hand-docx-edit-invalid-blockid-failed', async () => { diff --git a/tests/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts b/tests/case/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts similarity index 87% rename from tests/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts rename to tests/case/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts index 5bce4a4c..6c8388f6 100644 --- a/tests/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts +++ b/tests/case/ai/tool/hand/hand-docx-multi-file-single-write-allowed.test.ts @@ -1,11 +1,11 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { executeToolCall } from '../../../../src/main/services/ai/human/node/handNode/toolExecutor' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { executeToolCall } from '../../../../../src/main/services/ai/human/node/handNode/toolExecutor' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import type { DocxOutlineResult } from '../docx/testUtils' describe('ai tool hand backend', () => { diff --git a/tests/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts b/tests/case/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts similarity index 86% rename from tests/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts rename to tests/case/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts index 9262de83..f81c9c4e 100644 --- a/tests/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts +++ b/tests/case/ai/tool/hand/hand-docx-same-file-read-write-read-allowed.test.ts @@ -1,10 +1,10 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('ai tool hand backend', () => { it('hand-docx-same-file-read-write-read-allowed', async () => { diff --git a/tests/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts b/tests/case/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts similarity index 85% rename from tests/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts rename to tests/case/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts index 7daa3645..4128209b 100644 --- a/tests/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts +++ b/tests/case/ai/tool/hand/hand-docx-single-file-multi-write-allowed.test.ts @@ -1,10 +1,10 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('ai tool hand backend', () => { it('hand-docx-single-file-multi-write-allowed', async () => { diff --git a/tests/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts b/tests/case/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts similarity index 86% rename from tests/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts rename to tests/case/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts index a713f363..9a4236bd 100644 --- a/tests/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts +++ b/tests/case/ai/tool/hand/hand-schema-preflight-invalid-input.test.ts @@ -1,9 +1,9 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('ai tool hand backend', () => { it('hand-schema-preflight-invalid-input', async () => { diff --git a/tests/ai/tool/hand/hand-serial-docx-chain.test.ts b/tests/case/ai/tool/hand/hand-serial-docx-chain.test.ts similarity index 89% rename from tests/ai/tool/hand/hand-serial-docx-chain.test.ts rename to tests/case/ai/tool/hand/hand-serial-docx-chain.test.ts index d933203d..0e4bd92a 100644 --- a/tests/ai/tool/hand/hand-serial-docx-chain.test.ts +++ b/tests/case/ai/tool/hand/hand-serial-docx-chain.test.ts @@ -1,10 +1,10 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('ai tool hand backend', () => { it('hand-serial-docx-chain', async () => { diff --git a/tests/ai/tool/hand/hand-terminal-approval-batch.test.ts b/tests/case/ai/tool/hand/hand-terminal-approval-batch.test.ts similarity index 97% rename from tests/ai/tool/hand/hand-terminal-approval-batch.test.ts rename to tests/case/ai/tool/hand/hand-terminal-approval-batch.test.ts index bf4f804f..b003a247 100644 --- a/tests/ai/tool/hand/hand-terminal-approval-batch.test.ts +++ b/tests/case/ai/tool/hand/hand-terminal-approval-batch.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createHumanTestRunner } from '../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { buildCompleteTaskDecision, filterAiRunEventsByType diff --git a/tests/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts b/tests/case/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts similarity index 63% rename from tests/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts rename to tests/case/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts index 32e47202..661079b1 100644 --- a/tests/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts +++ b/tests/case/ai/tool/hand/hand-tool-limit-and-artifact-merge.test.ts @@ -1,11 +1,11 @@ import { describe, it } from 'vitest' -import { createHandNode } from '../../../../src/main/services/ai/human/node/handNode' -import { ensureSkillTools } from '../../../../src/main/services/ai/human/node/learningNode/skillLoader' -import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' -import type { AiArtifactRef } from '../../../../src/shared/types' -import { writeTestDocxFile } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createHandNode } from '../../../../../src/main/services/ai/human/node/handNode' +import { ensureSkillTools } from '../../../../../src/main/services/ai/human/node/learningNode/skillLoader' +import { createHumanRuntimeCache } from '../../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../../src/main/services/ai/human/state/schema' +import type { AiArtifactRef } from '../../../../../src/shared/types' +import { writeTestDocxFile } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const isWindows = process.platform === 'win32' @@ -48,90 +48,6 @@ const buildHandState = (input: { }) describe('ai tool hand backend', () => { - it('hand-tool-limit-near-exhausted', async () => { - await runBackendCase({ - caseId: 'hand-tool-limit-near-exhausted', - harnessMode: 'tool-real', - domain: 'ai', - suite: 'tool', - groups: ['hand'], - description: '验证 hand 在达到工具预算 0.6 阈值后会发出尽快收尾的提示。', - fixtures: [], - runMode: 'single', - execute: async (runtime) => { - const ctx = runtime.createAiRunContext({ - humanRuntimeOverrides: { - hardActionLimit: 5 - } - }) - const cache = createHumanRuntimeCache() - - ensureSkillTools({ - cache, - ctx, - runId: runtime.runId, - taskId: runtime.taskId, - skillId: 'terminal' - }) - - const handNode = createHandNode({ ctx, cache }) - const firstState = await handNode( - buildHandState({ - taskId: runtime.taskId, - userInput: '第一次安全指令', - learnedSkillIds: [], - toolName: 'terminal_run', - args: { - command: isWindows ? 'Get-Location' : 'pwd' - } - }) - ) - const secondState = await handNode({ - ...buildHandState({ - taskId: runtime.taskId, - userInput: '第二次安全指令', - learnedSkillIds: [], - toolName: 'terminal_run', - args: { - command: isWindows ? 'Get-ChildItem -LiteralPath .' : 'ls' - } - }), - trace: firstState.trace - }) - const thirdState = await handNode({ - ...buildHandState({ - taskId: runtime.taskId, - userInput: '第三次安全指令', - learnedSkillIds: [], - toolName: 'terminal_run', - args: { - command: isWindows ? 'Get-Location' : 'pwd' - } - }), - trace: secondState.trace - }) - - return { - thirdState - } - }, - assert: async (result, runtime) => { - runtime.assertThat( - result.thirdState.execution.pendingDecisionHint?.code === 'hand_tool_limit_near_exhausted' && - result.thirdState.execution.pendingDecisionHint.detail.limit === 5 && - result.thirdState.execution.pendingDecisionHint.detail.toolCallCount === 3, - { - name: 'tool-limit-near-exhausted-produces-soft-converging-decision-hint', - message: '达到工具预算 0.6 阈值后,hand 应写入尽快收尾的软提示。', - data: { - decisionHint: result.thirdState.execution.pendingDecisionHint - } - } - ) - } - }) - }) - it('hand-tool-limit-and-artifact-merge', async () => { await runBackendCase({ caseId: 'hand-tool-limit-and-artifact-merge', diff --git a/tests/ai/tool/kb/kb-evidence-search-family-contract.test.ts b/tests/case/ai/tool/kb/kb-evidence-search-family-contract.test.ts similarity index 95% rename from tests/ai/tool/kb/kb-evidence-search-family-contract.test.ts rename to tests/case/ai/tool/kb/kb-evidence-search-family-contract.test.ts index 6b3a9165..fdb2fae6 100644 --- a/tests/ai/tool/kb/kb-evidence-search-family-contract.test.ts +++ b/tests/case/ai/tool/kb/kb-evidence-search-family-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createRetrievalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createRetrievalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' type KbSearchRow = { id: string diff --git a/tests/ai/tool/kb/kb-list-documents-contract.test.ts b/tests/case/ai/tool/kb/kb-list-documents-contract.test.ts similarity index 95% rename from tests/ai/tool/kb/kb-list-documents-contract.test.ts rename to tests/case/ai/tool/kb/kb-list-documents-contract.test.ts index a49a0304..1ea59177 100644 --- a/tests/ai/tool/kb/kb-list-documents-contract.test.ts +++ b/tests/case/ai/tool/kb/kb-list-documents-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createRetrievalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createRetrievalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' type KbListDocumentsResult = { items: Array<{ diff --git a/tests/ai/tool/logHelpers.ts b/tests/case/ai/tool/logHelpers.ts similarity index 78% rename from tests/ai/tool/logHelpers.ts rename to tests/case/ai/tool/logHelpers.ts index 33614d2e..f68eaff7 100644 --- a/tests/ai/tool/logHelpers.ts +++ b/tests/case/ai/tool/logHelpers.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' -import { resolveRunLogDir } from '../../harness/backend/write-run-log' -import type { TestArtifactsIndex, TestRunMeta } from '../../harness/core/run-log-contract' +import { resolveRunLogDir } from '../../../harness/backend/write-run-log' +import type { TestArtifactsIndex, TestRunMeta } from '../../../harness/core/run-log-contract' export const readCapturedRunLog = async (input: { scope: string diff --git a/tests/ai/tool/terminal/terminal-directory-inspection-safe.test.ts b/tests/case/ai/tool/terminal/terminal-directory-inspection-safe.test.ts similarity index 95% rename from tests/ai/tool/terminal/terminal-directory-inspection-safe.test.ts rename to tests/case/ai/tool/terminal/terminal-directory-inspection-safe.test.ts index f8df511b..c7b4dbc3 100644 --- a/tests/ai/tool/terminal/terminal-directory-inspection-safe.test.ts +++ b/tests/case/ai/tool/terminal/terminal-directory-inspection-safe.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createTerminalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createTerminalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { filterEventsByType } from '../testHelpers' type TerminalRunCommandResult = { diff --git a/tests/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts b/tests/case/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts similarity index 96% rename from tests/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts rename to tests/case/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts index a27a72c1..e95bbcad 100644 --- a/tests/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts +++ b/tests/case/ai/tool/terminal/terminal-inline-edit-artifact-contract.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, expect, it } from 'vitest' -import { createTerminalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createTerminalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { filterEventsByType } from '../testHelpers' import { readCapturedRunLog } from '../logHelpers' diff --git a/tests/ai/tool/terminal/terminal-result-unusable-output.test.ts b/tests/case/ai/tool/terminal/terminal-result-unusable-docx-output.test.ts similarity index 95% rename from tests/ai/tool/terminal/terminal-result-unusable-output.test.ts rename to tests/case/ai/tool/terminal/terminal-result-unusable-docx-output.test.ts index 624b4e18..9148fa19 100644 --- a/tests/ai/tool/terminal/terminal-result-unusable-output.test.ts +++ b/tests/case/ai/tool/terminal/terminal-result-unusable-docx-output.test.ts @@ -3,8 +3,8 @@ import { describe, it } from 'vitest' import { createTerminalToolHarness, writeTestDocxFile -} from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' type TerminalRunCommandResult = { reason?: 'result_unusable' diff --git a/tests/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts b/tests/case/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts similarity index 67% rename from tests/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts rename to tests/case/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts index e5b6684b..94222a5c 100644 --- a/tests/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts +++ b/tests/case/ai/tool/terminal/terminal-risk-and-boundary-guard.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createTerminalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createTerminalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { filterEventsByType } from '../testHelpers' type TerminalRunCommandResult = { @@ -25,6 +25,7 @@ describe('ai tool terminal backend', () => { runMode: 'single', execute: async (runtime) => { const guardedDir = path.join(runtime.workspaceRoot, 'guarded') + const blockedDir = path.join(runtime.workspaceRoot, 'blocked-dir') const outsidePath = path.join(path.dirname(runtime.workspaceRoot), 'outside.txt') await fs.mkdir(guardedDir, { recursive: true }) await fs.writeFile(path.join(guardedDir, 'keep.txt'), 'keep\n') @@ -37,6 +38,11 @@ describe('ai tool terminal backend', () => { unsafeDenied: await harness.invoke('terminal_run', { command: isWindows ? 'Remove-Item -Recurse -Force guarded' : 'rm -rf guarded' }), + unlistedWriteDenied: await harness.invoke('terminal_run', { + command: isWindows + ? 'New-Item -ItemType Directory -Path blocked-dir' + : 'mkdir blocked-dir' + }), relativeEscape: await harness.invoke('terminal_run', { command: isWindows ? 'Get-Content ../outside.txt' : 'cat ../outside.txt' }), @@ -47,6 +53,10 @@ describe('ai tool terminal backend', () => { .access(path.join(guardedDir, 'keep.txt')) .then(() => true) .catch(() => false), + blockedDirExists: await fs + .access(blockedDir) + .then(() => true) + .catch(() => false), events: runtime.eventCollector.snapshot() } }, @@ -59,7 +69,8 @@ describe('ai tool terminal backend', () => { approvalEvents.some((event) => event.items.some( (item) => - item.command === (isWindows ? 'Remove-Item -Recurse -Force guarded' : 'rm -rf guarded') + item.command === + (isWindows ? 'Remove-Item -Recurse -Force guarded' : 'rm -rf guarded') ) ) && result.guardedStillExists, @@ -77,6 +88,32 @@ describe('ai tool terminal backend', () => { } ) + runtime.assertThat( + result.unlistedWriteDenied.reason === 'approval_denied' && + result.blockedDirExists === false && + approvalEvents.some((event) => + event.items.some( + (item) => + item.command === + (isWindows + ? 'New-Item -ItemType Directory -Path blocked-dir' + : 'mkdir blocked-dir') + ) + ), + { + name: 'unlisted-write-command-requires-approval', + message: '未命中安全指令集的低风险写命令也必须先审批;审批拒绝后不得创建目录。', + data: { + unlistedWriteDenied: result.unlistedWriteDenied, + blockedDirExists: result.blockedDirExists, + approvals: approvalEvents.map((event) => ({ + command: event.items[0]?.command, + reason: event.items[0]?.reason + })) + } + } + ) + runtime.assertThat( result.relativeEscape.reason === 'workspace_required' && result.absoluteEscape.reason === 'workspace_required' && diff --git a/tests/ai/tool/terminal/terminal-text-search-safe.test.ts b/tests/case/ai/tool/terminal/terminal-text-search-safe.test.ts similarity index 95% rename from tests/ai/tool/terminal/terminal-text-search-safe.test.ts rename to tests/case/ai/tool/terminal/terminal-text-search-safe.test.ts index cd5aa594..85510d21 100644 --- a/tests/ai/tool/terminal/terminal-text-search-safe.test.ts +++ b/tests/case/ai/tool/terminal/terminal-text-search-safe.test.ts @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { createTerminalToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createTerminalToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' import { filterEventsByType } from '../testHelpers' type TerminalRunCommandResult = { diff --git a/tests/ai/tool/testHelpers.ts b/tests/case/ai/tool/testHelpers.ts similarity index 88% rename from tests/ai/tool/testHelpers.ts rename to tests/case/ai/tool/testHelpers.ts index 55e1ab86..ec63b2ec 100644 --- a/tests/ai/tool/testHelpers.ts +++ b/tests/case/ai/tool/testHelpers.ts @@ -1,5 +1,5 @@ -import type { DocumentRecord, SearchResult } from '../../../src/shared/types' -import type { AiRunEvent } from '../../../src/shared/types' +import type { DocumentRecord, SearchResult } from '../../../../src/shared/types' +import type { AiRunEvent } from '../../../../src/shared/types' export const createTestDocumentRecord = (input: { id: string diff --git a/tests/ai/tool/utility/wait-time-contract.test.ts b/tests/case/ai/tool/utility/wait-time-contract.test.ts similarity index 87% rename from tests/ai/tool/utility/wait-time-contract.test.ts rename to tests/case/ai/tool/utility/wait-time-contract.test.ts index d4a3d158..bf0446da 100644 --- a/tests/ai/tool/utility/wait-time-contract.test.ts +++ b/tests/case/ai/tool/utility/wait-time-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { createUtilityToolHarness } from '../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createUtilityToolHarness } from '../../../../harness/backend/aiToolHarness' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' type WaitTimeResult = { waitedMs: number diff --git a/tests/ai/tool/web/web-crawl-contract.test.ts b/tests/case/ai/tool/web-browsing/web-crawl-contract.test.ts similarity index 97% rename from tests/ai/tool/web/web-crawl-contract.test.ts rename to tests/case/ai/tool/web-browsing/web-crawl-contract.test.ts index f69a61e9..1e900d5a 100644 --- a/tests/ai/tool/web/web-crawl-contract.test.ts +++ b/tests/case/ai/tool/web-browsing/web-crawl-contract.test.ts @@ -1,8 +1,8 @@ import http from 'node:http' import { describe, it } from 'vitest' -import { createWebToolHarness } from '../../../harness/backend/aiToolHarness' -import { createWebCrawlerService } from '../../../harness/backend/webCrawlerTestUtils' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { createWebToolHarness } from '../../../../harness/backend/aiToolHarness' +import { createWebCrawlerService } from '../../../../harness/backend/webCrawlerTestUtils' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' type WebCrawlResult = { finalUrl?: string diff --git a/tests/settings/model/.gitkeep b/tests/case/settings/model/.gitkeep similarity index 100% rename from tests/settings/model/.gitkeep rename to tests/case/settings/model/.gitkeep diff --git a/tests/settings/model/model-active-config-change-event.e2e.test.ts b/tests/case/settings/model/model-active-config-change-event.e2e.test.ts similarity index 94% rename from tests/settings/model/model-active-config-change-event.e2e.test.ts rename to tests/case/settings/model/model-active-config-change-event.e2e.test.ts index 90beb9c8..806e993c 100644 --- a/tests/settings/model/model-active-config-change-event.e2e.test.ts +++ b/tests/case/settings/model/model-active-config-change-event.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, seedModelConfigs -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SEEDED_CONFIGS: readonly LLMConfig[] = [ { diff --git a/tests/settings/model/model-config-list-contract.test.ts b/tests/case/settings/model/model-config-list-contract.test.ts similarity index 95% rename from tests/settings/model/model-config-list-contract.test.ts rename to tests/case/settings/model/model-config-list-contract.test.ts index c05c90b1..1e3c548c 100644 --- a/tests/settings/model/model-config-list-contract.test.ts +++ b/tests/case/settings/model/model-config-list-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { runBackendCase } from '../../harness/backend/run-backend-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { runBackendCase } from '../../../harness/backend/run-backend-case' const MODEL_FIXTURES: readonly LLMConfig[] = [ { diff --git a/tests/settings/model/model-create-edit-form.e2e.test.ts b/tests/case/settings/model/model-create-edit-form.e2e.test.ts similarity index 96% rename from tests/settings/model/model-create-edit-form.e2e.test.ts rename to tests/case/settings/model/model-create-edit-form.e2e.test.ts index d0c6c48d..61946ee2 100644 --- a/tests/settings/model/model-create-edit-form.e2e.test.ts +++ b/tests/case/settings/model/model-create-edit-form.e2e.test.ts @@ -1,11 +1,11 @@ import { describe, it } from 'vitest' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' describe('settings model e2e', () => { it('model-create-edit-form', async () => { diff --git a/tests/settings/model/model-delete-config-feedback.e2e.test.ts b/tests/case/settings/model/model-delete-config-feedback.e2e.test.ts similarity index 94% rename from tests/settings/model/model-delete-config-feedback.e2e.test.ts rename to tests/case/settings/model/model-delete-config-feedback.e2e.test.ts index 795d4724..b5d3f7ff 100644 --- a/tests/settings/model/model-delete-config-feedback.e2e.test.ts +++ b/tests/case/settings/model/model-delete-config-feedback.e2e.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, seedModelConfigs, setSettingsE2EApiOverrides -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SEEDED_CONFIGS: readonly LLMConfig[] = [ { diff --git a/tests/settings/model/model-enable-switch-current.e2e.test.ts b/tests/case/settings/model/model-enable-switch-current.e2e.test.ts similarity index 93% rename from tests/settings/model/model-enable-switch-current.e2e.test.ts rename to tests/case/settings/model/model-enable-switch-current.e2e.test.ts index ef8712d1..c884063d 100644 --- a/tests/settings/model/model-enable-switch-current.e2e.test.ts +++ b/tests/case/settings/model/model-enable-switch-current.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, seedModelConfigs -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SEEDED_CONFIGS: readonly LLMConfig[] = [ { diff --git a/tests/settings/model/model-first-config-auto-activate.e2e.test.ts b/tests/case/settings/model/model-first-config-auto-activate.e2e.test.ts similarity index 94% rename from tests/settings/model/model-first-config-auto-activate.e2e.test.ts rename to tests/case/settings/model/model-first-config-auto-activate.e2e.test.ts index a3b9a887..da604880 100644 --- a/tests/settings/model/model-first-config-auto-activate.e2e.test.ts +++ b/tests/case/settings/model/model-first-config-auto-activate.e2e.test.ts @@ -1,12 +1,12 @@ import type { Page } from 'playwright' import { describe, it } from 'vitest' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const fillModelForm = async (page: Page): Promise => { await getSettingsLocator(page, settingsSelectors.modelBaseUrlInput) diff --git a/tests/settings/model/model-page-load-active-config.e2e.test.ts b/tests/case/settings/model/model-page-load-active-config.e2e.test.ts similarity index 92% rename from tests/settings/model/model-page-load-active-config.e2e.test.ts rename to tests/case/settings/model/model-page-load-active-config.e2e.test.ts index 26797955..2ea1b08d 100644 --- a/tests/settings/model/model-page-load-active-config.e2e.test.ts +++ b/tests/case/settings/model/model-page-load-active-config.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, seedModelConfigs -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SEEDED_CONFIGS: readonly LLMConfig[] = [ { diff --git a/tests/settings/model/model-remote-list-refresh.e2e.test.ts b/tests/case/settings/model/model-remote-list-refresh.e2e.test.ts similarity index 93% rename from tests/settings/model/model-remote-list-refresh.e2e.test.ts rename to tests/case/settings/model/model-remote-list-refresh.e2e.test.ts index 2c6e2ab6..d1b29418 100644 --- a/tests/settings/model/model-remote-list-refresh.e2e.test.ts +++ b/tests/case/settings/model/model-remote-list-refresh.e2e.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import type { LLMConfig } from '../../../src/shared/types' -import { settingsSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { LLMConfig } from '../../../../src/shared/types' +import { settingsSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, seedModelConfigs, setSettingsE2EApiOverrides -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SEEDED_CONFIG: LLMConfig = { id: 'model-refresh-config', diff --git a/tests/settings/vector/.gitkeep b/tests/case/settings/vector/.gitkeep similarity index 100% rename from tests/settings/vector/.gitkeep rename to tests/case/settings/vector/.gitkeep diff --git a/tests/settings/vector/vector-delete-feedback.e2e.test.ts b/tests/case/settings/vector/vector-delete-feedback.e2e.test.ts similarity index 96% rename from tests/settings/vector/vector-delete-feedback.e2e.test.ts rename to tests/case/settings/vector/vector-delete-feedback.e2e.test.ts index dfd7a53d..ec9465b0 100644 --- a/tests/settings/vector/vector-delete-feedback.e2e.test.ts +++ b/tests/case/settings/vector/vector-delete-feedback.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { ModelInfo } from '../../../src/preload/api' -import { vectorSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { ModelInfo } from '../../../../src/preload/api' +import { vectorSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const DELETE_SUCCESS_SEQUENCE: readonly ModelInfo[][] = [ [ diff --git a/tests/settings/vector/vector-download-navigation-resume.e2e.test.ts b/tests/case/settings/vector/vector-download-navigation-resume.e2e.test.ts similarity index 93% rename from tests/settings/vector/vector-download-navigation-resume.e2e.test.ts rename to tests/case/settings/vector/vector-download-navigation-resume.e2e.test.ts index 3dabb427..5c9fd792 100644 --- a/tests/settings/vector/vector-download-navigation-resume.e2e.test.ts +++ b/tests/case/settings/vector/vector-download-navigation-resume.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { ActiveDownloadSnapshot, ModelInfo } from '../../../src/preload/api' -import { vectorSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { ActiveDownloadSnapshot, ModelInfo } from '../../../../src/preload/api' +import { vectorSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const MODEL_CATALOG: readonly ModelInfo[] = [ { diff --git a/tests/settings/vector/vector-download-progress.e2e.test.ts b/tests/case/settings/vector/vector-download-progress.e2e.test.ts similarity index 92% rename from tests/settings/vector/vector-download-progress.e2e.test.ts rename to tests/case/settings/vector/vector-download-progress.e2e.test.ts index 800cafec..455d34c5 100644 --- a/tests/settings/vector/vector-download-progress.e2e.test.ts +++ b/tests/case/settings/vector/vector-download-progress.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { ModelInfo } from '../../../src/preload/api' -import { vectorSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { ModelInfo } from '../../../../src/preload/api' +import { vectorSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const MODEL_CATALOG: readonly ModelInfo[] = [ { diff --git a/tests/settings/vector/vector-download-result-feedback.e2e.test.ts b/tests/case/settings/vector/vector-download-result-feedback.e2e.test.ts similarity index 95% rename from tests/settings/vector/vector-download-result-feedback.e2e.test.ts rename to tests/case/settings/vector/vector-download-result-feedback.e2e.test.ts index 60aa8de7..bd54fdf0 100644 --- a/tests/settings/vector/vector-download-result-feedback.e2e.test.ts +++ b/tests/case/settings/vector/vector-download-result-feedback.e2e.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import type { ModelInfo } from '../../../src/preload/api' -import { vectorSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { ModelInfo } from '../../../../src/preload/api' +import { vectorSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides, waitForAntMessage -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const SUCCESS_SEQUENCE: readonly ModelInfo[][] = [ [ diff --git a/tests/settings/vector/vector-download-single-task-contract.test.ts b/tests/case/settings/vector/vector-download-single-task-contract.test.ts similarity index 91% rename from tests/settings/vector/vector-download-single-task-contract.test.ts rename to tests/case/settings/vector/vector-download-single-task-contract.test.ts index 62189ab4..17b442eb 100644 --- a/tests/settings/vector/vector-download-single-task-contract.test.ts +++ b/tests/case/settings/vector/vector-download-single-task-contract.test.ts @@ -1,12 +1,12 @@ import { afterEach, describe, it, vi } from 'vitest' -import { EmbeddingService } from '../../../src/main/services/vector/EmbeddingService' -import { ModelService } from '../../../src/main/services/model/ModelService' -import { VectorModelDownloadService } from '../../../src/main/services/model/VectorModelDownloadService' +import { EmbeddingService } from '../../../../src/main/services/vector/EmbeddingService' +import { ModelService } from '../../../../src/main/services/model/ModelService' +import { VectorModelDownloadService } from '../../../../src/main/services/model/VectorModelDownloadService' import type { ModelDownloadProgress, ModelDownloadProgressReporter -} from '../../../src/main/services/model/ModelService' -import { runBackendCase } from '../../harness/backend/run-backend-case' +} from '../../../../src/main/services/model/ModelService' +import { runBackendCase } from '../../../harness/backend/run-backend-case' const REPO_A = 'jinaai/jina-embeddings-v2-base-zh' const REPO_B = 'jinaai/jina-embeddings-v2-base-zh-secondary' diff --git a/tests/settings/vector/vector-download-startup-resume-contract.test.ts b/tests/case/settings/vector/vector-download-startup-resume-contract.test.ts similarity index 92% rename from tests/settings/vector/vector-download-startup-resume-contract.test.ts rename to tests/case/settings/vector/vector-download-startup-resume-contract.test.ts index 96b2a891..de4c0c32 100644 --- a/tests/settings/vector/vector-download-startup-resume-contract.test.ts +++ b/tests/case/settings/vector/vector-download-startup-resume-contract.test.ts @@ -1,15 +1,15 @@ import fs from 'fs' import path from 'path' import { afterEach, describe, it, vi } from 'vitest' -import { MODELS_PATH } from '../../../src/main/utils/paths' -import { EmbeddingService } from '../../../src/main/services/vector/EmbeddingService' -import { ModelService } from '../../../src/main/services/model/ModelService' -import { VectorModelDownloadService } from '../../../src/main/services/model/VectorModelDownloadService' +import { MODELS_PATH } from '../../../../src/main/utils/paths' +import { EmbeddingService } from '../../../../src/main/services/vector/EmbeddingService' +import { ModelService } from '../../../../src/main/services/model/ModelService' +import { VectorModelDownloadService } from '../../../../src/main/services/model/VectorModelDownloadService' import type { ModelDownloadProgress, ModelDownloadProgressReporter -} from '../../../src/main/services/model/ModelService' -import { runBackendCase } from '../../harness/backend/run-backend-case' +} from '../../../../src/main/services/model/ModelService' +import { runBackendCase } from '../../../harness/backend/run-backend-case' const ACTIVE_DOWNLOAD_SNAPSHOT_FILE = '.active-vector-download.json' const SNAPSHOT_REPO_ID = 'jinaai/jina-embeddings-v2-base-zh' diff --git a/tests/settings/vector/vector-model-catalog-load.e2e.test.ts b/tests/case/settings/vector/vector-model-catalog-load.e2e.test.ts similarity index 92% rename from tests/settings/vector/vector-model-catalog-load.e2e.test.ts rename to tests/case/settings/vector/vector-model-catalog-load.e2e.test.ts index 62fcd701..99e4a6c8 100644 --- a/tests/settings/vector/vector-model-catalog-load.e2e.test.ts +++ b/tests/case/settings/vector/vector-model-catalog-load.e2e.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import type { ModelInfo } from '../../../src/preload/api' -import { vectorSelectors } from '../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' +import type { ModelInfo } from '../../../../src/preload/api' +import { vectorSelectors } from '../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides, waitForAntMessage -} from '../../harness/e2e/settings' +} from '../../../harness/e2e/settings' const MODEL_CATALOG: readonly ModelInfo[] = [ { diff --git a/tests/settings/workspace/.gitkeep b/tests/case/settings/workspace/.gitkeep similarity index 100% rename from tests/settings/workspace/.gitkeep rename to tests/case/settings/workspace/.gitkeep diff --git a/tests/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts b/tests/case/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts similarity index 95% rename from tests/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts rename to tests/case/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts index e4f285c8..90afe3ae 100644 --- a/tests/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts +++ b/tests/case/settings/workspace/core/workspace-add-selection-flow.e2e.test.ts @@ -1,11 +1,11 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace e2e', () => { it('workspace-add-selection-flow', async () => { diff --git a/tests/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts b/tests/case/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts similarity index 94% rename from tests/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts rename to tests/case/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts index c49ba423..0661dd55 100644 --- a/tests/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts +++ b/tests/case/settings/workspace/core/workspace-empty-tree-legacy-hints.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import type { WorkspaceListItem } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import type { WorkspaceListItem } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const NOW = 1_710_100_000_000 diff --git a/tests/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts b/tests/case/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts similarity index 97% rename from tests/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts rename to tests/case/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts index a80f6b55..acb28445 100644 --- a/tests/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts +++ b/tests/case/settings/workspace/core/workspace-page-load-default-selection.e2e.test.ts @@ -1,14 +1,14 @@ import { describe, it } from 'vitest' -import type { WorkspaceListItem, WorkspaceTreeListResponse } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import type { WorkspaceListItem, WorkspaceTreeListResponse } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const NOW = 1_710_000_000_000 diff --git a/tests/settings/workspace/core/workspace-remove-from-list.e2e.test.ts b/tests/case/settings/workspace/core/workspace-remove-from-list.e2e.test.ts similarity index 93% rename from tests/settings/workspace/core/workspace-remove-from-list.e2e.test.ts rename to tests/case/settings/workspace/core/workspace-remove-from-list.e2e.test.ts index 19087235..30340c1e 100644 --- a/tests/settings/workspace/core/workspace-remove-from-list.e2e.test.ts +++ b/tests/case/settings/workspace/core/workspace-remove-from-list.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, getSettingsPrefixLocator, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace e2e', () => { it('workspace-remove-from-list', async () => { diff --git a/tests/settings/workspace/core/workspace-remove-source-contract.test.ts b/tests/case/settings/workspace/core/workspace-remove-source-contract.test.ts similarity index 96% rename from tests/settings/workspace/core/workspace-remove-source-contract.test.ts rename to tests/case/settings/workspace/core/workspace-remove-source-contract.test.ts index f834bc07..64e79b7e 100644 --- a/tests/settings/workspace/core/workspace-remove-source-contract.test.ts +++ b/tests/case/settings/workspace/core/workspace-remove-source-contract.test.ts @@ -3,8 +3,8 @@ import type { WebSourceRecord, WorkspaceFileIndexRecord, WorkspaceSource -} from '../../../../src/shared/types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/shared/types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const NOW = 1_716_000_000_000 diff --git a/tests/settings/workspace/core/workspace-set-root-path.e2e.test.ts b/tests/case/settings/workspace/core/workspace-set-root-path.e2e.test.ts similarity index 93% rename from tests/settings/workspace/core/workspace-set-root-path.e2e.test.ts rename to tests/case/settings/workspace/core/workspace-set-root-path.e2e.test.ts index 6922a790..b32c1a02 100644 --- a/tests/settings/workspace/core/workspace-set-root-path.e2e.test.ts +++ b/tests/case/settings/workspace/core/workspace-set-root-path.e2e.test.ts @@ -1,15 +1,15 @@ import type { Page } from 'playwright' import { describe, it } from 'vitest' -import type { WorkspaceTreeListResponse } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import type { WorkspaceTreeListResponse } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, navigateToSettingsSection, setSettingsE2EApiOverrides -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const waitForWorkspaceTree = async ( page: Page, diff --git a/tests/settings/workspace/files/realWorkspaceHelpers.ts b/tests/case/settings/workspace/files/realWorkspaceHelpers.ts similarity index 95% rename from tests/settings/workspace/files/realWorkspaceHelpers.ts rename to tests/case/settings/workspace/files/realWorkspaceHelpers.ts index 25f8f993..248531a3 100644 --- a/tests/settings/workspace/files/realWorkspaceHelpers.ts +++ b/tests/case/settings/workspace/files/realWorkspaceHelpers.ts @@ -2,15 +2,15 @@ import { mkdir, writeFile, cp, rm, mkdtemp } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import type { Page } from 'playwright' -import type { WorkspaceIndexJobRecord, WorkspaceTreeListResponse } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' +import type { WorkspaceIndexJobRecord, WorkspaceTreeListResponse } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' import { createRealWorkspaceWithRoot, getSettingsLocator, navigateToSettingsSection, readWorkspaceTree, waitForFileIndexJobIdle -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' export type RealWorkspacePrepareResult = { workspaceId: string diff --git a/tests/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts b/tests/case/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts similarity index 94% rename from tests/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts index 6cd0e791..3b7e82d2 100644 --- a/tests/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-detail-status-and-error.e2e.test.ts @@ -1,15 +1,15 @@ import type { Page } from 'playwright' import { describe, it } from 'vitest' -import type { WorkspaceTreeListResponse } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import type { WorkspaceTreeListResponse } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, getWorkspaceIndexCounts, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const waitForWorkspaceTree = async ( page: Page, diff --git a/tests/settings/workspace/files/workspace-index-job-store-contract.test.ts b/tests/case/settings/workspace/files/workspace-index-job-store-contract.test.ts similarity index 96% rename from tests/settings/workspace/files/workspace-index-job-store-contract.test.ts rename to tests/case/settings/workspace/files/workspace-index-job-store-contract.test.ts index 6a6b7aad..6cddd960 100644 --- a/tests/settings/workspace/files/workspace-index-job-store-contract.test.ts +++ b/tests/case/settings/workspace/files/workspace-index-job-store-contract.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import type { WorkspaceIndexJobRecord } from '../../../../src/shared/types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import type { WorkspaceIndexJobRecord } from '../../../../../src/shared/types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const NOW = 1_716_200_000_000 diff --git a/tests/settings/workspace/files/workspace-index-worker-contract.test.ts b/tests/case/settings/workspace/files/workspace-index-worker-contract.test.ts similarity index 94% rename from tests/settings/workspace/files/workspace-index-worker-contract.test.ts rename to tests/case/settings/workspace/files/workspace-index-worker-contract.test.ts index 96b27564..a327f070 100644 --- a/tests/settings/workspace/files/workspace-index-worker-contract.test.ts +++ b/tests/case/settings/workspace/files/workspace-index-worker-contract.test.ts @@ -6,17 +6,17 @@ import { WORKSPACE_EMBEDDING_BATCH_CHUNK_COUNT, WORKSPACE_EMBEDDING_BATCH_MAX_CHARS, WORKSPACE_EMBEDDING_CHUNK_MAX_CHARS -} from '../../../../src/main/services/workspace/constants' -import { IngestionService } from '../../../../src/main/services/ingestion/loader' +} from '../../../../../src/main/services/workspace/constants' +import { IngestionService } from '../../../../../src/main/services/ingestion/loader' import { buildWorkspaceKnowledgeSplitSnapshot, computeWorkspaceFileContentHash, createWorkspaceEmbeddingBatches -} from '../../../../src/main/services/workspace/utils/knowledge' -import { handleWorkspaceIndexWorkerRequest } from '../../../../src/main/services/workspace/worker/workspaceIndexWorker' -import { WorkspaceIndexWorkerClient } from '../../../../src/main/services/workspace/worker/workspaceIndexWorkerClient' -import type { WorkspaceIndexWorkerRequest } from '../../../../src/main/services/workspace/types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/main/services/workspace/utils/knowledge' +import { handleWorkspaceIndexWorkerRequest } from '../../../../../src/main/services/workspace/worker/workspaceIndexWorker' +import { WorkspaceIndexWorkerClient } from '../../../../../src/main/services/workspace/worker/workspaceIndexWorkerClient' +import type { WorkspaceIndexWorkerRequest } from '../../../../../src/main/services/workspace/types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const createChunk = (length: number, page: number): { text: string; page: number } => ({ text: 'x'.repeat(length), diff --git a/tests/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts b/tests/case/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts similarity index 97% rename from tests/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts rename to tests/case/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts index c84339e7..7be28db3 100644 --- a/tests/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts +++ b/tests/case/settings/workspace/files/workspace-interrupted-job-recovery-contract.test.ts @@ -4,8 +4,8 @@ import type { WorkspaceFileIndexRecord, WorkspaceIndexJobRecord, WorkspaceSource -} from '../../../../src/shared/types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/shared/types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const NOW = 1_716_200_000_000 const WORKSPACE: WorkspaceSource = { diff --git a/tests/settings/workspace/files/workspace-prepare-contract.test.ts b/tests/case/settings/workspace/files/workspace-prepare-contract.test.ts similarity index 97% rename from tests/settings/workspace/files/workspace-prepare-contract.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-contract.test.ts index e5636677..196a4c18 100644 --- a/tests/settings/workspace/files/workspace-prepare-contract.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-contract.test.ts @@ -2,8 +2,8 @@ import { describe, it } from 'vitest' import type { WorkspaceFileIndexRecord, WorkspaceIndexJobRecord -} from '../../../../src/shared/types' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +} from '../../../../../src/shared/types' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' const NOW = 1_716_200_000_000 diff --git a/tests/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts b/tests/case/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts similarity index 94% rename from tests/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts index 6c1051f5..3abf1811 100644 --- a/tests/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-failed-retry.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_REAL_WORKSPACE_E2E_TIMEOUT_MS, createRealWorkspaceWithRoot, @@ -8,7 +8,7 @@ import { getWorkspaceIndexCounts, navigateToSettingsSection, waitForFileIndexJobIdle -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' import { cleanupRealWorkspace, createWorkspaceWithFiles, diff --git a/tests/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts b/tests/case/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts similarity index 95% rename from tests/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts index 45d807df..0cc18825 100644 --- a/tests/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-file-failure-isolated.e2e.test.ts @@ -1,15 +1,15 @@ import { cp, mkdir } from 'node:fs/promises' import path from 'node:path' import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_REAL_WORKSPACE_E2E_TIMEOUT_MS, createRealWorkspaceWithRoot, getSettingsLocator, navigateToSettingsSection, waitForFileIndexJobIdle -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' import { cleanupRealWorkspace, createWorkspaceWithFiles, diff --git a/tests/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts b/tests/case/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts similarity index 95% rename from tests/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts index 4d2e6405..1a99d085 100644 --- a/tests/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-progress-restore.e2e.test.ts @@ -1,12 +1,12 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace e2e', () => { it( diff --git a/tests/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts b/tests/case/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts similarity index 96% rename from tests/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts index d950b0f9..e2aa9518 100644 --- a/tests/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-real-fixture-refresh.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' -import { SETTINGS_REAL_WORKSPACE_E2E_TIMEOUT_MS } from '../../../harness/e2e/settings' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' +import { SETTINGS_REAL_WORKSPACE_E2E_TIMEOUT_MS } from '../../../../harness/e2e/settings' import { cleanupRealWorkspace, copySharedWorkspaceFixture, diff --git a/tests/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts b/tests/case/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts similarity index 94% rename from tests/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts index aa1652a5..d99d2b8d 100644 --- a/tests/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-prepare-refresh.e2e.test.ts @@ -1,14 +1,14 @@ import type { Page } from 'playwright' import { describe, it } from 'vitest' -import type { WorkspaceTreeListResponse } from '../../../../src/shared/types' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import type { WorkspaceTreeListResponse } from '../../../../../src/shared/types' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const waitForWorkspaceTree = async ( page: Page, diff --git a/tests/settings/workspace/files/workspace-scan-contract.test.ts b/tests/case/settings/workspace/files/workspace-scan-contract.test.ts similarity index 97% rename from tests/settings/workspace/files/workspace-scan-contract.test.ts rename to tests/case/settings/workspace/files/workspace-scan-contract.test.ts index 76dfb4c3..00b0385d 100644 --- a/tests/settings/workspace/files/workspace-scan-contract.test.ts +++ b/tests/case/settings/workspace/files/workspace-scan-contract.test.ts @@ -1,5 +1,5 @@ import { describe, it } from 'vitest' -import { runBackendCase } from '../../../harness/backend/run-backend-case' +import { runBackendCase } from '../../../../harness/backend/run-backend-case' describe('settings workspace backend', () => { it('workspace-scan-contract', async () => { diff --git a/tests/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts b/tests/case/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts similarity index 95% rename from tests/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts rename to tests/case/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts index f8f7869f..6932ad23 100644 --- a/tests/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts +++ b/tests/case/settings/workspace/files/workspace-scan-progress-refresh.e2e.test.ts @@ -1,12 +1,12 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_E2E_TIMEOUT_MS, createFixtureWorkspace, getSettingsLocator, navigateToSettingsSection -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace e2e', () => { it('workspace-scan-progress-refresh', async () => { diff --git a/tests/settings/workspace/web/web-add-sources-dialog.e2e.test.ts b/tests/case/settings/workspace/web/web-add-sources-dialog.e2e.test.ts similarity index 95% rename from tests/settings/workspace/web/web-add-sources-dialog.e2e.test.ts rename to tests/case/settings/workspace/web/web-add-sources-dialog.e2e.test.ts index 7a5026de..c0d5163a 100644 --- a/tests/settings/workspace/web/web-add-sources-dialog.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-add-sources-dialog.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -8,7 +8,7 @@ import { getSettingsPrefixLocator, navigateToSettingsSection, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it('web-add-sources-dialog', async () => { diff --git a/tests/settings/workspace/web/web-detail-and-delete.e2e.test.ts b/tests/case/settings/workspace/web/web-detail-and-delete.e2e.test.ts similarity index 94% rename from tests/settings/workspace/web/web-detail-and-delete.e2e.test.ts rename to tests/case/settings/workspace/web/web-detail-and-delete.e2e.test.ts index fe111697..5b20b41a 100644 --- a/tests/settings/workspace/web/web-detail-and-delete.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-detail-and-delete.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -9,7 +9,7 @@ import { navigateToSettingsSection, seedWebSources, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it('web-detail-and-delete', async () => { diff --git a/tests/settings/workspace/web/web-open-url-contract.e2e.test.ts b/tests/case/settings/workspace/web/web-open-url-contract.e2e.test.ts similarity index 92% rename from tests/settings/workspace/web/web-open-url-contract.e2e.test.ts rename to tests/case/settings/workspace/web/web-open-url-contract.e2e.test.ts index 6eec5d56..54a5a98e 100644 --- a/tests/settings/workspace/web/web-open-url-contract.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-open-url-contract.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -9,7 +9,7 @@ import { seedWebSources, setSettingsE2EApiOverrides, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it('web-open-url-contract', async () => { diff --git a/tests/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts b/tests/case/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts similarity index 96% rename from tests/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts rename to tests/case/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts index 48fe5fa4..06cf6069 100644 --- a/tests/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-prepare-progress-restore.e2e.test.ts @@ -1,13 +1,13 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, navigateToSettingsSection, seedWebSources, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it( diff --git a/tests/settings/workspace/web/web-prepare-summary.e2e.test.ts b/tests/case/settings/workspace/web/web-prepare-summary.e2e.test.ts similarity index 97% rename from tests/settings/workspace/web/web-prepare-summary.e2e.test.ts rename to tests/case/settings/workspace/web/web-prepare-summary.e2e.test.ts index 1a810ca0..18a13418 100644 --- a/tests/settings/workspace/web/web-prepare-summary.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-prepare-summary.e2e.test.ts @@ -1,7 +1,7 @@ import { describe, it } from 'vitest' import type { Page } from 'playwright' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -10,7 +10,7 @@ import { seedWebSources, waitForWebIndexJobIdle, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' const waitForPreparedWebSources = async (page: Page, workspaceId: string): Promise => { const deadline = Date.now() + 5000 diff --git a/tests/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts b/tests/case/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts similarity index 94% rename from tests/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts rename to tests/case/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts index 90bae62f..22b1b1db 100644 --- a/tests/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts +++ b/tests/case/settings/workspace/web/web-scan-summary-refresh.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -8,7 +8,7 @@ import { seedWebSources, waitForWebIndexJobIdle, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it( diff --git a/tests/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts b/tests/case/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts similarity index 94% rename from tests/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts rename to tests/case/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts index 22c71c01..11a026cb 100644 --- a/tests/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts +++ b/tests/case/settings/workspace/web/workspace-detail-contains-web-management.e2e.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest' -import { importSelectors } from '../../../../src/renderer/src/testing/selectors' -import { runE2ECase } from '../../../harness/e2e/run-e2e-case' +import { importSelectors } from '../../../../../src/renderer/src/testing/selectors' +import { runE2ECase } from '../../../../harness/e2e/run-e2e-case' import { SETTINGS_WEB_E2E_TIMEOUT_MS, createFixtureWorkspace, @@ -8,7 +8,7 @@ import { navigateToSettingsSection, seedWebSources, workspaceCardLocator -} from '../../../harness/e2e/settings' +} from '../../../../harness/e2e/settings' describe('settings workspace web e2e', () => { it('workspace-detail-contains-web-management', async () => { diff --git a/tests/unit/ai/ai-file-change-service.test.ts b/tests/case/unit/ai/ai-file-change-service.test.ts similarity index 96% rename from tests/unit/ai/ai-file-change-service.test.ts rename to tests/case/unit/ai/ai-file-change-service.test.ts index d74576e1..2735db42 100644 --- a/tests/unit/ai/ai-file-change-service.test.ts +++ b/tests/case/unit/ai/ai-file-change-service.test.ts @@ -8,8 +8,8 @@ import type { GetAiFileChangeRecordResponse, ListAiFileChangeRecordsRequest, ListAiFileChangeRecordsResponse -} from '../../../src/shared/types' -import { AiFileChangeService } from '../../../src/main/services/ai/fileChanges/AiFileChangeService' +} from '../../../../src/shared/types' +import { AiFileChangeService } from '../../../../src/main/services/ai/fileChanges/AiFileChangeService' class MemoryAiFileChangeStore { private records = new Map() diff --git a/tests/unit/ai/brain-decision-hint-prompt-contract.test.ts b/tests/case/unit/ai/brain-decision-hint-prompt-contract.test.ts similarity index 97% rename from tests/unit/ai/brain-decision-hint-prompt-contract.test.ts rename to tests/case/unit/ai/brain-decision-hint-prompt-contract.test.ts index 56a1a59d..b20337ad 100644 --- a/tests/unit/ai/brain-decision-hint-prompt-contract.test.ts +++ b/tests/case/unit/ai/brain-decision-hint-prompt-contract.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { buildBrainPrompt } from '../../../src/main/services/ai/human/prompts/brain' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import type { AiRunContext } from '../../../src/main/services/ai/types' +import { buildBrainPrompt } from '../../../../src/main/services/ai/human/prompts/brain' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import type { AiRunContext } from '../../../../src/main/services/ai/types' const createFakeCtx = (): AiRunContext => ({ diff --git a/tests/unit/ai/chat-model-provider-registry.test.ts b/tests/case/unit/ai/chat-model-provider-registry.test.ts similarity index 90% rename from tests/unit/ai/chat-model-provider-registry.test.ts rename to tests/case/unit/ai/chat-model-provider-registry.test.ts index 1c7748d9..1181ebb4 100644 --- a/tests/unit/ai/chat-model-provider-registry.test.ts +++ b/tests/case/unit/ai/chat-model-provider-registry.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { createChatModel, getSupportedTransportsForConfig } from '../../../src/main/services/llm/chatModel' -import { AI_LLM_CALL_TIMEOUT_MS } from '../../../src/main/services/ai/constants' -import type { LlmRuntimeConfig } from '../../../src/shared/types' +import { createChatModel, getSupportedTransportsForConfig } from '../../../../src/main/services/llm/chatModel' +import { AI_LLM_CALL_TIMEOUT_MS } from '../../../../src/main/services/ai/constants' +import type { LlmRuntimeConfig } from '../../../../src/shared/types' const buildConfig = (input: Partial): LlmRuntimeConfig => ({ id: 'cfg', diff --git a/tests/unit/ai/context-budget-node.test.ts b/tests/case/unit/ai/context-budget-node.test.ts similarity index 93% rename from tests/unit/ai/context-budget-node.test.ts rename to tests/case/unit/ai/context-budget-node.test.ts index 60a147d5..79cdf245 100644 --- a/tests/unit/ai/context-budget-node.test.ts +++ b/tests/case/unit/ai/context-budget-node.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest' -import { createContextBudgetNode } from '../../../src/main/services/ai/human/node/contextBudgetNode' -import { createHumanRuntimeCache } from '../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' +import { createContextBudgetNode } from '../../../../src/main/services/ai/human/node/contextBudgetNode' +import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' import { appendRoundRecord, buildLearningExecution -} from '../../../src/main/services/ai/human/memory/rounds' -import type { AiRunContext } from '../../../src/main/services/ai/types' -import type { AiRunEvent } from '../../../src/shared/types' +} from '../../../../src/main/services/ai/human/memory/rounds' +import type { AiRunContext } from '../../../../src/main/services/ai/types' +import type { AiRunEvent } from '../../../../src/shared/types' const createBaseContext = (events: AiRunEvent[]): AiRunContext => ({ diff --git a/tests/unit/ai/conversation-memory-prompt.test.ts b/tests/case/unit/ai/conversation-memory-prompt.test.ts similarity index 95% rename from tests/unit/ai/conversation-memory-prompt.test.ts rename to tests/case/unit/ai/conversation-memory-prompt.test.ts index 6a7bfb7d..b63ee2b0 100644 --- a/tests/unit/ai/conversation-memory-prompt.test.ts +++ b/tests/case/unit/ai/conversation-memory-prompt.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { buildConversationMemoryPrompt } from '../../../src/main/services/ai/memory/prompts/conversation' +import { buildConversationMemoryPrompt } from '../../../../src/main/services/ai/memory/prompts/conversation' describe('ai unit conversation memory prompt', () => { it('renders semantic system and user sections for conversation memory updates', () => { diff --git a/tests/unit/ai/docx-tool-contract.test.ts b/tests/case/unit/ai/docx-tool-contract.test.ts similarity index 83% rename from tests/unit/ai/docx-tool-contract.test.ts rename to tests/case/unit/ai/docx-tool-contract.test.ts index 9cc77297..c0f11a72 100644 --- a/tests/unit/ai/docx-tool-contract.test.ts +++ b/tests/case/unit/ai/docx-tool-contract.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import { SKILL_DEFINITIONS } from '../../../src/main/services/ai/human/skills' +import { SKILL_DEFINITIONS } from '../../../../src/main/services/ai/human/skills' import { DOCX_TOOL_SPECS, DOCX_WRITE_TOOL_SPECS -} from '../../../src/main/services/ai/human/tools/docx/toolSpecs' +} from '../../../../src/main/services/ai/human/tools/docx/toolSpecs' const OLD_DOCX_WRITE_TOOL_NAMES = [ 'docx_create', diff --git a/tests/unit/ai/docx-write-schema-contract.test.ts b/tests/case/unit/ai/docx-write-schema-contract.test.ts similarity index 95% rename from tests/unit/ai/docx-write-schema-contract.test.ts rename to tests/case/unit/ai/docx-write-schema-contract.test.ts index 0b1a1901..88330cf6 100644 --- a/tests/unit/ai/docx-write-schema-contract.test.ts +++ b/tests/case/unit/ai/docx-write-schema-contract.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' import { docxEditSchema -} from '../../../src/main/services/ai/human/tools/docx/schemas' +} from '../../../../src/main/services/ai/human/tools/docx/schemas' describe('ai unit docx write schema contract', () => { it('accepts batch docx_edit actions', () => { diff --git a/tests/unit/ai/file-changes-renderer.test.ts b/tests/case/unit/ai/file-changes-renderer.test.ts similarity index 86% rename from tests/unit/ai/file-changes-renderer.test.ts rename to tests/case/unit/ai/file-changes-renderer.test.ts index 202eaa6d..2e407903 100644 --- a/tests/unit/ai/file-changes-renderer.test.ts +++ b/tests/case/unit/ai/file-changes-renderer.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import { renderHumanFileChangeLines } from '../../../src/main/services/ai/human/shared/runtimeRenderers' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import { renderHumanFileChangeLines } from '../../../../src/main/services/ai/human/shared/runtimeRenderers' describe('ai unit file changes renderer', () => { it('renders changed files in prompt mode', () => { diff --git a/tests/unit/ai/human-decision-hint-contract.test.ts b/tests/case/unit/ai/human-decision-hint-contract.test.ts similarity index 93% rename from tests/unit/ai/human-decision-hint-contract.test.ts rename to tests/case/unit/ai/human-decision-hint-contract.test.ts index 7329770d..01544749 100644 --- a/tests/unit/ai/human-decision-hint-contract.test.ts +++ b/tests/case/unit/ai/human-decision-hint-contract.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest' -import { HUMAN_DECISION_HINT_CODES } from '../../../src/main/services/ai/human/constants' +import { HUMAN_DECISION_HINT_CODES } from '../../../../src/main/services/ai/human/constants' import { renderDecisionHintDetail, renderDecisionHintForBrain -} from '../../../src/main/services/ai/human/prompts/decisionHints' -import { humanDecisionHintSchema } from '../../../src/main/services/ai/human/schema' -import type { HumanDecisionHint } from '../../../src/main/services/ai/human/types' +} from '../../../../src/main/services/ai/human/prompts/decisionHints' +import { humanDecisionHintSchema } from '../../../../src/main/services/ai/human/schema' +import type { HumanDecisionHint } from '../../../../src/main/services/ai/human/types' const HINT_FIXTURES: Record<(typeof HUMAN_DECISION_HINT_CODES)[number], HumanDecisionHint> = { learning_no_effect: { diff --git a/tests/unit/ai/human-delivery-guidance-prompt.test.ts b/tests/case/unit/ai/human-delivery-guidance-prompt.test.ts similarity index 93% rename from tests/unit/ai/human-delivery-guidance-prompt.test.ts rename to tests/case/unit/ai/human-delivery-guidance-prompt.test.ts index b890468a..25250f5a 100644 --- a/tests/unit/ai/human-delivery-guidance-prompt.test.ts +++ b/tests/case/unit/ai/human-delivery-guidance-prompt.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { renderDeliveryGuidanceForMouth } from '../../../src/main/services/ai/human/prompts/deliveryGuidance' +import { renderDeliveryGuidanceForMouth } from '../../../../src/main/services/ai/human/prompts/deliveryGuidance' describe('ai unit human delivery guidance prompt', () => { it('renders positioned docx guidance for mouth from diagnostic', () => { diff --git a/tests/unit/ai/human-hand-decision-hints.test.ts b/tests/case/unit/ai/human-hand-decision-hints.test.ts similarity index 96% rename from tests/unit/ai/human-hand-decision-hints.test.ts rename to tests/case/unit/ai/human-hand-decision-hints.test.ts index 6f688974..eff99b00 100644 --- a/tests/unit/ai/human-hand-decision-hints.test.ts +++ b/tests/case/unit/ai/human-hand-decision-hints.test.ts @@ -2,13 +2,13 @@ import { describe, expect, it } from 'vitest' import { deriveHandFollowupSignals, shouldStopSerialHandPlan -} from '../../../src/main/services/ai/human/node/handNode/followupSignals' +} from '../../../../src/main/services/ai/human/node/handNode/followupSignals' import { buildHandCallInvalidHint, buildHandToolRuntimeFailedHint, buildHandWorkspaceBoundaryBlockedHint, deriveGroupedHandFailureHints -} from '../../../src/main/services/ai/human/node/handNode/decisionHints' +} from '../../../../src/main/services/ai/human/node/handNode/decisionHints' describe('ai unit hand decision hints', () => { it('aggregates multiple invalid calls into one hand_call_invalid hint', () => { diff --git a/tests/unit/ai/human-hand-followup-signals.test.ts b/tests/case/unit/ai/human-hand-followup-signals.test.ts similarity index 98% rename from tests/unit/ai/human-hand-followup-signals.test.ts rename to tests/case/unit/ai/human-hand-followup-signals.test.ts index 9f869beb..3418ac64 100644 --- a/tests/unit/ai/human-hand-followup-signals.test.ts +++ b/tests/case/unit/ai/human-hand-followup-signals.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { deriveHandFollowupSignals } from '../../../src/main/services/ai/human/node/handNode/followupSignals' +import { deriveHandFollowupSignals } from '../../../../src/main/services/ai/human/node/handNode/followupSignals' describe('ai unit hand followup signals', () => { it('passes through hand_tool_result_too_large using the shared decision hint contract', () => { diff --git a/tests/unit/ai/human-memory-renderers.test.ts b/tests/case/unit/ai/human-memory-renderers.test.ts similarity index 97% rename from tests/unit/ai/human-memory-renderers.test.ts rename to tests/case/unit/ai/human-memory-renderers.test.ts index ccfd9c21..b99fc508 100644 --- a/tests/unit/ai/human-memory-renderers.test.ts +++ b/tests/case/unit/ai/human-memory-renderers.test.ts @@ -2,13 +2,13 @@ import { describe, expect, it } from 'vitest' import { renderHumanThought, renderTaskProgress -} from '../../../src/main/services/ai/human/shared/memoryRenderers' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import { appendRoundRecord, buildHandExecution } from '../../../src/main/services/ai/human/memory/rounds' +} from '../../../../src/main/services/ai/human/shared/memoryRenderers' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import { appendRoundRecord, buildHandExecution } from '../../../../src/main/services/ai/human/memory/rounds' import { renderBrainRoundExecutionOnly, renderBrainRoundSection -} from '../../../src/main/services/ai/human/shared/historyRenderers' +} from '../../../../src/main/services/ai/human/shared/historyRenderers' describe('ai unit human memory renderers', () => { it('renders thought as global progress only without thought.next', () => { diff --git a/tests/unit/ai/human-process-memory.test.ts b/tests/case/unit/ai/human-process-memory.test.ts similarity index 97% rename from tests/unit/ai/human-process-memory.test.ts rename to tests/case/unit/ai/human-process-memory.test.ts index 2d6827bd..138f2546 100644 --- a/tests/unit/ai/human-process-memory.test.ts +++ b/tests/case/unit/ai/human-process-memory.test.ts @@ -6,14 +6,14 @@ import { buildLearningExecution, getCompressibleHistoryRounds, getRecentHistoryRounds -} from '../../../src/main/services/ai/human/memory/rounds' -import { applyThoughtPatch } from '../../../src/main/services/ai/human/memory/thoughtMemory' +} from '../../../../src/main/services/ai/human/memory/rounds' +import { applyThoughtPatch } from '../../../../src/main/services/ai/human/memory/thoughtMemory' import { applyBrainDecisionState, resolveBrainDecisionHint -} from '../../../src/main/services/ai/human/node/utils/stateTransitionState' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import type { HumanThoughtDecision } from '../../../src/main/services/ai/human/types' +} from '../../../../src/main/services/ai/human/node/utils/stateTransitionState' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import type { HumanThoughtDecision } from '../../../../src/main/services/ai/human/types' const buildThought = (input: { summary: string diff --git a/tests/unit/ai/human-references.test.ts b/tests/case/unit/ai/human-references.test.ts similarity index 88% rename from tests/unit/ai/human-references.test.ts rename to tests/case/unit/ai/human-references.test.ts index 298e32ea..9cb13f5f 100644 --- a/tests/unit/ai/human-references.test.ts +++ b/tests/case/unit/ai/human-references.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest' -import { brainNextActionSchema } from '../../../src/main/services/ai/human/schema' +import { brainNextActionSchema } from '../../../../src/main/services/ai/human/schema' import { buildReferenceMarkdown, inferReferenceIconKind -} from '../../../src/main/services/ai/human/shared/references' -import { applyBrainDecisionState } from '../../../src/main/services/ai/human/node/utils/stateTransitionState' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' +} from '../../../../src/main/services/ai/human/shared/references' +import { applyBrainDecisionState } from '../../../../src/main/services/ai/human/node/utils/stateTransitionState' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' describe('ai unit human references', () => { it('accepts references only on mouth nextAction', () => { diff --git a/tests/unit/ai/human-thought-memory.test.ts b/tests/case/unit/ai/human-thought-memory.test.ts similarity index 93% rename from tests/unit/ai/human-thought-memory.test.ts rename to tests/case/unit/ai/human-thought-memory.test.ts index 1b56abbb..3d1bbf88 100644 --- a/tests/unit/ai/human-thought-memory.test.ts +++ b/tests/case/unit/ai/human-thought-memory.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { applyThoughtPatch } from '../../../src/main/services/ai/human/memory/thoughtMemory' -import type { HumanThought } from '../../../src/main/services/ai/human/types' +import { applyThoughtPatch } from '../../../../src/main/services/ai/human/memory/thoughtMemory' +import type { HumanThought } from '../../../../src/main/services/ai/human/types' const BASE_THOUGHT: HumanThought = { next: '已有下一步', diff --git a/tests/unit/ai/human-tool-executor-diagnostic.test.ts b/tests/case/unit/ai/human-tool-executor-diagnostic.test.ts similarity index 89% rename from tests/unit/ai/human-tool-executor-diagnostic.test.ts rename to tests/case/unit/ai/human-tool-executor-diagnostic.test.ts index d98a25be..51ada6d9 100644 --- a/tests/unit/ai/human-tool-executor-diagnostic.test.ts +++ b/tests/case/unit/ai/human-tool-executor-diagnostic.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import type { AiRunContext } from '../../../src/main/services/ai/types' -import { executeToolCall } from '../../../src/main/services/ai/human/node/handNode/toolExecutor' -import type { HumanRuntimeCache } from '../../../src/main/services/ai/human/types' +import type { AiRunContext } from '../../../../src/main/services/ai/types' +import { executeToolCall } from '../../../../src/main/services/ai/human/node/handNode/toolExecutor' +import type { HumanRuntimeCache } from '../../../../src/main/services/ai/human/types' const createStubAiRunContext = (): AiRunContext => ({ runId: 'run-tool-diagnostic', diff --git a/tests/unit/ai/human-tool-result-renderers.test.ts b/tests/case/unit/ai/human-tool-result-renderers.test.ts similarity index 97% rename from tests/unit/ai/human-tool-result-renderers.test.ts rename to tests/case/unit/ai/human-tool-result-renderers.test.ts index b96b159b..c5faa6de 100644 --- a/tests/unit/ai/human-tool-result-renderers.test.ts +++ b/tests/case/unit/ai/human-tool-result-renderers.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { renderToolResultDataText } from '../../../src/main/services/ai/human/shared/toolResultRenderers' +import { renderToolResultDataText } from '../../../../src/main/services/ai/human/shared/toolResultRenderers' describe('ai unit human tool result renderers', () => { it('renders generic terminal-like object fields with stdout', () => { diff --git a/tests/unit/ai/learning-partial-success-contract.test.ts b/tests/case/unit/ai/learning-partial-success-contract.test.ts similarity index 92% rename from tests/unit/ai/learning-partial-success-contract.test.ts rename to tests/case/unit/ai/learning-partial-success-contract.test.ts index 017a86ed..c16ee626 100644 --- a/tests/unit/ai/learning-partial-success-contract.test.ts +++ b/tests/case/unit/ai/learning-partial-success-contract.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import type { AiRunContext } from '../../../src/main/services/ai/types' -import { createLearningNode } from '../../../src/main/services/ai/human/node/learningNode' -import { createHumanRuntimeCache } from '../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import type { HumanThoughtDecision } from '../../../src/main/services/ai/human/types' +import type { AiRunContext } from '../../../../src/main/services/ai/types' +import { createLearningNode } from '../../../../src/main/services/ai/human/node/learningNode' +import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import type { HumanThoughtDecision } from '../../../../src/main/services/ai/human/types' const createStubAiRunContext = (): AiRunContext => ({ runId: 'run-learning-unit', diff --git a/tests/unit/ai/learning-tool-budget.test.ts b/tests/case/unit/ai/learning-tool-budget.test.ts similarity index 94% rename from tests/unit/ai/learning-tool-budget.test.ts rename to tests/case/unit/ai/learning-tool-budget.test.ts index 4948f066..4a8d71f9 100644 --- a/tests/unit/ai/learning-tool-budget.test.ts +++ b/tests/case/unit/ai/learning-tool-budget.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import { humanThoughtDecisionSchema } from '../../../src/main/services/ai/human/schema' +import { humanThoughtDecisionSchema } from '../../../../src/main/services/ai/human/schema' import { countActiveToolNamesForSkillIds, fitSkillIdsToToolBudget -} from '../../../src/main/services/ai/human/node/learningNode/toolBudget' +} from '../../../../src/main/services/ai/human/node/learningNode/toolBudget' type FakeSkillId = 'alpha' | 'beta' | 'gamma' | 'delta' | 'epsilon' | 'zeta' diff --git a/tests/unit/ai/llm-executor-text.test.ts b/tests/case/unit/ai/llm-executor-text.test.ts similarity index 97% rename from tests/unit/ai/llm-executor-text.test.ts rename to tests/case/unit/ai/llm-executor-text.test.ts index 5e411e1e..e658b888 100644 --- a/tests/unit/ai/llm-executor-text.test.ts +++ b/tests/case/unit/ai/llm-executor-text.test.ts @@ -4,14 +4,14 @@ const { createChatModelMock } = vi.hoisted(() => ({ createChatModelMock: vi.fn() })) -vi.mock('../../../src/main/services/llm/chatModel', () => ({ +vi.mock('../../../../src/main/services/llm/chatModel', () => ({ createChatModel: createChatModelMock, getSupportedTransportsForConfig: () => ['responses', 'completions'] })) -import { createAiLlmExecutor } from '../../../src/main/services/ai/llmExecutor' -import type { AiLlmDriver } from '../../../src/main/services/ai/llmExecutor' -import type { AiLlmIoRecord, AiRunEvent, LlmRuntimeConfig } from '../../../src/shared/types' +import { createAiLlmExecutor } from '../../../../src/main/services/ai/llmExecutor' +import type { AiLlmDriver } from '../../../../src/main/services/ai/llmExecutor' +import type { AiLlmIoRecord, AiRunEvent, LlmRuntimeConfig } from '../../../../src/shared/types' const baseConfig: LlmRuntimeConfig = { id: 'cfg-1', diff --git a/tests/unit/ai/llm-usage-normalizer.test.ts b/tests/case/unit/ai/llm-usage-normalizer.test.ts similarity index 96% rename from tests/unit/ai/llm-usage-normalizer.test.ts rename to tests/case/unit/ai/llm-usage-normalizer.test.ts index dd8bedb6..c9100580 100644 --- a/tests/unit/ai/llm-usage-normalizer.test.ts +++ b/tests/case/unit/ai/llm-usage-normalizer.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { AIMessage } from '@langchain/core/messages' -import { normalizeLlmUsage } from '../../../src/main/services/llm/usage' +import { normalizeLlmUsage } from '../../../../src/main/services/llm/usage' describe('ai unit llm usage normalizer', () => { it('prefers usage metadata input tokens when cache details are present', () => { diff --git a/tests/unit/ai/markdown-reference.test.ts b/tests/case/unit/ai/markdown-reference.test.ts similarity index 93% rename from tests/unit/ai/markdown-reference.test.ts rename to tests/case/unit/ai/markdown-reference.test.ts index bde51690..896ee73a 100644 --- a/tests/unit/ai/markdown-reference.test.ts +++ b/tests/case/unit/ai/markdown-reference.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { parseRefHref } from '../../../src/renderer/src/components/markdownReference' +import { parseRefHref } from '../../../../src/renderer/src/components/markdownReference' describe('ai unit markdown reference helpers', () => { it('parses ncurator reference href', () => { diff --git a/tests/unit/ai/model-path-candidates.test.ts b/tests/case/unit/ai/model-path-candidates.test.ts similarity index 97% rename from tests/unit/ai/model-path-candidates.test.ts rename to tests/case/unit/ai/model-path-candidates.test.ts index 636b31ad..02be7330 100644 --- a/tests/unit/ai/model-path-candidates.test.ts +++ b/tests/case/unit/ai/model-path-candidates.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest' import { listKnownPersistentUserDataPaths, listModelPathCandidates -} from '../../../src/main/utils/paths' +} from '../../../../src/main/utils/paths' describe('ai unit model path candidates', () => { it('includes known persistent macOS user data directories', () => { diff --git a/tests/unit/ai/mouth-node.test.ts b/tests/case/unit/ai/mouth-node.test.ts similarity index 94% rename from tests/unit/ai/mouth-node.test.ts rename to tests/case/unit/ai/mouth-node.test.ts index f3458ac4..74c7d5ae 100644 --- a/tests/unit/ai/mouth-node.test.ts +++ b/tests/case/unit/ai/mouth-node.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest' -import { appendRoundRecord, buildHandExecution } from '../../../src/main/services/ai/human/memory/rounds' -import { createMouthNode } from '../../../src/main/services/ai/human/node/mouthNode' -import { createHumanRuntimeCache } from '../../../src/main/services/ai/human/node/utils/runtimeCache' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import type { AiRunContext } from '../../../src/main/services/ai/types' -import type { AiFileChangeRecord, AiRunEvent } from '../../../src/shared/types' +import { appendRoundRecord, buildHandExecution } from '../../../../src/main/services/ai/human/memory/rounds' +import { createMouthNode } from '../../../../src/main/services/ai/human/node/mouthNode' +import { createHumanRuntimeCache } from '../../../../src/main/services/ai/human/node/utils/runtimeCache' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import type { AiRunContext } from '../../../../src/main/services/ai/types' +import type { AiFileChangeRecord, AiRunEvent } from '../../../../src/shared/types' const createBaseContext = (input: { events: AiRunEvent[] diff --git a/tests/unit/ai/mouth-prompt-contract.test.ts b/tests/case/unit/ai/mouth-prompt-contract.test.ts similarity index 95% rename from tests/unit/ai/mouth-prompt-contract.test.ts rename to tests/case/unit/ai/mouth-prompt-contract.test.ts index 9487049e..a22d9650 100644 --- a/tests/unit/ai/mouth-prompt-contract.test.ts +++ b/tests/case/unit/ai/mouth-prompt-contract.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { buildMouthPrompt } from '../../../src/main/services/ai/human/prompts/mouth' -import { createInitialHumanState } from '../../../src/main/services/ai/human/state/schema' -import type { AiRunContext } from '../../../src/main/services/ai/types' +import { buildMouthPrompt } from '../../../../src/main/services/ai/human/prompts/mouth' +import { createInitialHumanState } from '../../../../src/main/services/ai/human/state/schema' +import type { AiRunContext } from '../../../../src/main/services/ai/types' const createFakeCtx = (): AiRunContext => ({ diff --git a/tests/unit/ai/openai-compatible-runtime.test.ts b/tests/case/unit/ai/openai-compatible-runtime.test.ts similarity index 94% rename from tests/unit/ai/openai-compatible-runtime.test.ts rename to tests/case/unit/ai/openai-compatible-runtime.test.ts index 8fb639e2..f90346cb 100644 --- a/tests/unit/ai/openai-compatible-runtime.test.ts +++ b/tests/case/unit/ai/openai-compatible-runtime.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { resolveOpenAICompatibleRuntimeOptions } from '../../../src/main/services/llm/openaiCompatible' -import type { LlmRuntimeConfig } from '../../../src/shared/types' +import { resolveOpenAICompatibleRuntimeOptions } from '../../../../src/main/services/llm/openaiCompatible' +import type { LlmRuntimeConfig } from '../../../../src/shared/types' const createConfig = (overrides?: Partial): LlmRuntimeConfig => ({ id: 'cfg-openai-compatible', diff --git a/tests/unit/ai/process-events.test.ts b/tests/case/unit/ai/process-events.test.ts similarity index 91% rename from tests/unit/ai/process-events.test.ts rename to tests/case/unit/ai/process-events.test.ts index 6d721e7e..3d2449f2 100644 --- a/tests/unit/ai/process-events.test.ts +++ b/tests/case/unit/ai/process-events.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest' import { emitToolProcessCompleted, emitToolProcessStarted -} from '../../../src/main/services/ai/events/processEvents' -import type { AiRunEvent } from '../../../src/shared/types' +} from '../../../../src/main/services/ai/events/processEvents' +import type { AiRunEvent } from '../../../../src/shared/types' describe('ai process events', () => { it('uses tool call identity for process group ids so repeated skills stay separate', () => { diff --git a/tests/unit/ai/runtime-skill-contract.test.ts b/tests/case/unit/ai/runtime-skill-contract.test.ts similarity index 93% rename from tests/unit/ai/runtime-skill-contract.test.ts rename to tests/case/unit/ai/runtime-skill-contract.test.ts index 729096f2..01d50b16 100644 --- a/tests/unit/ai/runtime-skill-contract.test.ts +++ b/tests/case/unit/ai/runtime-skill-contract.test.ts @@ -3,7 +3,7 @@ import { getEffectiveLearnedSkillIds, listBuiltinSkillDefinitions, listExternalSkillDefinitions -} from '../../../src/main/services/ai/human/skills' +} from '../../../../src/main/services/ai/human/skills' describe('ai unit runtime skill contract', () => { it('exposes builtin skills', () => { diff --git a/tests/unit/ai/selected-context-workspace-path.test.ts b/tests/case/unit/ai/selected-context-workspace-path.test.ts similarity index 96% rename from tests/unit/ai/selected-context-workspace-path.test.ts rename to tests/case/unit/ai/selected-context-workspace-path.test.ts index f553b1bd..33b9c766 100644 --- a/tests/unit/ai/selected-context-workspace-path.test.ts +++ b/tests/case/unit/ai/selected-context-workspace-path.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { buildReferenceScope, renderReferenceScopeText -} from '../../../src/main/services/ai/prompts/referenceScope' +} from '../../../../src/main/services/ai/prompts/referenceScope' describe('ai unit selected context workspace path', () => { it('renders workspace rootPath instead of workspaceName in reference scope text', () => { diff --git a/tests/unit/ai/selected-reference-validation.test.ts b/tests/case/unit/ai/selected-reference-validation.test.ts similarity index 92% rename from tests/unit/ai/selected-reference-validation.test.ts rename to tests/case/unit/ai/selected-reference-validation.test.ts index 4bc98605..22a5d27a 100644 --- a/tests/unit/ai/selected-reference-validation.test.ts +++ b/tests/case/unit/ai/selected-reference-validation.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import type { AiRunEvent, SelectedReference } from '../../../src/shared/types' -import { validateSelectedReferencesForRun } from '../../../src/main/services/sourceScope/validation' +import type { AiRunEvent, SelectedReference } from '../../../../src/shared/types' +import { validateSelectedReferencesForRun } from '../../../../src/main/services/sourceScope/validation' const WORKSPACE_ID = 'workspace-1' const WORKSPACE_ROOT = '/tmp/ncurator-test-workspace' diff --git a/tests/unit/ai/stagehand-model-config.test.ts b/tests/case/unit/ai/stagehand-model-config.test.ts similarity index 94% rename from tests/unit/ai/stagehand-model-config.test.ts rename to tests/case/unit/ai/stagehand-model-config.test.ts index e51ad223..6e7ad0a8 100644 --- a/tests/unit/ai/stagehand-model-config.test.ts +++ b/tests/case/unit/ai/stagehand-model-config.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { resolveStagehandModelConfig } from '../../../src/main/services/llm/stagehandModel' -import type { LlmRuntimeConfig } from '../../../src/shared/types' +import { resolveStagehandModelConfig } from '../../../../src/main/services/llm/stagehandModel' +import type { LlmRuntimeConfig } from '../../../../src/shared/types' const createConfig = (overrides?: Partial): LlmRuntimeConfig => ({ id: 'cfg-stagehand', diff --git a/tests/unit/ai/structured-chat-activity.test.ts b/tests/case/unit/ai/structured-chat-activity.test.ts similarity index 98% rename from tests/unit/ai/structured-chat-activity.test.ts rename to tests/case/unit/ai/structured-chat-activity.test.ts index 54a2d6dd..14009228 100644 --- a/tests/unit/ai/structured-chat-activity.test.ts +++ b/tests/case/unit/ai/structured-chat-activity.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest' import { runLlmCallWithActivity, runStructuredChatWithActivity -} from '../../../src/main/services/ai/utils/structuredChatActivity' -import type { AiLlmIoRecord, AiRunEvent } from '../../../src/shared/types' +} from '../../../../src/main/services/ai/utils/structuredChatActivity' +import type { AiLlmIoRecord, AiRunEvent } from '../../../../src/shared/types' describe('ai unit structured chat activity', () => { it('records scripted structured output activity and llm io', async () => { diff --git a/tests/unit/ai/structured-output-auto-fallback.test.ts b/tests/case/unit/ai/structured-output-auto-fallback.test.ts similarity index 98% rename from tests/unit/ai/structured-output-auto-fallback.test.ts rename to tests/case/unit/ai/structured-output-auto-fallback.test.ts index 9c9a23d6..cfd69c1b 100644 --- a/tests/unit/ai/structured-output-auto-fallback.test.ts +++ b/tests/case/unit/ai/structured-output-auto-fallback.test.ts @@ -5,16 +5,16 @@ const { createChatModelMock } = vi.hoisted(() => ({ createChatModelMock: vi.fn() })) -vi.mock('../../../src/main/services/llm/chatModel', () => ({ +vi.mock('../../../../src/main/services/llm/chatModel', () => ({ createChatModel: createChatModelMock, getSupportedTransportsForConfig: () => ['responses', 'completions'] })) import { STRUCTURED_OUTPUT_METHODS, - STRUCTURED_OUTPUT_PROFILES, -} from '../../../src/main/services/ai/structuredOutput' -import { invokeStructuredChat } from '../../../src/main/services/ai/llmExecutor' + STRUCTURED_OUTPUT_PROFILES +} from '../../../../src/main/services/ai/structuredOutput' +import { invokeStructuredChat } from '../../../../src/main/services/ai/llmExecutor' type StructuredOutputProfile = | 'completions:jsonSchema' diff --git a/tests/unit/ai/structured-output-fallback.test.ts b/tests/case/unit/ai/structured-output-fallback.test.ts similarity index 98% rename from tests/unit/ai/structured-output-fallback.test.ts rename to tests/case/unit/ai/structured-output-fallback.test.ts index a50f94bd..1a064bd9 100644 --- a/tests/unit/ai/structured-output-fallback.test.ts +++ b/tests/case/unit/ai/structured-output-fallback.test.ts @@ -3,7 +3,7 @@ import { classifyStructuredOutputError, isStructuredOutputMethodUnsupportedError, isStructuredOutputTransportCompatibilityError -} from '../../../src/main/services/ai/structuredOutput' +} from '../../../../src/main/services/ai/structuredOutput' describe('ai unit structured output fallback', () => { it('classifies response_format unavailable errors as capability mismatch', () => { diff --git a/tests/unit/ai/terminal-command-policy.test.ts b/tests/case/unit/ai/terminal-command-policy.test.ts similarity index 94% rename from tests/unit/ai/terminal-command-policy.test.ts rename to tests/case/unit/ai/terminal-command-policy.test.ts index 0ab52df5..538db8f6 100644 --- a/tests/unit/ai/terminal-command-policy.test.ts +++ b/tests/case/unit/ai/terminal-command-policy.test.ts @@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest' import { analyzeSafeTerminalCommandPolicy, analyzeTerminalCommand -} from '../../../src/main/services/ai/human/tools/terminal/commandPolicy' -import { buildWindowsPowerShellEncodedCommand } from '../../../src/main/services/ai/human/tools/terminal/terminalExec' +} from '../../../../src/main/services/ai/human/tools/terminal/commandPolicy' +import { buildWindowsPowerShellEncodedCommand } from '../../../../src/main/services/ai/human/tools/terminal/terminalExec' import { createTerminalRunPromptSpec, createTerminalRunToolSpec -} from '../../../src/main/services/ai/human/tools/terminal/toolSpecs' +} from '../../../../src/main/services/ai/human/tools/terminal/toolSpecs' describe('ai unit terminal command policy', () => { it('allows macos commands from the platform safe command set', () => { diff --git a/tests/unit/ai/terminal-output-quality.test.ts b/tests/case/unit/ai/terminal-output-quality.test.ts similarity index 91% rename from tests/unit/ai/terminal-output-quality.test.ts rename to tests/case/unit/ai/terminal-output-quality.test.ts index fad22052..48d7a393 100644 --- a/tests/unit/ai/terminal-output-quality.test.ts +++ b/tests/case/unit/ai/terminal-output-quality.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { inspectTerminalOutputQuality } from '../../../src/main/services/ai/human/tools/terminal/terminalExec' +import { inspectTerminalOutputQuality } from '../../../../src/main/services/ai/human/tools/terminal/terminalExec' describe('ai unit terminal output quality', () => { it('detects obvious office binary output signatures', () => { diff --git a/tests/unit/ai/tool-call-schema.test.ts b/tests/case/unit/ai/tool-call-schema.test.ts similarity index 96% rename from tests/unit/ai/tool-call-schema.test.ts rename to tests/case/unit/ai/tool-call-schema.test.ts index d4c25d4f..b3a3ca02 100644 --- a/tests/unit/ai/tool-call-schema.test.ts +++ b/tests/case/unit/ai/tool-call-schema.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { createToolCallSchema, formatToolCallSchemaDiagnostic } from '../../../src/main/services/ai/human/toolCallSchema' +import { createToolCallSchema, formatToolCallSchemaDiagnostic } from '../../../../src/main/services/ai/human/toolCallSchema' describe('ai unit tool call schema', () => { it('accepts a valid docx_edit create_document call and rejects nested args wrappers', () => { diff --git a/tests/unit/ai/tool-prompt-contract.test.ts b/tests/case/unit/ai/tool-prompt-contract.test.ts similarity index 88% rename from tests/unit/ai/tool-prompt-contract.test.ts rename to tests/case/unit/ai/tool-prompt-contract.test.ts index d65292ab..ebb92cf6 100644 --- a/tests/unit/ai/tool-prompt-contract.test.ts +++ b/tests/case/unit/ai/tool-prompt-contract.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { ALL_TOOL_SPECS } from '../../../src/main/services/ai/human/tools/registry' +import { ALL_TOOL_SPECS } from '../../../../src/main/services/ai/human/tools/registry' describe('ai unit tool prompt contract', () => { it('requires every tool spec to provide schema-valid examples', () => { diff --git a/tests/unit/backend-artifact-collector.test.ts b/tests/case/unit/backend-artifact-collector.test.ts similarity index 92% rename from tests/unit/backend-artifact-collector.test.ts rename to tests/case/unit/backend-artifact-collector.test.ts index e09d414a..891e114c 100644 --- a/tests/unit/backend-artifact-collector.test.ts +++ b/tests/case/unit/backend-artifact-collector.test.ts @@ -2,9 +2,9 @@ import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import { afterEach, describe, expect, it } from 'vitest' -import type { AiRunEvent } from '../../src/shared/types' -import { collectBackendRunArtifacts } from '../harness/backend/artifactCollector' -import { resolveRunLogDir } from '../harness/backend/write-run-log' +import type { AiRunEvent } from '../../../src/shared/types' +import { collectBackendRunArtifacts } from '../../harness/backend/artifactCollector' +import { resolveRunLogDir } from '../../harness/backend/write-run-log' const createdDirs: string[] = [] diff --git a/tests/unit/streaming-run-log-writer.test.ts b/tests/case/unit/streaming-run-log-writer.test.ts similarity index 96% rename from tests/unit/streaming-run-log-writer.test.ts rename to tests/case/unit/streaming-run-log-writer.test.ts index f5d76b85..74b02220 100644 --- a/tests/unit/streaming-run-log-writer.test.ts +++ b/tests/case/unit/streaming-run-log-writer.test.ts @@ -2,13 +2,13 @@ import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import { afterEach, describe, expect, it } from 'vitest' -import type { AiLlmIoRecord, AiRunEvent } from '../../src/shared/types' -import { EventCollector } from '../harness/core/eventCollector' +import type { AiLlmIoRecord, AiRunEvent } from '../../../src/shared/types' +import { EventCollector } from '../../harness/core/eventCollector' import { createStreamingRunLogWriter, resolveRunLogDir -} from '../harness/backend/write-run-log' -import type { TestLogEvent, TestRunMeta } from '../harness/core/run-log-contract' +} from '../../harness/backend/write-run-log' +import type { TestLogEvent, TestRunMeta } from '../../harness/core/run-log-contract' const createdDirs: string[] = [] diff --git a/tests/harness/backend/test-types.ts b/tests/harness/backend/test-types.ts index 8ae4c11e..84a4ab00 100644 --- a/tests/harness/backend/test-types.ts +++ b/tests/harness/backend/test-types.ts @@ -25,12 +25,7 @@ import type { SelectedReference } from '../../../src/shared/types' import type { EventCollector } from '../core/eventCollector' -import type { - TestDomain, - TestType, - TestLogEvent, - TestRunMeta -} from '../core/run-log-contract' +import type { TestDomain, TestType, TestLogEvent, TestRunMeta } from '../core/run-log-contract' export type { TestDomain, TestType, TestLogEvent, TestRunMeta } from '../core/run-log-contract' @@ -55,14 +50,14 @@ type ScriptedLlmStreamShape = { } export type ScriptedLlmStep = - | { + | ({ output: JsonObject usage?: AiLlmUsage - } & ScriptedLlmStreamShape - | { + } & ScriptedLlmStreamShape) + | ({ text: string usage?: AiLlmUsage - } & ScriptedLlmStreamShape + } & ScriptedLlmStreamShape) | { error: string } @@ -201,7 +196,10 @@ export type BackendTestRuntime = { embeddingService: EmbeddingService docxApplyService: DocxApplyService recordEvent: (input: Omit) => void - assertThat: (condition: boolean, input: { name: string; message: string; data?: JsonValue }) => void + assertThat: ( + condition: boolean, + input: { name: string; message: string; data?: JsonValue } + ) => void getFixtureWorkspacePath: (sourcePath: string) => string recordSeedWorkspaceFile: (input: SeedWorkspaceFileRecord) => void getSeedManifest: () => BackendSeedManifest @@ -218,7 +216,7 @@ export type TestCaseDefinition = { caseId: string harnessMode: BackendHarnessMode testType?: Extract - domain: Extract + domain: Extract suite: string groups?: readonly string[] description: string diff --git a/tests/harness/core/run-log-contract.ts b/tests/harness/core/run-log-contract.ts index 3d27d5ad..58e11569 100644 --- a/tests/harness/core/run-log-contract.ts +++ b/tests/harness/core/run-log-contract.ts @@ -2,7 +2,7 @@ import type { JsonValue } from '../../../src/shared/types' export const TEST_LOG_SCHEMA_VERSION = 5 as const -export type TestDomain = 'ai' | 'settings' | 'kb-search' | 'renderer' | 'temp' +export type TestDomain = 'ai' | 'settings' | 'kb-search' export type TestType = 'backend' | 'e2e' export type TestRunStatus = 'running' | 'passed' | 'failed' export type TestLogStatus = 'info' | 'ok' | 'failed' | 'skipped' diff --git a/tests/harness/e2e/run-e2e-case.ts b/tests/harness/e2e/run-e2e-case.ts index 57e2bf42..75cca105 100644 --- a/tests/harness/e2e/run-e2e-case.ts +++ b/tests/harness/e2e/run-e2e-case.ts @@ -62,7 +62,7 @@ export type E2ETestRuntime = { export type E2ETestCaseDefinition = { caseId: string - domain: Extract + domain: Extract suite: string groups?: readonly string[] description: string @@ -83,11 +83,8 @@ const buildRunScope = (input: { return parts.join('/') } -const buildTaskId = (input: { - suite: string - groups: readonly string[] - caseId: string -}): string => [input.suite, ...input.groups, input.caseId].join('-') +const buildTaskId = (input: { suite: string; groups: readonly string[]; caseId: string }): string => + [input.suite, ...input.groups, input.caseId].join('-') const buildStats = ( startedAtMs: number, @@ -170,7 +167,9 @@ const buildPlaywrightReportHtml = (input: { ].join('\n') } -export const runE2ECase = async (definition: E2ETestCaseDefinition): Promise => { +export const runE2ECase = async ( + definition: E2ETestCaseDefinition +): Promise => { const startedAtMs = Date.now() const runId = createRunId(startedAtMs) const scope = buildRunScope({ diff --git a/tests/harness/e2e/workbench.ts b/tests/harness/e2e/workbench.ts deleted file mode 100644 index 6e6d656c..00000000 --- a/tests/harness/e2e/workbench.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { Locator, Page } from 'playwright' -import { workbenchSelectors } from '../../../src/renderer/src/testing/selectors' - -const byTestId = (testId: string): string => `[data-testid="${testId}"]` - -const byTestIdPrefix = (testIdPrefix: string): string => `[data-testid^="${testIdPrefix}-"]` - -export const getWorkbenchLocator = (page: Page, testId: string): Locator => - page.locator(byTestId(testId)) - -export const getWorkbenchAssistantProcesses = (page: Page): Locator => - page.locator(byTestIdPrefix(workbenchSelectors.assistantProcessPrefix)) - -export const getWorkbenchAssistantApplyButtons = (page: Page): Locator => - page.locator(`button${byTestIdPrefix(workbenchSelectors.assistantTurnApplyPrefix)}`) - -export const waitForWorkbenchReady = async (page: Page): Promise => { - await getWorkbenchLocator(page, workbenchSelectors.input).waitFor({ state: 'visible' }) -} - -export const switchWorkbenchMode = async ( - page: Page, - mode: 'ai' | 'search' -): Promise => { - await getWorkbenchLocator(page, workbenchSelectors.modeSwitch).click() - const optionName = mode === 'ai' ? '大聪明' : '没头脑' - const dropdownOption = page - .locator('.ant-select-dropdown .ant-select-item-option') - .filter({ hasText: optionName }) - .first() - await dropdownOption.waitFor({ state: 'visible' }) - await dropdownOption.click() -} - -export const submitWorkbenchMessage = async (page: Page, input: string): Promise => { - const editor = getWorkbenchLocator(page, workbenchSelectors.input) - await editor.click() - await page.keyboard.press(process.platform === 'darwin' ? 'Meta+A' : 'Control+A') - await page.keyboard.press('Backspace') - await page.keyboard.type(input) - await getWorkbenchLocator(page, workbenchSelectors.sendButton).click() -} - -export const waitForAssistantTurnCount = async (page: Page, count: number): Promise => { - await page.waitForFunction( - ({ selector, minCount }) => document.querySelectorAll(selector).length >= minCount, - { - selector: byTestIdPrefix(workbenchSelectors.assistantProcessPrefix), - minCount: count - } - ) -} - -export const waitForApplyTabCount = async (page: Page, count: number): Promise => { - await page.waitForFunction( - ({ selector, minCount }) => { - const host = document.querySelector(selector) - if (!(host instanceof HTMLElement)) return false - return Array.from(host.querySelectorAll('button')).filter( - (button) => button instanceof HTMLElement && button.offsetParent !== null - ).length >= minCount - }, - { - selector: byTestId(workbenchSelectors.applyTabs), - minCount: count - } - ) -} - -export const openLatestAssistantApply = async (page: Page): Promise => { - const applyButtons = getWorkbenchAssistantApplyButtons(page) - await page.waitForFunction( - ({ selector }) => - Array.from(document.querySelectorAll(selector)).some( - (button) => button instanceof HTMLElement && button.offsetParent !== null - ), - { - selector: `button${byTestIdPrefix(workbenchSelectors.assistantTurnApplyPrefix)}` - } - ) - await applyButtons.last().click() -} - -export const openFirstEditedFileChipFromExpandedTurn = async (page: Page): Promise => { - const editedSectionTitle = page.getByText('修改的文件').last() - await editedSectionTitle.waitFor({ state: 'visible' }) - const editedFileButtons = editedSectionTitle.locator('xpath=..').locator('button') - await editedFileButtons.first().click() -} diff --git a/tests/harness/renderer/setup.ts b/tests/harness/renderer/setup.ts deleted file mode 100644 index 69ead2b7..00000000 --- a/tests/harness/renderer/setup.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { cleanup } from '@testing-library/react' -import { afterEach, vi } from 'vitest' - -afterEach(() => { - cleanup() - vi.restoreAllMocks() -}) - -Object.defineProperty(window, 'matchMedia', { - writable: true, - value: (query: string): MediaQueryList => ({ - matches: false, - media: query, - onchange: null, - addListener: (): void => {}, - removeListener: (): void => {}, - addEventListener: (): void => {}, - removeEventListener: (): void => {}, - dispatchEvent: (): boolean => false - }) -}) - -Object.defineProperty(window.HTMLElement.prototype, 'scrollIntoView', { - writable: true, - value: (): void => {} -}) - -class ResizeObserverMock { - public observe(): void {} - - public unobserve(): void {} - - public disconnect(): void {} -} - -globalThis.ResizeObserver = ResizeObserverMock as typeof ResizeObserver diff --git a/tests/renderer/file-renders/pdf-preview.test.ts b/tests/renderer/file-renders/pdf-preview.test.ts deleted file mode 100644 index ead0fe8d..00000000 --- a/tests/renderer/file-renders/pdf-preview.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { prepareFileRenderDocument } from '../../../src/renderer/src/components/fileRenders/utils' - -const createPdfBytes = (): Uint8Array => { - return new Uint8Array([37, 80, 68, 70, 45, 49, 46, 55]) -} - -describe('file render PDF preview preparation', () => { - it('loads local PDF files as byte data for pdf.js rendering', async () => { - const pdfBytes = createPdfBytes() - const readFile = vi.fn<[string], Promise>().mockResolvedValue(pdfBytes) - const createdUrls: string[] = [] - - Object.defineProperty(window, 'api', { - configurable: true, - value: { - readFile - } - }) - Object.defineProperty(URL, 'createObjectURL', { - configurable: true, - value: vi.fn(() => 'blob:ncurator-pdf-preview') - }) - - const document = await prepareFileRenderDocument( - { - filePath: '/workspace/学习笔记/深度学习.pdf' - }, - createdUrls - ) - - expect(readFile).toHaveBeenCalledWith('/workspace/学习笔记/深度学习.pdf') - expect(document?.fileType).toBe('pdf') - expect(document?.uri).toBe('blob:ncurator-pdf-preview') - expect(document?.fileData).toBeInstanceOf(ArrayBuffer) - expect(new Uint8Array(document?.fileData as ArrayBuffer)).toEqual(pdfBytes) - expect(createdUrls).toEqual(['blob:ncurator-pdf-preview']) - }) -}) diff --git a/tests/renderer/file-renders/pdf-renderer.test.tsx b/tests/renderer/file-renders/pdf-renderer.test.tsx deleted file mode 100644 index c79fa673..00000000 --- a/tests/renderer/file-renders/pdf-renderer.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useEffect } from 'react' -import { render, screen, waitFor } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import CustomPDFRenderer from '../../../src/renderer/src/components/fileRenders/pdf' - -type MockDocumentProps = { - children: React.ReactNode - onLoadSuccess: (pdf: { numPages: number }) => void -} - -type MockPageProps = { - pageNumber: number -} - -vi.mock('react-pdf', () => ({ - pdfjs: { - GlobalWorkerOptions: { - workerSrc: '' - } - }, - Document: ({ children, onLoadSuccess }: MockDocumentProps) => { - useEffect(() => { - onLoadSuccess({ numPages: 3 }) - }, [onLoadSuccess]) - - return
{children}
- }, - Page: ({ pageNumber }: MockPageProps) => ( -
PDF page {pageNumber}
- ) -})) - -describe('CustomPDFRenderer', () => { - it('renders the full PDF and scrolls to the chunk page', async () => { - const scrollIntoView = vi.spyOn(window.HTMLElement.prototype, 'scrollIntoView') - Object.defineProperty(window, 'requestAnimationFrame', { - configurable: true, - value: (callback: FrameRequestCallback): number => { - callback(0) - return 1 - } - }) - - render( - - ) - - await waitFor(() => { - expect(screen.getAllByTestId('mock-pdf-page')).toHaveLength(3) - }) - expect(screen.getByText('PDF page 1')).toBeTruthy() - expect(screen.getByText('PDF page 2')).toBeTruthy() - expect(screen.getByText('PDF page 3')).toBeTruthy() - - await waitFor(() => { - expect(scrollIntoView).toHaveBeenCalled() - }) - expect(scrollIntoView.mock.instances[0]?.getAttribute('data-pdf-page')).toBe('2') - }) -}) diff --git a/tests/renderer/markdown-renderer.test.tsx b/tests/renderer/markdown-renderer.test.tsx deleted file mode 100644 index 8722f58c..00000000 --- a/tests/renderer/markdown-renderer.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { ReactNode } from 'react' -import { fireEvent, render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import MarkdownRenderer from '../../src/renderer/src/components/MarkdownRenderer' - -vi.mock('react-syntax-highlighter', () => ({ - Prism: ({ children }: { children?: ReactNode }) =>
{children}
-})) - -vi.mock('react-syntax-highlighter/dist/esm/styles/prism', () => ({ - oneLight: {} -})) - -describe('MarkdownRenderer', () => { - it('preserves ncurator file references and routes clicks through onOpenReference', () => { - const onOpenReference = vi.fn() - - render( - - ) - - const link = screen.getByRole('link', { name: /sample-edit\.docx/i }) - expect(link.getAttribute('href')).toBe( - 'ncurator://ref?kind=normal&resourceType=file&label=sample-edit.docx&path=%2Ftmp%2Fsample-edit.docx' - ) - - fireEvent.click(link) - - expect(onOpenReference).toHaveBeenCalledTimes(1) - expect(onOpenReference).toHaveBeenCalledWith({ - kind: 'normal', - resourceType: 'file', - label: 'sample-edit.docx', - path: '/tmp/sample-edit.docx' - }) - }) - - it('keeps standard web links on the default external path', () => { - render() - - const link = screen.getByRole('link', { name: 'OpenAI' }) - expect(link.getAttribute('href')).toBe('https://openai.com') - expect(link.getAttribute('target')).toBe('_blank') - expect(link.getAttribute('rel')).toBe('noopener noreferrer') - }) -}) diff --git a/tests/renderer/workbench/assistant-process.test.ts b/tests/renderer/workbench/assistant-process.test.ts deleted file mode 100644 index df32ecb6..00000000 --- a/tests/renderer/workbench/assistant-process.test.ts +++ /dev/null @@ -1,402 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { AiRunEvent } from '../../../src/shared/types' -import { - buildAssistantProcessText, - buildAssistantExecutionSummaryText, - completeAssistantProcessGroups, - createAssistantThinkingGroup, - createSearchProcessGroup, - reduceAssistantProcessGroups, - reduceAssistantProcessThoughts -} from '../../../src/renderer/src/components/workbench/utils' - -const toText = (segments: { text: string }[]): string => - segments.map((segment) => segment.text).join('') - -const groupStarted = (overrides: { - groupId: string - kind: Extract['kind'] - display: Extract['display'] - createdAt?: number -}): AiRunEvent => ({ - type: 'assistant_process_group_started', - runId: 'run-1', - groupId: overrides.groupId, - kind: overrides.kind, - display: overrides.display, - createdAt: overrides.createdAt || 1 -}) - -const groupCompleted = (overrides: { - groupId: string - display?: Extract['display'] - completedAt?: number -}): AiRunEvent => ({ - type: 'assistant_process_group_completed', - runId: 'run-1', - groupId: overrides.groupId, - display: overrides.display, - completedAt: overrides.completedAt || 2 -}) - -const itemStarted = (overrides: { - groupId: string - itemId: string - display: Extract['display'] - createdAt?: number -}): AiRunEvent => ({ - type: 'assistant_process_item_started', - runId: 'run-1', - groupId: overrides.groupId, - itemId: overrides.itemId, - display: overrides.display, - createdAt: overrides.createdAt || 1 -}) - -const itemCompleted = (overrides: { - groupId: string - itemId: string - display?: Extract['display'] - completedAt?: number -}): AiRunEvent => ({ - type: 'assistant_process_item_completed', - runId: 'run-1', - groupId: overrides.groupId, - itemId: overrides.itemId, - display: overrides.display, - completedAt: overrides.completedAt || 2 -}) - -describe('workbench assistant process groups', () => { - it('collects public thought text from assistant_process_thought events', () => { - const thoughts = reduceAssistantProcessThoughts([], { - type: 'assistant_process_thought', - runId: 'run-1', - thoughtId: 'thought-1', - text: '俺先看一下文档结构。', - createdAt: 1 - }) - - expect(thoughts).toEqual([ - { - id: 'thought-1', - text: '俺先看一下文档结构。', - createdAt: 1, - sourceEventType: 'assistant_process_thought' - } - ]) - }) - - it('replaces local thinking placeholder with backend thinking event', () => { - const groups = reduceAssistantProcessGroups( - [createAssistantThinkingGroup('assistant-1')], - groupStarted({ - groupId: 'run-1:thinking', - kind: 'thinking', - display: { kind: 'thinking' } - }) - ) - - expect(groups).toHaveLength(1) - expect(groups[0]).toMatchObject({ - id: 'run-1:thinking', - kind: 'thinking', - status: 'running' - }) - expect(toText(groups[0].runningText)).toBe('让俺想想') - }) - - it('removes thinking group when backend marks thinking completed', () => { - const startedGroups = reduceAssistantProcessGroups( - [], - groupStarted({ - groupId: 'run-1:thinking', - kind: 'thinking', - display: { kind: 'thinking' } - }) - ) - const completedGroups = reduceAssistantProcessGroups( - startedGroups, - groupCompleted({ - groupId: 'run-1:thinking', - display: { kind: 'thinking' } - }) - ) - - expect(completedGroups).toEqual([]) - }) - - it('removes thinking group as soon as public decision text arrives', () => { - const groups = reduceAssistantProcessGroups( - [createAssistantThinkingGroup('assistant-1')], - { - type: 'assistant_process_thought', - runId: 'run-1', - thoughtId: 'thought-1', - text: '先确认需要审批的本地命令。', - createdAt: 2 - } - ) - - expect(groups).toEqual([]) - }) - - it('keeps thinking placeholder when a malformed public decision event has no text', () => { - const initialGroups = [createAssistantThinkingGroup('assistant-1')] - const groups = reduceAssistantProcessGroups(initialGroups, { - type: 'assistant_process_thought', - runId: 'run-1', - thoughtId: 'thought-empty', - text: ' ', - createdAt: 2 - }) - - expect(groups).toEqual(initialGroups) - }) - - it('groups docx read tools by skill and builds tool-specific sentences from input', () => { - const groupId = 'run-1:skill:docx-reader' - const group = groupStarted({ - groupId, - kind: 'skill', - display: { - kind: 'tool', - toolName: 'docx_outline', - input: { sourcePath: '/tmp/report.docx' } - } - }) - const outline = itemStarted({ - groupId, - itemId: 'tool-outline', - display: { - kind: 'tool', - toolName: 'docx_outline', - input: { sourcePath: '/tmp/report.docx' } - }, - createdAt: 2 - }) - const readBlocks = itemStarted({ - groupId, - itemId: 'tool-read', - display: { - kind: 'tool', - toolName: 'docx_read_blocks', - input: { sourcePath: '/tmp/report.docx' } - }, - createdAt: 3 - }) - - const groups = [group, outline, readBlocks].reduce(reduceAssistantProcessGroups, []) - - expect(groups).toHaveLength(1) - expect(groups[0]).toMatchObject({ - id: groupId, - skillKey: 'docx-reader', - status: 'running' - }) - expect(toText(buildAssistantExecutionSummaryText(groups))).toBe('正在读取 1 个 docx 文件') - expect(toText(groups[0].items[0].runningText)).toBe('正在阅读 /tmp/report.docx 文件的结构') - expect(toText(groups[0].items[1].runningText)).toBe('正在读取 /tmp/report.docx 文件细节') - expect(groups[0].items[0].runningText.some((segment) => segment.emphasis)).toBe(true) - }) - - it('summarizes adjacent execution skill groups by action and unique docx file count', () => { - const readGroupId = 'run-1:skill:docx-reader' - const writeGroupId = 'run-1:skill:docx-writer' - const groups = [ - itemStarted({ - groupId: readGroupId, - itemId: 'tool-outline', - display: { - kind: 'tool', - toolName: 'docx_outline', - input: { sourcePath: '/tmp/report.docx' } - }, - createdAt: 1 - }), - itemStarted({ - groupId: readGroupId, - itemId: 'tool-read', - display: { - kind: 'tool', - toolName: 'docx_read_blocks', - input: { - sourcePath: '/tmp/report.docx', - range: { type: 'block_index', fromBlockIndex: 3, toBlockIndex: 8 } - } - }, - createdAt: 2 - }), - itemStarted({ - groupId: writeGroupId, - itemId: 'tool-edit', - display: { - kind: 'tool', - toolName: 'docx_edit', - input: { sourcePath: '/tmp/report.docx' } - }, - createdAt: 3 - }) - ].reduce(reduceAssistantProcessGroups, []) - - expect(toText(buildAssistantExecutionSummaryText(groups))).toBe( - '正在读取 1 个 docx 文件,正在编辑 1 个 docx 文件' - ) - expect(buildAssistantExecutionSummaryText(groups).filter((segment) => segment.iconKind)).toHaveLength(2) - expect(toText(groups[0].items[1].runningText)).toBe( - '正在读取 /tmp/report.docx 文件的 3-8 区间' - ) - }) - - it('keeps a skill group running while any grouped tool item is still running', () => { - const groupId = 'run-1:skill:docx-reader' - const outlineDisplay = { - kind: 'tool' as const, - toolName: 'docx_outline', - input: { sourcePath: '/tmp/report.docx' } - } - const readDisplay = { - kind: 'tool' as const, - toolName: 'docx_read_blocks', - input: { sourcePath: '/tmp/report.docx' } - } - const groups = [ - groupStarted({ groupId, kind: 'skill', display: outlineDisplay }), - itemStarted({ groupId, itemId: 'tool-outline', display: outlineDisplay, createdAt: 2 }), - itemStarted({ groupId, itemId: 'tool-read', display: readDisplay, createdAt: 3 }), - itemCompleted({ groupId, itemId: 'tool-outline', completedAt: 4 }), - groupCompleted({ groupId, completedAt: 5 }) - ].reduce(reduceAssistantProcessGroups, []) - - expect(groups[0].status).toBe('running') - expect(groups[0].items[0].status).toBe('completed') - expect(groups[0].items[1].status).toBe('running') - }) - - it('keeps docx edit precise to the file and marks completion naturally', () => { - const groupId = 'run-1:skill:docx-writer' - const display = { - kind: 'tool' as const, - toolName: 'docx_edit', - input: { sourcePath: '/tmp/draft.docx' } - } - const groups = [ - groupStarted({ groupId, kind: 'skill', display }), - itemStarted({ groupId, itemId: 'tool-edit', display }), - itemCompleted({ groupId, itemId: 'tool-edit' }), - groupCompleted({ groupId }) - ].reduce(reduceAssistantProcessGroups, []) - - expect(toText(buildAssistantExecutionSummaryText(groups))).toBe('已编辑 1 个 docx 文件') - expect(groups[0].status).toBe('completed') - expect(toText(groups[0].items[0].runningText)).toBe('正在编辑 /tmp/draft.docx 文件') - expect(toText(groups[0].items[0].completedText)).toBe('已编辑 /tmp/draft.docx 文件') - }) - - it('uses reference-reader text for search and does not expose internal tool names', () => { - const event = groupStarted({ - groupId: 'run-1:skill:reference-reader', - kind: 'skill', - display: { - kind: 'tool', - toolName: 'kb_search', - input: { query: '课程设计' } - } - }) - const groups = reduceAssistantProcessGroups([], event) - const itemGroups = reduceAssistantProcessGroups( - groups, - itemStarted({ - groupId: 'run-1:skill:reference-reader', - itemId: 'tool-search', - display: { - kind: 'tool', - toolName: 'kb_search', - input: { query: '课程设计' } - } - }) - ) - - expect(buildAssistantProcessText(event)).toBe('俺正在查阅资料') - expect(toText(buildAssistantExecutionSummaryText(itemGroups))).toBe('正在检索知识库') - expect(toText(itemGroups[0].items[0].runningText)).toBe('正在搜索 课程设计') - expect(toText(itemGroups[0].items[0].runningText)).not.toContain('kb_search') - }) - - it('keeps learning running until assistant_process_group_completed arrives', () => { - const started = groupStarted({ - groupId: 'run-1:learning:docx-read', - kind: 'learning', - display: { kind: 'learning', skillId: 'docx-read' } - }) - const runningGroups = reduceAssistantProcessGroups([], started) - const completedGroups = reduceAssistantProcessGroups( - runningGroups, - groupCompleted({ - groupId: 'run-1:learning:docx-read', - display: { kind: 'learning', skillId: 'docx-read' } - }) - ) - - expect(toText(runningGroups[0].runningText)).toBe('正在学习 docx 阅读器') - expect(runningGroups[0].status).toBe('running') - expect(toText(completedGroups[0].completedText)).toBe('已学会 docx 阅读器') - expect(completedGroups[0].status).toBe('completed') - }) - - it('maps context compaction protocol to dedicated groups', () => { - const compactionGroups = [ - groupStarted({ - groupId: 'run-1:compaction', - kind: 'compaction', - display: { kind: 'compaction' } - }), - groupCompleted({ - groupId: 'run-1:compaction', - display: { kind: 'compaction' } - }) - ].reduce(reduceAssistantProcessGroups, []) - - expect(toText(compactionGroups[0].runningText)).toBe('东西太多了,让俺整理下') - expect(toText(compactionGroups[0].completedText)).toBe('呼,终于整理好了,累死俺了') - }) - - it('hides finalizing when final answer starts', () => { - const groups = reduceAssistantProcessGroups( - [], - groupStarted({ - groupId: 'run-1:finalizing', - kind: 'finalizing', - display: { kind: 'finalizing' } - }) - ) - - expect(toText(groups[0].runningText)).toBe('基本完成了,让俺收尾下吧') - expect(completeAssistantProcessGroups(groups)).toEqual([]) - }) - - it('does not add legacy mouth events to process groups', () => { - const mouth: AiRunEvent = { - type: 'human_step', - runId: 'run-1', - taskId: 'task-1', - stepIndex: 2, - action: 'deliver_result', - publicNarrative: '准备回答', - learnedSkillIds: [], - effectiveSkillIds: [], - availableToolNames: [], - summary: '', - createdAt: 1 - } - - expect(reduceAssistantProcessGroups([], mouth)).toEqual([]) - }) - - it('creates search-mode placeholder as reference reader', () => { - const group = createSearchProcessGroup('assistant-search') - - expect(toText(group.runningText)).toBe('俺正在查阅资料') - expect(group.skillKey).toBe('reference-reader') - }) -}) diff --git a/tests/renderer/workbench/assistant-turn-card.test.tsx b/tests/renderer/workbench/assistant-turn-card.test.tsx deleted file mode 100644 index 6987a3dc..00000000 --- a/tests/renderer/workbench/assistant-turn-card.test.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import AssistantTurnCard from '../../../src/renderer/src/components/workbench/AssistantTurnCard' -import type { - AssistantProcessGroup, - AssistantTurnViewModel -} from '../../../src/renderer/src/components/workbench/types' -import { - createAssistantThinkingGroup, - createSearchProcessGroup -} from '../../../src/renderer/src/components/workbench/utils' - -vi.mock('../../../src/renderer/src/components/MarkdownRenderer', () => ({ - default: ({ content }: { content: string }) => ( -
{content}
- ) -})) - -const createTurn = (overrides: Partial = {}): AssistantTurnViewModel => ({ - messageId: 'assistant-1', - status: 'running', - processThoughts: [], - processGroups: [createAssistantThinkingGroup('assistant-1')], - finalAnswerMarkdown: '', - artifacts: [], - applyTargets: [], - blockers: [], - ...overrides -}) - -const createToolGroup = (): AssistantProcessGroup => ({ - id: 'run-1:skill:docx-reader', - kind: 'skill', - skillKey: 'docx-reader', - iconKind: 'document', - status: 'running', - runningText: [{ text: '俺正在阅读 ' }, { text: 'docx', emphasis: true }], - completedText: [{ text: '俺已阅读完 ' }, { text: 'docx', emphasis: true }], - failedText: [{ text: '阅读 ' }, { text: 'docx', emphasis: true }, { text: ' 失败了' }], - createdAt: 1, - sourceEventType: 'tool_call_started', - items: [ - { - id: 'tool-1', - toolName: 'docx_outline', - targetLabel: '/tmp/report.docx', - status: 'running', - runningText: [ - { text: '正在阅读 ' }, - { text: '/tmp/report.docx', emphasis: true }, - { text: ' 文件的结构' } - ], - completedText: [ - { text: '已阅读 ' }, - { text: '/tmp/report.docx', emphasis: true }, - { text: ' 文件的结构' } - ], - failedText: [ - { text: '阅读 ' }, - { text: '/tmp/report.docx', emphasis: true }, - { text: ' 文件的结构失败了' } - ], - createdAt: 1, - sourceEventType: 'tool_call_started' - } - ] -}) - -describe('AssistantTurnCard', () => { - it('shows thinking placeholder before final answer without process dots', () => { - render( - - ) - - expect(screen.getByText('让俺想想')).toBeTruthy() - expect(screen.queryByTestId('workbench-assistant-final-answer-assistant-1')).toBeNull() - expect(document.querySelector('.rounded-full.bg-\\[\\#D97757\\]')).toBeNull() - }) - - it('renders search placeholder as reference-reader group', () => { - render( - - ) - - expect(screen.getByText('俺正在查阅资料')).toBeTruthy() - expect(screen.queryByTestId('workbench-assistant-final-answer-assistant-search-1')).toBeNull() - }) - - it('renders markdown final answer below process stream', () => { - render( - - ) - - const process = screen.getByTestId('workbench-assistant-process-assistant-1') - const finalAnswer = screen.getByTestId('workbench-assistant-final-answer-assistant-1') - expect( - process.compareDocumentPosition(finalAnswer) & Node.DOCUMENT_POSITION_FOLLOWING - ).toBeTruthy() - expect(screen.getByTestId('mock-markdown-renderer').textContent).toContain('## 最终结果') - expect(finalAnswer.className).not.toContain('bg-white') - expect(process.className).not.toContain('bg-') - }) - - it('renders public thought text above skill and tool process groups', () => { - render( - - ) - - const thoughts = screen.getByTestId('workbench-assistant-thoughts') - const processGroup = screen.getByTestId('workbench-assistant-process-item-run-1:skill:docx-reader') - expect(screen.getByText('俺先看一下文档结构,再决定读哪些细节。')).toBeTruthy() - expect( - thoughts.compareDocumentPosition(processGroup) & Node.DOCUMENT_POSITION_FOLLOWING - ).toBeTruthy() - }) - - it('renders tool group with emphasized file name in expanded tool sentence', () => { - const { container } = render( - - ) - - expect(screen.getByText('正在读取 1 个 docx 文件')).toBeTruthy() - const fileName = screen.getByText('/tmp/report.docx') - expect(fileName.className).toContain('font-semibold') - expect(screen.getByText('文件的结构')).toBeTruthy() - expect(screen.queryByText('这一步处理完了')).toBeNull() - const details = container.querySelector( - '[data-testid="workbench-assistant-process-item-run-1:skill:docx-reader"]' - ) - expect(details?.hasAttribute('open')).toBe(false) - }) - - it('renders finalizing as a transient shimmer layer before markdown', () => { - const finalizingGroup: AssistantProcessGroup = { - id: 'run-1:finalizing', - kind: 'finalizing', - skillKey: 'generic-task', - iconKind: 'finalizing', - status: 'running', - runningText: [{ text: '基本完成了,让俺收尾下吧' }], - completedText: [{ text: '收尾完成了' }], - failedText: [{ text: '收尾失败了' }], - createdAt: 2, - sourceEventType: 'assistant_process_group_started', - items: [] - } - - render( - - ) - - expect(screen.getByText('基本完成了,让俺收尾下吧').parentElement?.className).toContain( - 'workbench-thinking-shimmer' - ) - }) - - it('hides finalizing once markdown is available', () => { - const finalizingGroup: AssistantProcessGroup = { - id: 'run-1:finalizing', - kind: 'finalizing', - skillKey: 'generic-task', - iconKind: 'finalizing', - status: 'running', - runningText: [{ text: '基本完成了,让俺收尾下吧' }], - completedText: [{ text: '收尾完成了' }], - failedText: [{ text: '收尾失败了' }], - createdAt: 2, - sourceEventType: 'assistant_process_group_started', - items: [] - } - - render( - - ) - - expect(screen.queryByText('基本完成了,让俺收尾下吧')).toBeNull() - expect(screen.getByTestId('mock-markdown-renderer').textContent).toBe('收好了') - }) -}) diff --git a/tests/renderer/workbench/browser-use-e2e-helpers.ts b/tests/renderer/workbench/browser-use-e2e-helpers.ts deleted file mode 100644 index e609eb11..00000000 --- a/tests/renderer/workbench/browser-use-e2e-helpers.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { createServer, type Server } from 'node:http' -import type { AddressInfo } from 'node:net' -import type { Page } from 'playwright' -import type { BrowserUseResult } from '../../../src/main/services/ai/types' -import type { BrowserPanelE2ERunRequest, JsonValue } from '../../../src/shared/types' -import { workbenchSelectors } from '../../../src/renderer/src/testing/selectors' -import { getWorkbenchLocator } from '../../harness/e2e/workbench' - -type FixtureRoute = { - statusCode?: number - headers?: Record - body: string -} - -type FixtureServer = { - url: (pathname: string) => string - requests: () => readonly string[] - close: () => Promise -} - -const defaultHeaders = { - 'Content-Type': 'text/html; charset=utf-8' -} as const - -const normalizeRoute = (route: string | FixtureRoute): FixtureRoute => - typeof route === 'string' - ? { - body: route - } - : route - -const closeServer = async (server: Server): Promise => { - await new Promise((resolve, reject) => { - server.close((error) => { - if (error) { - reject(error) - return - } - resolve() - }) - }) -} - -export const startWorkbenchBrowserFixtureServer = async ( - routes: Record -): Promise => { - const requests: string[] = [] - const server = createServer((request, response) => { - const pathname = new URL(request.url || '/', 'http://127.0.0.1').pathname - requests.push(pathname) - const route = routes[pathname] - if (!route) { - response.writeHead(404, defaultHeaders) - response.end('Not FoundNot Found') - return - } - - const normalized = normalizeRoute(route) - response.writeHead(normalized.statusCode || 200, { - ...defaultHeaders, - ...(normalized.headers || {}) - }) - response.end(normalized.body) - }) - - await new Promise((resolve, reject) => { - server.once('error', reject) - server.listen(0, '127.0.0.1', () => { - server.off('error', reject) - resolve() - }) - }) - - const address = server.address() as AddressInfo - const origin = `http://127.0.0.1:${String(address.port)}` - return { - url: (pathname) => `${origin}${pathname.startsWith('/') ? pathname : `/${pathname}`}`, - requests: () => [...requests], - close: async () => { - await closeServer(server) - } - } -} - -const isJsonObject = (value: JsonValue | undefined): value is Record => - typeof value === 'object' && value !== null && !Array.isArray(value) - -const toBrowserUseResult = (value: JsonValue | undefined): BrowserUseResult => { - if (!isJsonObject(value)) { - throw new Error('browser panel e2e returned empty result') - } - if (value.action !== 'extract' && value.action !== 'observe' && value.action !== 'act') { - throw new Error('browser panel e2e returned invalid action result') - } - return value as BrowserUseResult -} - -export const openWorkbenchBrowserPanel = async (page: Page): Promise => { - await page.evaluate(() => { - window.dispatchEvent(new Event('ncurator-e2e-open-browser-panel')) - }) - await getWorkbenchLocator(page, workbenchSelectors.browserPanel).waitFor({ state: 'visible' }) - await getWorkbenchLocator(page, workbenchSelectors.browserViewport).waitFor({ state: 'visible' }) - await page.evaluate(async (testId) => { - const element = document.querySelector(`[data-testid="${testId}"]`) - if (!(element instanceof HTMLElement)) { - throw new Error('browser viewport not found') - } - const rect = element.getBoundingClientRect() - const boundsResponse = await window.api.browserPanelSetBounds({ - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - visible: rect.width > 0 && rect.height > 0 - }) - if (!boundsResponse.success) { - throw new Error(boundsResponse.error || 'browser panel bounds sync failed') - } - const showResponse = await window.api.browserPanelShow() - if (!showResponse.success) { - throw new Error(showResponse.error || 'browser panel show failed') - } - }, workbenchSelectors.browserViewport) -} - -export const runBrowserPanelE2E = async ( - page: Page, - payload: BrowserPanelE2ERunRequest -): Promise => { - const response = await page.evaluate( - async (input) => await window.api.browserPanelE2ERun(input), - payload - ) - if (!response.success) { - throw new Error(response.error || 'browser panel e2e run failed') - } - return toBrowserUseResult(response.result) -} diff --git a/tests/renderer/workbench/browser-use-embedded-dom.e2e.test.ts b/tests/renderer/workbench/browser-use-embedded-dom.e2e.test.ts deleted file mode 100644 index a32205c5..00000000 --- a/tests/renderer/workbench/browser-use-embedded-dom.e2e.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { describe, it } from 'vitest' -import { loadBrowserE2ELlmConfigFromEnv } from '../../harness/e2e/browserLlmConfig' -import { runE2ECase } from '../../harness/e2e/run-e2e-case' -import { waitForWorkbenchReady } from '../../harness/e2e/workbench' -import { - openWorkbenchBrowserPanel, - runBrowserPanelE2E, - startWorkbenchBrowserFixtureServer -} from './browser-use-e2e-helpers' - -const WORKBENCH_BROWSER_DOM_E2E_TIMEOUT_MS = 420_000 - -const domFixtureHtml = ` - - - - Browser DOM E2E Fixture - - - -
-

Browser DOM E2E Fixture

-

Alpha status: idle

- -
- - -` - -type DomE2EResult = { - extractContent: string - observedActionCount: number - observedActionText: string - actSuccess: boolean - actMessage: string - pageState: string - finalContent: string -} - -describe('workbench browser panel e2e', () => { - it('browser-use-embedded-dom', async () => { - await runE2ECase({ - caseId: 'browser-use-embedded-dom', - domain: 'renderer', - suite: 'workbench', - description: - 'Workbench 右侧内嵌浏览器应能连接 Stagehand DOM 能力完成 extract、observe、act,并真实改变页面状态。', - fixtures: [], - runMode: 'single', - execute: async (runtime) => { - const { page } = runtime - const fixture = await startWorkbenchBrowserFixtureServer({ - '/dom': domFixtureHtml - }) - - try { - const llmConfig = loadBrowserE2ELlmConfigFromEnv() - const targetUrl = fixture.url('/dom') - const preflightResponse = await fetch(targetUrl) - const preflightText = await preflightResponse.text() - runtime.signal({ - name: 'fixture-preflight', - message: 'Node 侧预检本地浏览器 fixture 可达。', - data: { - status: preflightResponse.status, - textLength: preflightText.length, - requests: fixture.requests() - } - }) - - await runtime.step({ - name: 'open-browser-panel', - message: '打开 Workbench 右侧内置浏览器面板。', - run: async () => { - await waitForWorkbenchReady(page) - await openWorkbenchBrowserPanel(page) - } - }) - - const extractResult = await runtime.step({ - name: 'dom-extract-idle', - message: '通过 Stagehand DOM extract 读取初始页面状态。', - run: async () => { - try { - return await runBrowserPanelE2E(page, { - action: 'extract', - instruction: 'Extract the current Alpha status text. Return only that visible text.', - url: targetUrl, - llmConfig - }) - } catch (error) { - runtime.signal({ - name: 'fixture-requests-after-navigation-failure', - message: '记录浏览器导航失败时本地 fixture 收到的请求。', - status: 'failed', - data: { - requests: fixture.requests(), - error: error instanceof Error ? error.message : String(error) - } - }) - throw error - } - } - }) - - const observeResult = await runtime.step({ - name: 'dom-observe-actions', - message: '通过 Stagehand DOM observe 观察可执行动作。', - run: async () => - await runBrowserPanelE2E(page, { - action: 'observe', - instruction: 'Find the action that marks the Alpha status complete.', - llmConfig - }) - }) - - const actResult = await runtime.step({ - name: 'dom-act-complete', - message: '通过 Stagehand DOM act 点击完成按钮。', - run: async () => - await runBrowserPanelE2E(page, { - action: 'act', - instruction: 'Click the Mark Complete button.', - llmConfig - }) - }) - - const finalExtractResult = await runtime.step({ - name: 'dom-extract-complete', - message: '再次读取页面状态,确认真实页面已变化。', - run: async () => - await runBrowserPanelE2E(page, { - action: 'extract', - instruction: 'Extract the current Alpha status text. Return only that visible text.', - llmConfig - }) - }) - - if (extractResult.action !== 'extract') throw new Error('expected extract result') - if (observeResult.action !== 'observe') throw new Error('expected observe result') - if (actResult.action !== 'act') throw new Error('expected act result') - if (finalExtractResult.action !== 'extract') throw new Error('expected final extract result') - - return { - extractContent: extractResult.content, - observedActionCount: observeResult.actions.length, - observedActionText: observeResult.actions.map((action) => action.description).join('\n'), - actSuccess: actResult.success, - actMessage: actResult.message, - pageState: actResult.pageState || '', - finalContent: finalExtractResult.content - } - } finally { - await fixture.close() - } - }, - assert: async (result, runtime) => { - await runtime.assertThat(result.extractContent.toLowerCase().includes('idle'), { - name: 'dom-extract-reads-initial-state', - message: 'DOM extract 应能读取内嵌浏览器页面的初始状态。', - data: { - extractContent: result.extractContent - } - }) - await runtime.assertThat(result.observedActionCount > 0, { - name: 'dom-observe-returns-actions', - message: 'DOM observe 应返回可执行动作。', - data: { - observedActionCount: result.observedActionCount, - observedActionText: result.observedActionText - } - }) - await runtime.assertThat( - result.actSuccess && result.pageState.toLowerCase().includes('complete'), - { - name: 'dom-act-mutates-page', - message: 'DOM act 应真实点击页面按钮,并在页面状态中体现完成态。', - data: { - actSuccess: result.actSuccess, - actMessage: result.actMessage, - pageState: result.pageState - } - } - ) - await runtime.assertThat(result.finalContent.toLowerCase().includes('complete'), { - name: 'dom-extract-reads-mutated-state', - message: '动作后再次 DOM extract 应读到页面完成态。', - data: { - finalContent: result.finalContent - } - }) - } - }) - }, WORKBENCH_BROWSER_DOM_E2E_TIMEOUT_MS) -}) diff --git a/tests/renderer/workbench/workbench-composer.test.tsx b/tests/renderer/workbench/workbench-composer.test.tsx deleted file mode 100644 index 783c028c..00000000 --- a/tests/renderer/workbench/workbench-composer.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import WorkbenchComposer from '../../../src/renderer/src/components/workbench/WorkbenchComposer' -import { workbenchSelectors } from '../../../src/renderer/src/testing/selectors' - -const renderComposer = (overrides: Partial> = {}) => { - const props: React.ComponentProps = { - mode: 'ai', - onModeChange: vi.fn(), - activeModelConfig: null, - remoteModels: [], - selectedModelName: 'model-a', - onModelNameChange: vi.fn(), - workspaces: [{ id: 'workspace-1', name: '工作区', created_at: 1, updated_at: 1 }], - selectedWorkspaceId: 'workspace-1', - onWorkspaceChange: vi.fn(), - onDraftChange: vi.fn(), - onSubmit: vi.fn(), - onCancel: vi.fn(), - loading: false, - canSubmit: true, - showCancel: false, - resetKey: 0, - contextUsage: null, - ...overrides - } - render() - return props -} - -describe('WorkbenchComposer', () => { - it('uses the send button as the only stop control while an ai run is active', () => { - const onSubmit = vi.fn() - const onCancel = vi.fn() - renderComposer({ - loading: false, - showCancel: true, - onSubmit, - onCancel - }) - - expect(screen.queryByTestId(workbenchSelectors.cancelButton)).toBeNull() - const sendButton = screen.getByTestId(workbenchSelectors.sendButton) - expect(sendButton.getAttribute('aria-label')).toBe('停止本次运行') - - fireEvent.click(sendButton) - - expect(onCancel).toHaveBeenCalledTimes(1) - expect(onSubmit).not.toHaveBeenCalled() - }) - - it('disables the stop button only after cancellation is already in progress', () => { - renderComposer({ - showCancel: true, - cancelling: true - }) - - expect(screen.getByTestId(workbenchSelectors.sendButton)).toHaveProperty('disabled', true) - }) -}) diff --git a/tests/renderer/workbench/workbench-content-panel.test.tsx b/tests/renderer/workbench/workbench-content-panel.test.tsx deleted file mode 100644 index 7b0fb218..00000000 --- a/tests/renderer/workbench/workbench-content-panel.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import WorkbenchContentPanel from '../../../src/renderer/src/components/workbench/WorkbenchContentPanel' -import type { WorkbenchContentPanelState } from '../../../src/renderer/src/components/workbench/types' -import { workbenchSelectors } from '../../../src/renderer/src/testing/selectors' -import type { SearchResult } from '../../../src/shared/types' - -vi.mock('../../../src/renderer/src/components/fileRenders', () => ({ - FileRenderViewer: ({ documents }: { documents: Array<{ fileName: string; filePath: string }> }) => ( -
- {documents.map((document) => ( - {document.fileName} - ))} -
- ) -})) - -const createSearchResult = (input: { - id: string - name: string - path: string - sourceType: 'file' | 'web' -}): SearchResult => ({ - id: input.id, - text: `content for ${input.name}`, - document_name: input.name, - source_type: input.sourceType, - _score: 0.8, - document: { - id: `${input.id}-doc`, - name: input.name, - source_type: input.sourceType, - file_path: input.path, - created_at: 1, - import_status: 2 - } -}) - -const renderPanel = (state: WorkbenchContentPanelState) => { - const props: React.ComponentProps = { - state, - activeApplyRecord: null, - applyLoading: false, - applyReverting: false, - onOpenSearchResult: vi.fn(), - onSelectApplyTab: vi.fn(), - onRevertApply: vi.fn(), - onBackToPanelState: vi.fn(), - onOpenPath: vi.fn(), - onOpenWithApplication: vi.fn(), - onRevealPath: vi.fn() - } - render() - return props -} - -describe('WorkbenchContentPanel', () => { - it('opens local search results through the file preview path', () => { - const state: WorkbenchContentPanelState = { - kind: 'search_list', - title: '搜索结果', - query: '报告', - results: [ - createSearchResult({ - id: 'result-1', - name: 'report.docx', - path: '/workspace/report.docx', - sourceType: 'file' - }) - ], - tokens: ['报告'] - } - const props = renderPanel(state) - - fireEvent.click(screen.getByRole('button', { name: '预览文件' })) - - expect(props.onOpenSearchResult).toHaveBeenCalledTimes(1) - expect(props.onOpenSearchResult).toHaveBeenCalledWith( - expect.objectContaining({ - query: '报告', - results: state.results, - tokens: ['报告'] - }), - 0 - ) - }) - - it('renders file preview with back, reveal and system-open controls', () => { - const backTarget: WorkbenchContentPanelState = { - kind: 'search_list', - title: '搜索结果', - query: '报告', - results: [], - tokens: ['报告'] - } - const state: WorkbenchContentPanelState = { - kind: 'file_preview', - title: '文档查看', - file: { - path: '/workspace/report.docx', - fileName: 'report.docx', - origin: 'search_hit' - }, - backTarget - } - const props = renderPanel(state) - - expect(screen.getByTestId(workbenchSelectors.documentView)).toBeTruthy() - expect(screen.getAllByText('report.docx').length).toBeGreaterThan(0) - - fireEvent.click(screen.getByRole('button', { name: '返回' })) - fireEvent.click(screen.getByRole('button', { name: '定位文件' })) - fireEvent.click(screen.getByRole('button', { name: '系统打开' })) - - expect(props.onBackToPanelState).toHaveBeenCalledWith(backTarget) - expect(props.onRevealPath).toHaveBeenCalledWith('/workspace/report.docx') - expect(props.onOpenPath).toHaveBeenCalledWith('/workspace/report.docx') - }) - - it('opens web search results through the search-result callback without file preview', () => { - const state: WorkbenchContentPanelState = { - kind: 'search_list', - title: '搜索结果', - query: '网页', - results: [ - createSearchResult({ - id: 'result-web', - name: 'Example', - path: 'https://example.com', - sourceType: 'web' - }) - ], - tokens: ['网页'] - } - const props = renderPanel(state) - - fireEvent.click(screen.getByRole('button', { name: '打开网页' })) - - expect(props.onOpenSearchResult).toHaveBeenCalledTimes(1) - expect(screen.queryByTestId(workbenchSelectors.documentView)).toBeNull() - }) -}) diff --git a/tests/renderer/workbench/workbench-preferences.test.ts b/tests/renderer/workbench/workbench-preferences.test.ts deleted file mode 100644 index b823af8f..00000000 --- a/tests/renderer/workbench/workbench-preferences.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest' -import type { LlmRemoteModel, WorkspaceSource } from '../../../src/shared/types' -import { - readWorkbenchModePreference, - readWorkbenchModelPreference, - readWorkbenchWorkspacePreference, - resolveWorkbenchModelSelection, - resolveWorkbenchWorkspaceSelection, - writeWorkbenchModePreference, - writeWorkbenchModelPreference, - writeWorkbenchWorkspacePreference -} from '../../../src/renderer/src/components/workbench/utils' - -const workspaceFixtures: WorkspaceSource[] = [ - { - id: 'workspace-1', - name: '资料库 A', - created_at: 1, - updated_at: 1 - }, - { - id: 'workspace-2', - name: '资料库 B', - created_at: 2, - updated_at: 2 - } -] - -const modelFixtures: LlmRemoteModel[] = [ - { id: 'model-a', label: 'Model A' }, - { id: 'model-b', label: 'Model B' } -] - -describe('workbench preferences', () => { - beforeEach(() => { - window.localStorage.clear() - }) - - it('persists mode as ai/search and falls back to ai for invalid values', () => { - expect(readWorkbenchModePreference(window.localStorage)).toBe('ai') - - writeWorkbenchModePreference(window.localStorage, 'search') - expect(readWorkbenchModePreference(window.localStorage)).toBe('search') - - window.localStorage.setItem('workbench.preference.mode', 'invalid') - expect(readWorkbenchModePreference(window.localStorage)).toBe('ai') - }) - - it('restores workspace preference and falls back to the first available workspace', () => { - writeWorkbenchWorkspacePreference(window.localStorage, 'workspace-2') - expect(readWorkbenchWorkspacePreference(window.localStorage)).toBe('workspace-2') - expect(resolveWorkbenchWorkspaceSelection(workspaceFixtures, 'workspace-2')).toBe('workspace-2') - - expect(resolveWorkbenchWorkspaceSelection(workspaceFixtures, 'workspace-missing')).toBe( - 'workspace-1' - ) - - writeWorkbenchWorkspacePreference(window.localStorage, null) - expect(readWorkbenchWorkspacePreference(window.localStorage)).toBeNull() - }) - - it('restores workbench model preference before default_model and falls back when invalid', () => { - writeWorkbenchModelPreference(window.localStorage, { - configId: 'config-1', - modelName: 'model-b' - }) - - expect(readWorkbenchModelPreference(window.localStorage)).toEqual({ - configId: 'config-1', - modelName: 'model-b' - }) - - expect( - resolveWorkbenchModelSelection({ - configId: 'config-1', - models: modelFixtures, - defaultModelName: 'model-a', - preference: readWorkbenchModelPreference(window.localStorage) - }) - ).toBe('model-b') - - expect( - resolveWorkbenchModelSelection({ - configId: 'config-2', - models: modelFixtures, - defaultModelName: 'model-a', - preference: readWorkbenchModelPreference(window.localStorage) - }) - ).toBe('model-a') - - expect( - resolveWorkbenchModelSelection({ - configId: 'config-1', - models: [{ id: 'model-a', label: 'Model A' }], - defaultModelName: 'model-a', - preference: readWorkbenchModelPreference(window.localStorage) - }) - ).toBe('model-a') - }) -}) diff --git a/tests/temp/ai/human/live/human-kb-guided-docx-reading-live.test.ts b/tests/temp/ai/human/live/human-kb-guided-docx-reading-live.test.ts deleted file mode 100644 index e25335ce..00000000 --- a/tests/temp/ai/human/live/human-kb-guided-docx-reading-live.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it } from 'vitest' -import { loadLiveLlmConfigFromEnv, hasLiveLlmConfigInEnv } from '../../../../harness/backend/llmConfig' -import { createHumanTestRunner } from '../../../../harness/backend/humanHarness' -import { runBackendCase } from '../../../../harness/backend/run-backend-case' -import { - HUMAN_KB_GUIDED_DOCX_READING_USER_INPUT, - SHARED_WORKSPACE_FIXTURE_PATH -} from '../../../../harness/backend/aiLiveTestData' -import { - collectStartedToolStringArgs, - filterAiRunEventsByType, - hasKbToolCall, - runWithStructuredOutputRetry -} from '../../../../ai/human/testUtils' - -describe('temp ai human live backend', () => { - it('human-kb-guided-docx-reading-live', async () => { - await runWithStructuredOutputRetry(async () => { - await runBackendCase({ - caseId: 'human-kb-guided-docx-reading-live', - harnessMode: 'live', - domain: 'temp', - suite: 'ai', - groups: ['human', 'live'], - description: '观察 live 模式下 human 在工作区资料较多时,会如何用 KB 缩圈并回答过拟合与反走样的处理思路。', - fixtures: [SHARED_WORKSPACE_FIXTURE_PATH], - runMode: 'single', - runtimeModelName: hasLiveLlmConfigInEnv() - ? loadLiveLlmConfigFromEnv().model_name - : null, - execute: async (runtime) => { - if (!hasLiveLlmConfigInEnv()) { - return { - skipped: true, - output: null, - events: [] - } - } - - await runtime.seedLlmConfig(loadLiveLlmConfigFromEnv()) - await runtime.seedWorkspaceKb({ - mode: 'all-supported' - }) - - const runner = createHumanTestRunner(runtime) - const output = await runner.run({ - taskId: runtime.taskId, - userInput: HUMAN_KB_GUIDED_DOCX_READING_USER_INPUT - }) - - return { - skipped: false, - output, - events: runtime.eventCollector.snapshot() - } - }, - assert: async (result, runtime) => { - if (result.skipped) { - runtime.assertThat(true, { - name: 'live-config-not-provided', - message: '未提供 live 模型配置,已跳过 temp human live KB-guided docx reading case。' - }) - return - } - - const toolResults = filterAiRunEventsByType(result.events, 'tool_call_result') - const deliveryText = String(result.output?.deliveryMarkdown || '') - const openedSourcePaths = collectStartedToolStringArgs( - result.events, - 'docx_open', - 'sourcePath' - ) - - runtime.assertThat( - result.output?.status === 'completed' && - hasKbToolCall(result.events) && - /(数据增强|未使用过的数据|验证数据)/.test(deliveryText) && - /(低通滤波|多个采样点|多个像素点|多次采样)/.test(deliveryText), - { - name: 'temp-human-live-kb-guided-reading-covers-kb-and-answer-facts', - message: 'temp KB-guided 读 case 应至少触发 KB,并回答过拟合与反走样的处理思路。', - data: { - openedSourcePaths, - toolNames: toolResults.map((event) => event.toolName), - deliveryMarkdown: result.output?.deliveryMarkdown - } - } - ) - } - }) - }) - }, 600_000) -}) diff --git a/tests/temp/ai/tool/docx/docx-insert-after-live-regression-repro.test.ts b/tests/temp/ai/tool/docx/docx-insert-after-live-regression-repro.test.ts deleted file mode 100644 index 39ba427d..00000000 --- a/tests/temp/ai/tool/docx/docx-insert-after-live-regression-repro.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -import fs from 'node:fs/promises' -import { describe, it } from 'vitest' -import { - buildBlockIndex, - loadDocxBundle -} from '../../../../../src/main/services/ai/human/tools/docx/adapters/ooxml/blockIndex' -import { - createDocxToolHarness, - writeRichTestDocxFile -} from '../../../../harness/backend/aiToolHarness' -import { runBackendCase } from '../../../../harness/backend/run-backend-case' -import type { - DocxMutationResult, - DocxOutlineResult, - DocxReadResult -} from '../../../../ai/tool/docx/testUtils' - -describe('temp ai tool docx backend', () => { - it('docx-insert-after-live-regression-repro', async () => { - await runBackendCase({ - caseId: 'docx-insert-after-live-regression-repro', - harnessMode: 'tool-real', - domain: 'temp', - suite: 'ai', - groups: ['tool', 'docx'], - description: '最小复现 insert_after 导致 anchor 样式漂移、createdBlocks 误认尾块、review 重复旧段落的 live 回归。', - fixtures: [], - runMode: 'single', - execute: async (runtime) => { - await writeRichTestDocxFile(runtime.workspaceRoot, 'insert-after-live-repro.docx', [ - { - type: 'paragraph', - paragraph: { - runs: [ - { text: '学生可以通过', bold: true }, - { text: '点击对话中的按钮', color: '2563EB' }, - { text: '进入引导流程。', underline: true } - ] - } - }, - { - type: 'paragraph', - paragraph: { - runs: [{ text: '学生端脚手架引导:' }] - } - }, - { - type: 'paragraph', - paragraph: { - runs: [{ text: '引导完成后进入正式练习。' }] - } - } - ]) - - runtime.fileReviewService.beginRun({ - runId: runtime.runId, - sessionId: 'docx-insert-after-live-regression-repro-session', - inputText: '补充脚手架说明和对照表' - }) - - const harness = createDocxToolHarness(runtime) - try { - const outlineBefore = await harness.invoke('docx_outline', { - sourcePath: 'insert-after-live-repro.docx', - ranges: [{ type: 'block_index', fromBlockIndex: 0, toBlockIndex: 49 }] - }) - const anchorBlockId = - outlineBefore.blocks.find( - (block) => - block.type === 'paragraph' && - block.text.includes('学生可以通过点击对话中的按钮进入引导流程。') - )?.blockId || '' - - const anchorBefore = await harness.invoke('docx_read_blocks', { - sourcePath: 'insert-after-live-repro.docx', - range: { type: 'block_ids', blockIds: [anchorBlockId] } - }) - - const insertedParagraph = await harness.invoke('docx_edit', { - sourcePath: 'insert-after-live-repro.docx', - actions: [{ - type: 'insert_blocks', - anchorBlockId, - position: 'after', - blocks: [ - { - type: 'paragraph', - text: '学生可以根据按钮提示逐步完成脚手架操作。' - } - ] - }] - }) - const insertedParagraphBlockId = insertedParagraph.createdBlocks?.find( - (block) => block.type === 'paragraph' - )?.blockId || '' - - const insertedTable = await harness.invoke('docx_edit', { - sourcePath: insertedParagraph.sourcePath, - actions: [{ - type: 'insert_blocks', - anchorBlockId: insertedParagraphBlockId, - position: 'after', - blocks: [ - { - type: 'table', - rows: [ - ['入口按钮', '对应动作'], - ['开始引导', '进入脚手架'] - ] - } - ] - }] - }) - - const anchorAfter = await harness.invoke('docx_read_blocks', { - sourcePath: insertedTable.sourcePath, - range: { type: 'block_ids', blockIds: [anchorBlockId] } - }) - const outlineAfter = await harness.invoke('docx_outline', { - sourcePath: insertedTable.sourcePath, - ranges: [{ type: 'block_index', fromBlockIndex: 0, toBlockIndex: 79 }] - }) - const createdPreviews = [ - ...(insertedParagraph.createdBlocks || []), - ...(insertedTable.createdBlocks || []) - ].map((created) => ({ - ...created, - preview: - outlineAfter.blocks.find((block) => block.blockId === created.blockId)?.text || '' - })) - - const details = await runtime.fileReviewService.finalizeRunReview({ - runId: runtime.runId, - workspaceRootPath: runtime.workspaceRoot - }) - const reviewPath = details[0]?.review.draft_artifact_path - if (!reviewPath) { - throw new Error('expected review artifact path') - } - - const reviewBundle = await loadDocxBundle(await fs.readFile(reviewPath)) - const reviewOutline = buildBlockIndex(reviewBundle.documentDom).outline - .filter((block) => block.type !== 'table_cell') - .map((block) => ({ - type: block.type, - text: block.type === 'table' ? block.preview : block.text - })) - - return { - anchorBefore: anchorBefore.blocks[0], - anchorAfter: anchorAfter.blocks[0], - insertedParagraph, - insertedTable, - outlineAfter, - createdPreviews, - reviewOutline - } - } finally { - await harness.release() - } - }, - assert: async (result, runtime) => { - runtime.assertThat( - result.anchorBefore?.type === 'paragraph' && - result.anchorAfter?.type === 'paragraph' && - JSON.stringify(result.anchorBefore.runs) === JSON.stringify(result.anchorAfter.runs), - { - name: 'anchor-runs-remain-identical-after-structural-insert', - message: '结构插入后,anchor 原段落的 run 样式不应发生漂移。', - data: { - anchorBefore: result.anchorBefore, - anchorAfter: result.anchorAfter - } - } - ) - - const createdBlockIds = [ - ...result.createdPreviews - ] - runtime.assertThat( - !createdBlockIds.some((block) => block.preview.includes('学生端脚手架引导:')), - { - name: 'created-blocks-do-not-capture-shifted-tail-block', - message: 'createdBlocks 只能命中新插入块,不得把下游旧段落误认为新增块。', - data: { - createdBlocks: { - paragraph: result.insertedParagraph.createdBlocks, - table: result.insertedTable.createdBlocks - }, - createdPreviews: result.createdPreviews, - outlineAfter: result.outlineAfter.blocks - } - } - ) - - runtime.assertThat( - result.reviewOutline.filter((block) => - String(block.text || '').includes('学生端脚手架引导:') - ).length === 1, - { - name: 'review-does-not-duplicate-downstream-tail-block', - message: 'review artifact 中下游旧段落只应出现一次,不得因插入重绑错误而重复。', - data: { - reviewOutline: result.reviewOutline - } - } - ) - } - }) - }) -}) diff --git a/tests/unit/ai/llm-stream-raw-sse-live-probe.test.ts b/tests/unit/ai/llm-stream-raw-sse-live-probe.test.ts deleted file mode 100644 index c51d9b2d..00000000 --- a/tests/unit/ai/llm-stream-raw-sse-live-probe.test.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { HumanMessage, SystemMessage } from '@langchain/core/messages' -import fs from 'node:fs/promises' -import { describe, expect, it } from 'vitest' -import { z } from 'zod/v4' -import { createChatModel } from '../../../src/main/services/llm/chatModel' -import { normalizeOpenAIBaseUrl } from '../../../src/shared/llmBaseUrl' -import { extractTextContent } from '../../../src/main/services/ai/structuredOutput' -import { humanThoughtDecisionSchema } from '../../../src/main/services/ai/human/schema' -import type { LlmRuntimeConfig } from '../../../src/shared/types' -import { - hasLiveLlmConfigInEnv, - loadLiveLlmConfigFromEnv -} from '../../harness/backend/llmConfig' - -type StreamStats = { - chunkCount: number - nonBlankChunkCount: number - blankChunkCount: number - maxConsecutiveBlankChunks: number - stoppedAfterBlankStall: boolean - totalTextLength: number - trimmedTextLength: number - firstSamples: string[] - reasoningChunkCount: number - nonBlankReasoningChunkCount: number - blankReasoningChunkCount: number - totalReasoningLength: number - firstReasoningSamples: string[] - observedChunkKeys: string[] - observedAdditionalKwargKeys: string[] - finishReasons: string[] -} - -type ReplayPrompt = { - callId?: string - schemaName: string - system: string - user: string -} - -const schema = z.object({ - answer: z.string() -}) - -const systemPrompt = - 'You are a structured streaming transport probe. Return only valid JSON matching the schema.' -const userPrompt = 'Return a JSON object where answer is exactly "ok".' - -const emptyStats = (): StreamStats => ({ - chunkCount: 0, - nonBlankChunkCount: 0, - blankChunkCount: 0, - maxConsecutiveBlankChunks: 0, - stoppedAfterBlankStall: false, - totalTextLength: 0, - trimmedTextLength: 0, - firstSamples: [], - reasoningChunkCount: 0, - nonBlankReasoningChunkCount: 0, - blankReasoningChunkCount: 0, - totalReasoningLength: 0, - firstReasoningSamples: [], - observedChunkKeys: [], - observedAdditionalKwargKeys: [], - finishReasons: [] -}) - -const addUnique = (target: string[], values: string[]): void => { - for (const value of values) { - if (!target.includes(value)) target.push(value) - } -} - -const addTextToStats = (stats: StreamStats, text: string, blankStreak: number): number => { - stats.chunkCount += 1 - stats.totalTextLength += text.length - stats.trimmedTextLength += text.trim().length - if (stats.firstSamples.length < 8) { - stats.firstSamples.push(text.slice(0, 80)) - } - - if (text.trim()) { - stats.nonBlankChunkCount += 1 - return 0 - } - - const nextBlankStreak = blankStreak + 1 - stats.blankChunkCount += 1 - stats.maxConsecutiveBlankChunks = Math.max( - stats.maxConsecutiveBlankChunks, - nextBlankStreak - ) - return nextBlankStreak -} - -const addReasoningToStats = (stats: StreamStats, text: string): void => { - stats.reasoningChunkCount += 1 - stats.totalReasoningLength += text.length - if (text.trim()) { - stats.nonBlankReasoningChunkCount += 1 - } else { - stats.blankReasoningChunkCount += 1 - } - if (stats.firstReasoningSamples.length < 8) { - stats.firstReasoningSamples.push(text.slice(0, 80)) - } -} - -const buildCompletionsUrl = (baseUrl: string): string => { - const normalized = normalizeOpenAIBaseUrl(baseUrl).replace(/\/$/, '') - return `${normalized}/chat/completions` -} - -const resolveProbeSchema = (schemaName: string): z.ZodType> => - schemaName === 'human_brain_decision' ? humanThoughtDecisionSchema : schema - -const loadReplayPromptFromEnv = async (): Promise => { - const llmIoPath = String(process.env.AI_LIVE_STREAM_PROBE_LLM_IO_PATH || '').trim() - if (!llmIoPath) return undefined - - const targetCallId = String(process.env.AI_LIVE_STREAM_PROBE_CALL_ID || '').trim() - const lines = (await fs.readFile(llmIoPath, 'utf8')).split(/\r?\n/).filter(Boolean) - const records = lines - .map((line) => JSON.parse(line) as Partial & { caller?: string }) - .filter( - (record): record is ReplayPrompt & { caller?: string } => - typeof record.schemaName === 'string' && - typeof record.system === 'string' && - typeof record.user === 'string' - ) - const record = targetCallId - ? records.find((item) => item.callId === targetCallId) - : records - .slice() - .reverse() - .find((item) => item.schemaName === 'human_brain_decision' || item.caller === 'human.brain') - - if (!record) { - throw new Error(`llm_io_replay_record_not_found:${llmIoPath}`) - } - - return { - callId: record.callId, - schemaName: record.schemaName, - system: record.system, - user: record.user - } -} - -const runRawSseProbe = async (input: { - cfg: LlmRuntimeConfig - schemaName: string - system: string - user: string -}): Promise => { - const stats = emptyStats() - const schemaForRequest = resolveProbeSchema(input.schemaName) - const response = await fetch(buildCompletionsUrl(input.cfg.base_url), { - method: 'POST', - headers: { - Authorization: `Bearer ${input.cfg.api_key}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: input.cfg.model_name, - temperature: 0, - stream: true, - messages: [ - { - role: 'system', - content: input.system - }, - { - role: 'user', - content: input.user - } - ], - response_format: { - type: 'json_schema', - json_schema: { - name: input.schemaName, - schema: z.toJSONSchema(schemaForRequest), - strict: true - } - } - }) - }) - - if (!response.ok || !response.body) { - throw new Error(`raw_sse_probe_failed:${response.status}:${await response.text()}`) - } - - const decoder = new TextDecoder() - let buffer = '' - let blankStreak = 0 - - for await (const chunk of response.body as unknown as AsyncIterable) { - buffer += decoder.decode(chunk, { stream: true }) - const lines = buffer.split(/\r?\n/) - buffer = lines.pop() || '' - - for (const line of lines) { - if (!line.startsWith('data:')) continue - const data = line.slice('data:'.length).trim() - if (!data || data === '[DONE]') continue - - const parsed = JSON.parse(data) as { - choices?: Array<{ - delta?: { - content?: string - reasoning_content?: string - } - finish_reason?: string | null - }> - } - const choice = parsed.choices?.[0] - const finishReason = choice?.finish_reason - if (finishReason) { - stats.finishReasons.push(finishReason) - } - const text = choice?.delta?.content - if (typeof text === 'string') { - blankStreak = addTextToStats(stats, text, blankStreak) - if (blankStreak > 512) { - stats.stoppedAfterBlankStall = true - return stats - } - } - const reasoningText = choice?.delta?.reasoning_content - if (typeof reasoningText === 'string') { - addReasoningToStats(stats, reasoningText) - } - } - } - - return stats -} - -const runLangChainStreamProbe = async (input: { - cfg: LlmRuntimeConfig - schemaName: string - system: string - user: string -}): Promise => { - const stats = emptyStats() - const schemaForRequest = resolveProbeSchema(input.schemaName) - const runnable = createChatModel(input.cfg, { - temperature: 0, - transport: 'completions', - streaming: true - }).withConfig({ - outputVersion: 'v0', - response_format: { - type: 'json_schema', - json_schema: { - name: input.schemaName, - schema: z.toJSONSchema(schemaForRequest), - strict: true - } - } - }) as { - stream: (input: Array) => Promise> - } - - const stream = await runnable.stream([ - new SystemMessage(input.system), - new HumanMessage(input.user) - ]) - let blankStreak = 0 - - for await (const chunk of stream) { - if (chunk && typeof chunk === 'object') { - addUnique(stats.observedChunkKeys, Object.keys(chunk)) - } - const text = extractTextContent( - chunk && typeof chunk === 'object' && 'content' in chunk - ? (chunk as { content?: unknown }).content - : '' - ) - if (!text) continue - blankStreak = addTextToStats(stats, text, blankStreak) - if (blankStreak > 512) { - stats.stoppedAfterBlankStall = true - return stats - } - - const responseMetadata = - chunk && typeof chunk === 'object' && 'response_metadata' in chunk - ? (chunk as { response_metadata?: { finish_reason?: string } }).response_metadata - : undefined - if (responseMetadata?.finish_reason) { - stats.finishReasons.push(responseMetadata.finish_reason) - } - - const additionalKwargs = - chunk && typeof chunk === 'object' && 'additional_kwargs' in chunk - ? (chunk as { additional_kwargs?: Record }).additional_kwargs - : undefined - if (additionalKwargs) { - addUnique(stats.observedAdditionalKwargKeys, Object.keys(additionalKwargs)) - const reasoningText = additionalKwargs.reasoning_content - if (typeof reasoningText === 'string') { - addReasoningToStats(stats, reasoningText) - } - } - } - - return stats -} - -describe('ai unit raw SSE stream live probe', () => { - it( - 'compares raw provider SSE with LangChain stream chunks', - async () => { - if (!hasLiveLlmConfigInEnv()) { - expect(true).toBe(true) - return - } - - const cfg = loadLiveLlmConfigFromEnv() as LlmRuntimeConfig - const rawSse = await runRawSseProbe({ - cfg, - schemaName: 'live_probe_answer', - system: systemPrompt, - user: userPrompt - }) - const langChain = await runLangChainStreamProbe({ - cfg, - schemaName: 'live_probe_answer', - system: systemPrompt, - user: userPrompt - }) - - process.stdout.write( - `${JSON.stringify( - { - baseUrl: cfg.base_url, - model: cfg.model_name, - rawSse, - langChain - }, - null, - 2 - )}\n` - ) - - expect(rawSse.chunkCount).toBeGreaterThan(0) - expect(langChain.chunkCount).toBeGreaterThan(0) - }, - 180_000 - ) - - it( - 'replays an llm-io prompt when AI_LIVE_STREAM_PROBE_LLM_IO_PATH is provided', - async () => { - if (!hasLiveLlmConfigInEnv()) { - expect(true).toBe(true) - return - } - - const replayPrompt = await loadReplayPromptFromEnv() - if (!replayPrompt) { - expect(true).toBe(true) - return - } - - const cfg = loadLiveLlmConfigFromEnv() as LlmRuntimeConfig - const rawSse = await runRawSseProbe({ - cfg, - schemaName: replayPrompt.schemaName, - system: replayPrompt.system, - user: replayPrompt.user - }) - const langChain = await runLangChainStreamProbe({ - cfg, - schemaName: replayPrompt.schemaName, - system: replayPrompt.system, - user: replayPrompt.user - }) - - process.stdout.write( - `${JSON.stringify( - { - baseUrl: cfg.base_url, - model: cfg.model_name, - replay: { - callId: replayPrompt.callId, - schemaName: replayPrompt.schemaName, - systemLength: replayPrompt.system.length, - userLength: replayPrompt.user.length - }, - rawSse, - langChain - }, - null, - 2 - )}\n` - ) - - expect(rawSse.chunkCount).toBeGreaterThan(0) - expect(langChain.chunkCount).toBeGreaterThan(0) - }, - 240_000 - ) -}) diff --git a/tests/unit/ai/llm-text-live-probe.test.ts b/tests/unit/ai/llm-text-live-probe.test.ts deleted file mode 100644 index b3ab0970..00000000 --- a/tests/unit/ai/llm-text-live-probe.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { createAiLlmExecutor } from '../../../src/main/services/ai/llmExecutor' -import type { AiRunEvent, LlmRuntimeConfig } from '../../../src/shared/types' -import { - hasLiveLlmConfigInEnv, - loadLiveLlmConfigFromEnv -} from '../../harness/backend/llmConfig' - -describe('ai unit text output live probe', () => { - it( - 'probes production text executor in both non-stream and stream modes', - async () => { - if (!hasLiveLlmConfigInEnv()) { - expect(true).toBe(true) - return - } - - const cfg = loadLiveLlmConfigFromEnv() as LlmRuntimeConfig - const probeModes = ['force_non_stream', 'force_stream'] as const - const results: Array> = [] - - for (const streamingMode of probeModes) { - const events: AiRunEvent[] = [] - const executor = createAiLlmExecutor({ - eventCtx: { - runId: `live-text-probe-${streamingMode}`, - sendEvent: (event) => events.push(event) - }, - runtimeSettings: { - llmStreamingMode: streamingMode - }, - getConfig: async () => cfg, - saveConfig: async () => undefined, - checkCancelled: () => undefined - }) - - try { - const output = await executor.chatText({ - caller: `live.text.${streamingMode}`, - schemaName: 'live_text_answer', - system: 'You are a transport compatibility probe. Reply with exactly "ok".', - user: 'Reply with exactly ok.', - temperature: 0 - }) - - results.push({ - streamingMode, - ok: true, - output, - llmStreamDeltaCount: events.filter((event) => event.type === 'llm_stream_delta').length - }) - } catch (error) { - results.push({ - streamingMode, - ok: false, - error: error instanceof Error ? error.message : String(error) - }) - } - } - - process.stdout.write( - `${JSON.stringify({ baseUrl: cfg.base_url, model: cfg.model_name, results }, null, 2)}\n` - ) - - expect(results).toHaveLength(2) - }, - 180_000 - ) -}) diff --git a/tests/unit/ai/structured-output-live-probe.test.ts b/tests/unit/ai/structured-output-live-probe.test.ts deleted file mode 100644 index a018ab4e..00000000 --- a/tests/unit/ai/structured-output-live-probe.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { z } from 'zod/v4' -import { invokeStructuredChat } from '../../../src/main/services/ai/llmExecutor' -import type { AiLlmStreamingMode, PreferredStructuredOutputProfile } from '../../../src/shared/types' -import { - hasLiveLlmConfigInEnv, - loadLiveLlmConfigFromEnv -} from '../../harness/backend/llmConfig' - -const schema = z.object({ - answer: z.string() -}) - -describe('ai unit structured output live probe', () => { - it( - 'probes production structured output wrapper against the current primary live model', - async () => { - if (!hasLiveLlmConfigInEnv()) { - expect(true).toBe(true) - return - } - - const cfg = loadLiveLlmConfigFromEnv() - const probeCases: Array<{ - label: string - preferredProfile?: PreferredStructuredOutputProfile - streamingMode?: AiLlmStreamingMode - }> = [ - { label: 'auto' }, - { label: 'auto-stream', streamingMode: 'force_stream' }, - { - label: 'completions:jsonSchema', - preferredProfile: 'completions:jsonSchema' - }, - { - label: 'completions:jsonMode', - preferredProfile: 'completions:jsonMode' - }, - { - label: 'completions:promptJson', - preferredProfile: 'completions:promptJson' - }, - { - label: 'responses:jsonSchema', - preferredProfile: 'responses:jsonSchema' - }, - { - label: 'responses:promptJson', - preferredProfile: 'responses:promptJson' - } - ] - - const results: Array> = [] - - for (const probeCase of probeCases) { - try { - const result = await invokeStructuredChat({ - caller: `live.probe.${probeCase.label}`, - cfg, - preferredProfile: probeCase.preferredProfile, - streamingMode: probeCase.streamingMode, - schema, - schemaName: 'live_probe_answer', - system: - 'You are a structured output compatibility probe. Return only valid JSON matching the schema.', - user: 'Return a JSON object where answer is exactly "ok".', - temperature: 0 - }) - - results.push({ - case: probeCase.label, - ok: true, - transport: result.transport, - method: result.method, - profile: result.profile, - fallbackChain: result.fallbackChain, - streamed: result.streamed, - output: result.output - }) - } catch (error) { - results.push({ - case: probeCase.label, - ok: false, - streamingMode: probeCase.streamingMode || 'force_non_stream', - error: error instanceof Error ? error.message : String(error) - }) - } - } - - process.stdout.write( - `${JSON.stringify({ baseUrl: cfg.base_url, model: cfg.model_name, results }, null, 2)}\n` - ) - - expect(results).toHaveLength(7) - }, - 180_000 - ) -}) diff --git a/vitest.backend.config.mts b/vitest.backend.config.mts index 0c1ede42..538c7889 100644 --- a/vitest.backend.config.mts +++ b/vitest.backend.config.mts @@ -2,13 +2,8 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - include: ['tests/**/*.test.ts'], - exclude: [ - 'tests/**/*.e2e.test.ts', - 'tests/harness/**', - 'tests/fixtures/**', - 'tests/logs/**' - ], + include: ['tests/case/**/*.test.ts'], + exclude: ['tests/**/*.e2e.test.ts', 'tests/harness/**', 'tests/fixtures/**', 'tests/logs/**'], environment: 'node', globals: true, testTimeout: 900_000, diff --git a/vitest.backend.live.config.mts b/vitest.backend.live.config.mts index 86f9c44f..dc4a4cf0 100644 --- a/vitest.backend.live.config.mts +++ b/vitest.backend.live.config.mts @@ -2,13 +2,8 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - include: ['tests/**/*.test.ts'], - exclude: [ - 'tests/**/*.e2e.test.ts', - 'tests/harness/**', - 'tests/fixtures/**', - 'tests/logs/**' - ], + include: ['tests/case/**/*.test.ts'], + exclude: ['tests/**/*.e2e.test.ts', 'tests/harness/**', 'tests/fixtures/**', 'tests/logs/**'], environment: 'node', globals: true, fileParallelism: false, diff --git a/vitest.e2e.config.mts b/vitest.e2e.config.mts index 2ba5c847..ccd8131c 100644 --- a/vitest.e2e.config.mts +++ b/vitest.e2e.config.mts @@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - include: ['tests/**/*.e2e.test.ts', 'tests/**/*.e2e.test.tsx'], + include: ['tests/case/**/*.e2e.test.ts', 'tests/case/**/*.e2e.test.tsx'], exclude: ['tests/harness/**', 'tests/fixtures/**', 'tests/logs/**'], environment: 'node', globals: true, diff --git a/vitest.renderer.config.mts b/vitest.renderer.config.mts deleted file mode 100644 index 7702aaaf..00000000 --- a/vitest.renderer.config.mts +++ /dev/null @@ -1,20 +0,0 @@ -import { resolve } from 'node:path' -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - '@renderer': resolve('src/renderer/src') - } - }, - test: { - include: ['tests/renderer/**/*.test.ts', 'tests/renderer/**/*.test.tsx'], - exclude: ['tests/harness/**', 'tests/fixtures/**', 'tests/logs/**'], - environment: 'jsdom', - globals: true, - setupFiles: ['tests/harness/renderer/setup.ts'], - passWithNoTests: false - } -})