Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Features.Chat;
4using CascadeIDE.Features.Chat.Application;
5using Xunit;
6
7namespace CascadeIDE.Tests;
8
9public sealed class IntercomOutboundSendOrchestratorTests
10{
11 [Fact]
12 public async Task RunAsync_empty_input_is_noop()
13 {
14 var host = new RecordingHost { TrimmedInput = " " };
15 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
16 Assert.False(host.BuildAttempted);
17 }
18
19 [Fact]
20 public async Task RunAsync_slash_handled_skips_build()
21 {
22 var host = new RecordingHost
23 {
24 TrimmedInput = "/help",
25 SlashHandled = true,
26 };
27 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
28 Assert.False(host.BuildAttempted);
29 }
30
31 [Fact]
32 public async Task RunAsync_build_fail_sets_clarification()
33 {
34 var host = new RecordingHost
35 {
36 TrimmedInput = "hello",
37 BuildResult = (false, new IntercomAttachmentMessageBuilder.Outbound("", [], null), "bad attach"),
38 };
39 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
40 Assert.Equal("bad attach", host.LastClarification);
41 Assert.Equal(0, host.CommitCount);
42 }
43
44 [Fact]
45 public async Task RunAsync_mcp_only_commits_without_provider()
46 {
47 var host = new RecordingHost
48 {
49 TrimmedInput = "hi",
50 BuildResult = (true, new IntercomAttachmentMessageBuilder.Outbound("hi", [], null), ""),
51 McpOnly = true,
52 };
53 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
54 Assert.Equal(1, host.CommitCount);
55 Assert.False(host.LastCommitStartProviderLoading);
56 Assert.Equal(0, host.ProviderDispatchCount);
57 Assert.Equal(0, host.EndProviderTurnCount);
58 }
59
60 [Fact]
61 public async Task RunAsync_happy_path_dispatches_streaming()
62 {
63 var host = new RecordingHost
64 {
65 TrimmedInput = "hi",
66 BuildResult = (true, new IntercomAttachmentMessageBuilder.Outbound("hi", [], null), ""),
67 ActiveProvider = "Ollama",
68 };
69 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
70 Assert.Equal(1, host.CommitCount);
71 Assert.True(host.LastCommitStartProviderLoading);
72 Assert.Equal(1, host.ProviderDispatchCount);
73 Assert.Equal(1, host.EndProviderTurnCount);
74 Assert.Equal("hi|agent", host.LastAgentInput);
75 }
76
77 [Fact]
78 public async Task RunAsync_follow_up_defers_provider_and_enqueues()
79 {
80 var host = new RecordingHost
81 {
82 TrimmedInput = "wait",
83 BuildResult = (true, new IntercomAttachmentMessageBuilder.Outbound("wait", [], null), ""),
84 DeferProvider = true,
85 };
86 await IntercomOutboundSendOrchestrator.RunAsync(host.ToHost());
87 Assert.Equal(1, host.CommitCount);
88 Assert.False(host.LastCommitStartProviderLoading);
89 Assert.Equal(0, host.ProviderDispatchCount);
90 Assert.Equal(1, host.EnqueueCount);
91 Assert.Equal("wait|agent", host.LastAgentInput);
92 }
93
94 private sealed class RecordingHost
95 {
96 public string TrimmedInput { get; init; } = "";
97 public string? WorkspaceRoot { get; init; } = "C:\\ws";
98 public int PendingAttachCount { get; init; }
99 public bool SlashHandled { get; init; }
100 public (bool Ok, IntercomAttachmentMessageBuilder.Outbound Outbound, string Error) BuildResult { get; init; }
101 public bool McpOnly { get; init; }
102 public string ActiveProvider { get; init; } = "CursorACP";
103
104 public string DeliveryMode { get; init; } = "normal";
105 public bool DeferProvider { get; init; }
106
107 public bool BuildAttempted { get; private set; }
108 public int EnqueueCount { get; private set; }
109 public string? LastDeliveryMode { get; private set; }
110 public string? LastClarification { get; private set; }
111 public int CommitCount { get; private set; }
112 public bool LastCommitStartProviderLoading { get; private set; }
113 public int ProviderDispatchCount { get; private set; }
114 public int EndProviderTurnCount { get; private set; }
115 public string? LastAgentInput { get; private set; }
116 public string? LastDisplayInput { get; private set; }
117
118 public IntercomOutboundSendHost ToHost() =>
119 new()
120 {
121 GetTrimmedInput = () => TrimmedInput.Trim(),
122 GetWorkspaceRoot = () => WorkspaceRoot,
123 GetPendingAttachCount = () => PendingAttachCount,
124 TryHandleSlashLineAsync = _ => Task.FromResult(SlashHandled),
125 TryBuildOutboundAsync = (_, _) =>
126 {
127 BuildAttempted = true;
128 return Task.FromResult(BuildResult);
129 },
130 BeginPrepareOutboundAsync = () => Task.CompletedTask,
131 EndPrepareOutboundAsync = () => Task.CompletedTask,
132 ApplyProductSpine = s => s,
133 FormatAgentInput = (display, _) => display + "|agent",
134 CommitUserMessageAsync = (_, _, startLoading, deliveryMode) =>
135 {
136 CommitCount++;
137 LastCommitStartProviderLoading = startLoading;
138 LastDeliveryMode = deliveryMode;
139 return Task.CompletedTask;
140 },
141 ConsumeDeliveryMode = () => DeliveryMode,
142 ShouldDeferProviderDispatch = _ => DeferProvider,
143 CancelActiveTurnIfSteer = _ => { },
144 EnqueueFollowUpAgentInputAsync = input =>
145 {
146 EnqueueCount++;
147 LastAgentInput = input;
148 return Task.CompletedTask;
149 },
150 ProcessFollowUpQueueAsync = () => Task.CompletedTask,
151 GetChatMcpOnly = () => McpOnly,
152 GetActiveAiProvider = () => ActiveProvider,
153 SendCursorAcpAsync = input =>
154 {
155 LastAgentInput = input;
156 ProviderDispatchCount++;
157 return Task.CompletedTask;
158 },
159 SendStreamingAsync = (agent, display) =>
160 {
161 LastAgentInput = agent;
162 LastDisplayInput = display;
163 ProviderDispatchCount++;
164 return Task.CompletedTask;
165 },
166 SetClarificationStatusAsync = text =>
167 {
168 LastClarification = text;
169 return Task.CompletedTask;
170 },
171 EndProviderTurnAsync = () =>
172 {
173 EndProviderTurnCount++;
174 return Task.CompletedTask;
175 },
176 };
177 }
178}
179
View only · write via MCP/CIDE