From 876909e5aa193f19dc33b0c183886210a710bbd7 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Fri, 23 Oct 2026 03:11:41 +0800 Subject: [PATCH] =?UTF-8?q?test:=20TASK065=20=E7=AC=AC=E5=85=AD=E6=B3=A2?= =?UTF-8?q?=20E2E=20=E8=A1=A5=E8=A6=86=E7=9B=96=E4=B8=89=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=89=B9=E6=AC=A1=EF=BC=8812=20=E6=B5=8B=E8=AF=95=EF=BC=8CE2E?= =?UTF-8?q?=20=E5=91=BD=E4=BB=A4=E8=A6=86=E7=9B=96=2089=E2=86=9298=20?= =?UTF-8?q?=E6=B8=85=E9=9B=B6=EF=BC=89=20|=20=E5=86=B3=E7=AD=96:=20?= =?UTF-8?q?=E4=B8=89=E4=BB=A3=E7=90=86=E5=B9=B6=E8=A1=8C=E6=8C=89=20P0/P1/?= =?UTF-8?q?P2=20=E5=88=86=E7=BA=A7=E9=9B=B6=E6=96=87=E4=BB=B6=E9=87=8D?= =?UTF-8?q?=E5=8F=A0=EF=BC=88Phase6/7/8=20=E8=84=9A=E6=9C=AC+=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=E7=8B=AC=E7=AB=8B=E6=B5=8B=E8=AF=95=E7=B1=BB=EF=BC=89?= =?UTF-8?q?=E2=80=94=E2=80=94A:=20P0=20bug=20=E5=9B=9E=E5=BD=92=E9=94=81?= =?UTF-8?q?=E5=AE=9A=205=20=E6=B5=8B=E8=AF=95=EF=BC=88vendor=20=E5=88=97?= =?UTF-8?q?=E8=A1=A8/=E5=88=87=E6=8D=A2+mcp=20add+model=20=E7=9B=B4?= =?UTF-8?q?=E5=88=87+effort=20=E8=AE=BE=E7=BD=AE=EF=BC=8C=E7=AC=AC?= =?UTF-8?q?=E4=B8=89/=E4=BA=94=E6=B3=A2=E4=BF=AE=E5=A4=8D=E7=9A=84=20E2E?= =?UTF-8?q?=20=E4=B8=8D=E5=A4=8D=E5=8F=91=E9=94=81=E5=AE=9A=EF=BC=8C?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E5=85=A8=E9=83=A8=E6=BA=90=E7=A0=81=E8=A1=8C?= =?UTF-8?q?=E7=BA=A7=E5=AF=B9=E9=BD=90=EF=BC=89=EF=BC=9BB:=20P1=20?= =?UTF-8?q?=E9=AB=98=E9=A2=91=205=20=E6=B5=8B=E8=AF=95=EF=BC=88debuglog/?= =?UTF-8?q?=3F=3F=E9=9C=80=E6=B1=82=E6=BE=84=E6=B8=85=E5=90=AB=20ClarifyDo?= =?UTF-8?q?neMarker=20=E9=98=B2=20ReadLine=20=E5=8D=A1=E6=AD=BB/sampling/s?= =?UTF-8?q?witch/diag=EF=BC=89=EF=BC=9BC:=20P2=20=E9=9A=94=E7=A6=BB?= =?UTF-8?q?=E5=86=99=E5=91=BD=E4=BB=A4=202=20=E6=B5=8B=E8=AF=95=EF=BC=88re?= =?UTF-8?q?set-config=20=E5=8F=8C=E9=87=8D=E9=9A=94=E7=A6=BB=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=AE=9E=E6=B5=8B=E7=9C=9F=E5=AE=9E=20~/.jcc=20?= =?UTF-8?q?=E9=9B=B6=E8=A7=A6=E7=A2=B0+code-index=20fixture=20=E6=8E=A7?= =?UTF-8?q?=E8=80=97=E6=97=B6=EF=BC=89=EF=BC=9B=E6=9C=AC=E5=9C=B0=2012/12?= =?UTF-8?q?=20=E5=85=A8=E7=BB=BF=2055s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatCommandP0RegressionCoverageTests.cs | 44 ++++++ .../e2e/a_to_c/ChatCommandP1CoverageTests.cs | 39 +++++ .../ChatCommandP2IsolatedCoverageTests.cs | 84 +++++++++++ .../ChatCommandConversationScripts.Phase6.cs | 139 ++++++++++++++++++ .../ChatCommandConversationScripts.Phase7.cs | 134 +++++++++++++++++ .../ChatCommandConversationScripts.Phase8.cs | 77 ++++++++++ 6 files changed, 517 insertions(+) create mode 100644 test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP0RegressionCoverageTests.cs create mode 100644 test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP1CoverageTests.cs create mode 100644 test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP2IsolatedCoverageTests.cs create mode 100644 test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase6.cs create mode 100644 test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase7.cs create mode 100644 test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase8.cs diff --git a/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP0RegressionCoverageTests.cs b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP0RegressionCoverageTests.cs new file mode 100644 index 0000000000..b66cb98e4e --- /dev/null +++ b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP0RegressionCoverageTests.cs @@ -0,0 +1,44 @@ +namespace MockServer.E2E.Tests; + +/// +/// P0 bug 回归锁定 E2E 覆盖测试 — TASK065 第六波 +/// 锁定第三/五波修复的生产 bug 不复发: /vendor catalog 崩溃降级、/mcp add 子命令识别、/model 别名透传直切、/effort 设置 +/// 真实 jcc 进程 + MockServer 模拟 LLM,NonInteractive 单轮形态 +/// +public sealed class ChatCommandP0RegressionCoverageTests : CoverageTestBase { + public ChatCommandP0RegressionCoverageTests(ITestOutputHelper output) : base(output) { } + + // ============================================================ + // Phase 6: P0 bug 回归锁定 — 2026-10-23 新增 + // ============================================================ + + /// /vendor 无参 — VendorCommand catalog 崩溃修复回归(无参列出供应商) + [Fact] + public async Task VendorCommand_ShouldListVendors() { + await RunScriptAsync(ChatCommandConversationScripts.VendorCommand).ConfigureAwait(true); + } + + /// /vendor deepseek — VendorCommand 切换降级修复回归(catalog 可用时正常切换) + [Fact] + public async Task VendorSwitchDeepSeekCommand_ShouldSwitchVendor() { + await RunScriptAsync(ChatCommandConversationScripts.VendorSwitchDeepSeekCommand).ConfigureAwait(true); + } + + /// /mcp add testsvr — MCP add 子命令识别修复回归(输出用法提示而非未知操作) + [Fact] + public async Task McpAddUsageCommand_ShouldShowAddUsage() { + await RunScriptAsync(ChatCommandConversationScripts.McpAddUsageCommand).ConfigureAwait(true); + } + + /// /model gpt-4o — ModelCommand catalog 崩溃修复回归(别名透传直切) + [Fact] + public async Task ModelSwitchCommand_ShouldSwitchModelDirectly() { + await RunScriptAsync(ChatCommandConversationScripts.ModelSwitchCommand).ConfigureAwait(true); + } + + /// /effort high — EffortCommand 设置形态回归(输出力度设置成功) + [Fact] + public async Task EffortSetHighCommand_ShouldSetEffortLevel() { + await RunScriptAsync(ChatCommandConversationScripts.EffortSetHighCommand).ConfigureAwait(true); + } +} diff --git a/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP1CoverageTests.cs b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP1CoverageTests.cs new file mode 100644 index 0000000000..9b930a78b4 --- /dev/null +++ b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP1CoverageTests.cs @@ -0,0 +1,39 @@ +namespace MockServer.E2E.Tests; + +/// +/// P1 高频命令 E2E 覆盖测试 — TASK065 第六波补覆盖 +/// 包含: /debuglog -e(错误段查询)、/??(需求澄清)、/sampling(无参查询)、/switch(无参显示模式)、/diag recent(崩溃快照查询) +/// +public sealed class ChatCommandP1CoverageTests : CoverageTestBase { + public ChatCommandP1CoverageTests(ITestOutputHelper output) : base(output) { } + + /// /debuglog -e — 断言错误段标题输出且无错误 + [Fact] + public async Task DebugLogErrorCommand_ShouldShowErrorSection() { + await RunScriptAsync(ChatCommandConversationScripts.DebugLogErrorCommand).ConfigureAwait(true); + } + + /// /?? 需求描述 — mock 响应含 ClarifyDoneMarker,断言澄清模式横幅输出且无错误 + [Fact] + public async Task AskClarifyCommand_ShouldCompleteClarifyLoop() { + await RunScriptAsync(ChatCommandConversationScripts.AskClarifyCommand).ConfigureAwait(true); + } + + /// /sampling 无参 — 断言采样参数查询输出且无错误 + [Fact] + public async Task SamplingCommand_ShouldShowSamplingParams() { + await RunScriptAsync(ChatCommandConversationScripts.SamplingCommand).ConfigureAwait(true); + } + + /// /switch 无参 — 断言当前模式输出且无错误 + [Fact] + public async Task SwitchCommand_ShouldShowCurrentMode() { + await RunScriptAsync(ChatCommandConversationScripts.SwitchCommand).ConfigureAwait(true); + } + + /// /diag recent — 断言崩溃快照查询输出且无错误 + [Fact] + public async Task DiagRecentCommand_ShouldShowCrashSnapshotReport() { + await RunScriptAsync(ChatCommandConversationScripts.DiagRecentCommand).ConfigureAwait(true); + } +} diff --git a/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP2IsolatedCoverageTests.cs b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP2IsolatedCoverageTests.cs new file mode 100644 index 0000000000..d2c84f78b9 --- /dev/null +++ b/test/mock/mock_server.e2e.tests/e2e/a_to_c/ChatCommandP2IsolatedCoverageTests.cs @@ -0,0 +1,84 @@ +namespace MockServer.E2E.Tests; + +/// +/// P2 隔离写命令 E2E 覆盖测试 — TASK065 第六波补覆盖 +/// 包含: /reset-config all(JCC_APP_DATA_FOLDER 隔离目录重置)、/code-index(fixture 目录索引重建) +/// 隔离验证: /reset-config 写入目标为 runner 设置的 %TEMP%\jcc_test_{guid}(Path.Combine 对绝对路径直接采用),不碰真实 ~/.jcc +/// +public sealed class ChatCommandP2IsolatedCoverageTests : CoverageTestBase { + public ChatCommandP2IsolatedCoverageTests(ITestOutputHelper output) : base(output) { } + + /// /reset-config all — 断言隔离 AppData 配置重置输出(重置开始/配置树骨架/重置完成)且无错误 + [Fact] + public async Task ResetConfigAllCommand_ShouldResetIsolatedAppData() { + await RunScriptAsync(ChatCommandConversationScripts.ResetConfigAllCommand).ConfigureAwait(true); + } + + /// /code-index — 断言 fixture 目录索引构建完成(构建开始/完成提示/统计输出)且无错误 + [Fact] + public async Task CodeIndexCommand_ShouldBuildIndexInFixtureDir() { + var fixtureDir = await CreateCodeIndexFixtureAsync().ConfigureAwait(true); + ChatCommandConversationScripts.CodeIndexFixtureDirectory = fixtureDir; + try { + await RunScriptAsync(ChatCommandConversationScripts.CodeIndexCommand).ConfigureAwait(true); + } finally { + ChatCommandConversationScripts.CodeIndexFixtureDirectory = null; + CleanupCodeIndexFixture(fixtureDir); + } + } + + /// + /// 创建 /code-index fixture 目录 — %TEMP%\jcc_e2e_codeindex_{guid} + 2 个小 .cs 文件 + /// 小目录控制 RebuildIndexAsync 耗时(Ast 集成测试 WorkingDirectory 指向 fixture 的先例) + /// + /// fixture 目录绝对路径 + private static async Task CreateCodeIndexFixtureAsync() { + var fs = TestConfiguration.FileSystem; + var fixtureDir = fs.CombinePath(Path.GetTempPath(), $"jcc_e2e_codeindex_{Guid.NewGuid():N}"); + fs.CreateDirectory(fixtureDir); + await fs.WriteAllTextAsync(fs.CombinePath(fixtureDir, "Calculator.cs"), CalculatorFixtureSource).ConfigureAwait(true); + await fs.WriteAllTextAsync(fs.CombinePath(fixtureDir, "Greeter.cs"), GreeterFixtureSource).ConfigureAwait(true); + return fixtureDir; + } + + /// + /// 清理 fixture 目录(含索引产物 {fixture}/.jcc/code-index/)— %TEMP% 下测试自建产物 + /// 清理失败不阻断测试结果(%TEMP% 残留无害,同 DualRoleConversationRunner 清理临时目录先例) + /// + /// fixture 目录绝对路径 + private void CleanupCodeIndexFixture(string fixtureDir) { + try { + var fs = TestConfiguration.FileSystem; + if (fs.DirectoryExists(fixtureDir)) { + fs.DeleteDirectory(fixtureDir, recursive: true); + } + } catch (Exception ex) { + Output.WriteLine($"[P2] ⚠ fixture 清理失败(%TEMP% 残留无害): {ex.Message}"); + } + } + + /// fixture 源码 1 — 计算器(含类/方法符号,供索引器解析) + private const string CalculatorFixtureSource = """ + namespace CodeIndexFixture; + + /// fixture 计算器 — /code-index E2E 索引目标 + public static class Calculator { + /// 两数相加 + public static int Add(int a, int b) => a + b; + + /// 两数相乘 + public static int Multiply(int a, int b) => a * b; + } + """; + + /// fixture 源码 2 — 问候器(含类/方法符号,供索引器解析) + private const string GreeterFixtureSource = """ + namespace CodeIndexFixture; + + /// fixture 问候器 — /code-index E2E 索引目标 + public static class Greeter { + /// 生成问候语 + public static string Greet(string name) => $"Hello, {name}!"; + } + """; +} diff --git a/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase6.cs b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase6.cs new file mode 100644 index 0000000000..e8312e779b --- /dev/null +++ b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase6.cs @@ -0,0 +1,139 @@ +namespace MockServer.E2E.Tests.Scripts; + +/// +/// Phase 6 E2E 脚本 — TASK065 第六波 P0 bug 回归锁定 +/// 锁定第三/五波修复的 4 类 bug: /vendor catalog 崩溃降级、/mcp add 子命令识别、/model 别名透传直切、/effort max 降级 +/// 断言文案全部对齐命令源码真实输出(VendorCommand/McpCommand/ModelCommand/EffortCommand) +/// +public static partial class ChatCommandConversationScripts { + /// + /// /vendor 命令(无参) — 列出可用供应商 + /// 对齐 VendorCommand.cs 无参分支: "=== 可用供应商 ===" + 枚举值逐行输出(" openai ← 当前") + /// + public static ConversationScript VendorCommand => new() { + Name = "/vendor 命令(无参列表)", + Turns = + [ + new ConversationTurn + { + UserInput = "/vendor", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "可用供应商", Description = "应输出可用供应商列表标题" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "openai", Description = "列表应包含 openai 供应商(VendorKind 枚举遍历)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /vendor deepseek 命令 — 切换供应商 + /// 对齐 VendorCommand.cs 切换分支: "已切换供应商: openai → deepseek"(E2E 环境 JCC_VENDOR=openai,catalog 全装配不崩溃) + /// + public static ConversationScript VendorSwitchDeepSeekCommand => new() { + Name = "/vendor deepseek 命令(切换供应商)", + Turns = + [ + new ConversationTurn + { + UserInput = "/vendor deepseek", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "已切换供应商", Description = "应输出切换成功文案" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "deepseek", Description = "目标供应商 deepseek 应出现在切换输出中" }, + new OutputAssert { Type = AssertType.NotContainsText, Expected = "未知供应商", Description = "deepseek 在 VendorKind 枚举内,不应触发未知供应商拒绝" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "切换不应有错误(catalog 崩溃修复回归锁定)" }, + ] + } + ] + }; + + /// + /// /mcp add 命令(缺少 url/command 参数) — AddServerAsync 用法提示分支 + /// 对齐 McpCommand.cs AddServerAsync: args.Length<3 时输出 "用法: /mcp add ..." + /// 锁定 CrudAction.Add 识别修复 — 修复前 add 走 default 分支输出 "未知操作: add" + /// + public static ConversationScript McpAddUsageCommand => new() { + Name = "/mcp add 命令(用法提示)", + Turns = + [ + new ConversationTurn + { + UserInput = "/mcp add testsvr", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "用法: /mcp add", Description = "应输出 add 子命令用法提示(AddServerAsync 分支)" }, + new OutputAssert { Type = AssertType.NotContainsText, Expected = "未知操作", Description = "add 应被 CrudAction.Add case 识别,不应走 default 未知操作分支(bug 回归锁定)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /model 命令(带参直切) — 别名透传直切 + /// 对齐 ModelCommand.cs SwitchToModelAsync: "已切换模型: gpt-4o"(E2E settings.json openai 无别名表,ResolveAlias 返回 null 透传输入) + /// + public static ConversationScript ModelSwitchCommand => new() { + Name = "/model gpt-4o 命令(带参直切)", + Turns = + [ + new ConversationTurn + { + UserInput = "/model gpt-4o", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "已切换模型: gpt-4o", Description = "应输出直切成功文案(catalog 可用,别名透传)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "切换不应有错误(catalog 崩溃修复回归锁定)" }, + ] + } + ] + }; + + /// + /// /effort 命令(设置形态) — 设置推理力度 high + /// 对齐 EffortCommand.cs 设置分支: "推理力度: high (全面,广泛测试)" + /// + public static ConversationScript EffortSetHighCommand => new() { + Name = "/effort high 命令(设置推理力度)", + Turns = + [ + new ConversationTurn + { + UserInput = "/effort high", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "推理力度: high", Description = "应输出 high 力度设置成功文案" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "全面,广泛测试", Description = "应输出 high 等级对应描述(GetEffortDescription)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; +} diff --git a/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase7.cs b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase7.cs new file mode 100644 index 0000000000..4baa07c9d7 --- /dev/null +++ b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase7.cs @@ -0,0 +1,134 @@ +namespace MockServer.E2E.Tests.Scripts; + +/// +/// Phase 7 E2E 脚本 — P1 高频命令补覆盖(TASK065 第六波) +/// 包含: /debuglog -e、/??(需求澄清)、/sampling(无参查询)、/switch(无参显示模式)、/diag recent +/// +public static partial class ChatCommandConversationScripts { + /// + /// /debuglog -e 命令 — 仅显示错误段 + /// DebugLogRenderer.AppendErrors 固定输出 "═══ 错误 ═══" 段落标题;无错误时追加 "无错误记录" + /// + public static ConversationScript DebugLogErrorCommand => new() { + Name = "/debuglog -e 命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/debuglog -e", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "错误", Description = "应包含错误段标题(═══ 错误 ═══)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /?? 命令 — 需求澄清模式(AskClarify) + /// 关键: mock 响应文本必须含 ClarifyDoneMarker("【需求已明确】"),否则澄清循环进 ReadLine 卡 60s 超时 + /// 命令固定输出 "╔══ 需求澄清模式 ══╗" 横幅;响应含标记时输出 "✓ 需求澄清完成" 后退出子循环 + /// + public static ConversationScript AskClarifyCommand => new() { + Name = "/?? 需求澄清命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/?? 帮我写一个待办事项应用", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "【需求已明确】需求总结:开发一个本地待办事项应用,支持增删改查与完成状态标记。" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "需求澄清", Description = "应包含需求澄清模式横幅" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /sampling 命令 — 无参查询当前采样参数 + /// SamplingCommand 无参时输出 "采样参数: 温度 {x}, 最大 {y} tokens" + /// + public static ConversationScript SamplingCommand => new() { + Name = "/sampling 命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/sampling", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "采样参数", Description = "应包含采样参数查询输出" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /switch 命令 — 无参显示当前模式 + /// SwitchCommand 无参时 ShowCurrentMode 输出 "当前模式: 显示全部子代理输出"(默认)及活跃子代理列表 + /// + public static ConversationScript SwitchCommand => new() { + Name = "/switch 命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/switch", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "当前模式", Description = "应包含当前模式输出" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /diag recent 命令 — 崩溃快照查询 + /// E2E 隔离目录快照为空时 FormatReport 返回 "无崩溃快照记录。";非空时输出 "崩溃快照报告(最近 …)" + /// 两种输出均含 "崩溃快照",据此断言查询正常执行 + /// + public static ConversationScript DiagRecentCommand => new() { + Name = "/diag recent 命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/diag recent", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "崩溃快照", Description = "应包含崩溃快照查询输出" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; +} diff --git a/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase8.cs b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase8.cs new file mode 100644 index 0000000000..21572df603 --- /dev/null +++ b/test/mock/mock_server.e2e.tests/scripts/ChatCommandConversationScripts.Phase8.cs @@ -0,0 +1,77 @@ +namespace MockServer.E2E.Tests.Scripts; + +/// +/// Phase 8 E2E 脚本 — P2 隔离写命令补覆盖(TASK065 第六波) +/// 包含: /reset-config all(JCC_APP_DATA_FOLDER 隔离目录重置)、/code-index(小 fixture 目录控索引耗时) +/// +public static partial class ChatCommandConversationScripts { + /// + /// /code-index fixture 目录 — 由测试类创建/清理,脚本属性消费 + /// 指向 %TEMP% 下含 2 个小 .cs 文件的临时目录,控制 RebuildIndexAsync 耗时 + /// + internal static string? CodeIndexFixtureDirectory { get; set; } + + /// + /// /reset-config all 命令 — 重置隔离 AppData 配置(单轮) + /// 隔离链路: DualRoleConversationRunner 设 JCC_APP_DATA_FOLDER=%TEMP%\jcc_test_{guid}, + /// AppDataPaths.Default 解析为绝对路径后 ResetConfigCommand 的 Path.Combine(UserProfile, 绝对路径) 直接采用绝对路径 → 写入隔离目录,不碰真实 ~/.jcc + /// 断言 ResetAllAsync 固定输出: "重置所有配置文件..."(首行)、"配置树已重置为默认骨架"(settings 重置)、"✓ 所有配置已重置"(收尾,颜色码不影响子串匹配) + /// + public static ConversationScript ResetConfigAllCommand => new() { + Name = "/reset-config all 命令", + Turns = + [ + new ConversationTurn + { + UserInput = "/reset-config all", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "重置所有配置文件", Description = "应包含重置开始提示(ResetAllAsync 首行)" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "配置树已重置为默认骨架", Description = "应包含配置树重置输出(settings.json + current/ + providers_llm/ + providers_search/)" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "所有配置已重置", Description = "应包含重置完成提示(✓ 所有配置已重置)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; + + /// + /// /code-index 命令 — 显式重建 fixture 目录代码索引(单轮) + /// WorkingDirectory 指向小 fixture(2 个 .cs 文件)控制索引耗时(Ast 集成测试 WorkingDirectory 先例); + /// 断言 CodeIndexCommand 固定输出: "正在构建代码索引..."(无条件首行)、"代码索引构建完成"(成功路径)、"文件数"(GetStatsAsync 统计行) + /// + /// CodeIndexFixtureDirectory 未就绪时抛出 — 测试类须先设置 fixture 目录再访问本脚本 + public static ConversationScript CodeIndexCommand => new() { + Name = "/code-index 命令", + WorkingDirectory = CodeIndexFixtureDirectory + ?? throw new InvalidOperationException( + "[E2E-FIXTURE] /code-index 脚本的 CodeIndexFixtureDirectory 未就绪。" + + "触发原因: 测试类未先创建 fixture 目录。" + + "拦截位置: ChatCommandConversationScripts.CodeIndexCommand 属性 getter。" + + "正确写法: 先在 ChatCommandP2IsolatedCoverageTests 中调用 CreateCodeIndexFixtureAsync() 并赋值 CodeIndexFixtureDirectory,再访问本脚本。"), + Turns = + [ + new ConversationTurn + { + UserInput = "/code-index", + AiResponse = new MockResponseScript + { + Type = MockResponseType.TextOnly, + TextResponse = "" + }, + Asserts = + [ + new OutputAssert { Type = AssertType.ContainsText, Expected = "正在构建代码索引", Description = "应包含索引构建开始提示(无条件输出)" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "代码索引构建完成", Description = "应包含索引构建完成提示(含耗时 ms)" }, + new OutputAssert { Type = AssertType.ContainsText, Expected = "文件数", Description = "应包含索引统计输出(文件数/符号数/调用边/依赖边/项目数)" }, + new OutputAssert { Type = AssertType.NoErrors, Expected = "", Description = "不应有错误" }, + ] + } + ] + }; +}