Skip to content

Commit 016b8c3

Browse files
sharpninjaCopilot
andcommitted
Add 52 CQRS handler unit tests (P8-2)
Test coverage for all command handlers in AllCommands.cs, AsyncCommands.cs, and ChatCommands.cs. Tests mock ICommandTarget and service interfaces, verify correct method dispatch, and cover edge cases (null args, errors, cancellation). Total test count: 93 (52 new + 41 existing). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cece874 commit 016b8c3

7 files changed

Lines changed: 747 additions & 0 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using FluentAssertions;
2+
using McpServer.Cqrs;
3+
using McpServerManager.Core.Commands;
4+
using McpServerManager.Core.Models;
5+
using McpServerManager.Core.Models.Json;
6+
using Moq;
7+
using Xunit;
8+
9+
namespace McpServerManager.Core.Tests.Commands;
10+
11+
public sealed class ArchiveHandlerTests
12+
{
13+
private readonly Mock<ICommandTarget> _target = new();
14+
private readonly CallContext _ctx = new();
15+
16+
[Fact]
17+
public async Task ArchiveCurrentHandler_HandleAsync_CallsArchive()
18+
{
19+
var handler = new ArchiveCurrentHandler(_target.Object);
20+
var result = await handler.HandleAsync(new ArchiveCurrentCommand(), _ctx);
21+
22+
result.IsSuccess.Should().BeTrue();
23+
_target.Verify(t => t.Archive(), Times.Once);
24+
}
25+
26+
[Fact]
27+
public async Task ArchiveHandler_HandleAsync_CallsArchive()
28+
{
29+
var handler = new ArchiveHandler(_target.Object);
30+
var result = await handler.HandleAsync(new ArchiveCommand(), _ctx);
31+
32+
result.IsSuccess.Should().BeTrue();
33+
_target.Verify(t => t.Archive(), Times.Once);
34+
}
35+
36+
[Fact]
37+
public async Task ArchiveTreeItemHandler_HandleAsync_CallsArchiveTreeItem()
38+
{
39+
var node = new FileNode("session.json", false);
40+
var handler = new ArchiveTreeItemHandler(_target.Object);
41+
var result = await handler.HandleAsync(new ArchiveTreeItemCommand(node), _ctx);
42+
43+
result.IsSuccess.Should().BeTrue();
44+
_target.Verify(t => t.ArchiveTreeItem(node), Times.Once);
45+
}
46+
47+
[Fact]
48+
public async Task ArchiveTreeItemHandler_HandleAsync_NullNode()
49+
{
50+
var handler = new ArchiveTreeItemHandler(_target.Object);
51+
var result = await handler.HandleAsync(new ArchiveTreeItemCommand(null), _ctx);
52+
53+
result.IsSuccess.Should().BeTrue();
54+
_target.Verify(t => t.ArchiveTreeItem(null), Times.Once);
55+
}
56+
57+
[Fact]
58+
public async Task OpenTreeItemHandler_HandleAsync_CallsOpenTreeItem()
59+
{
60+
var node = new FileNode("data.json", false);
61+
var handler = new OpenTreeItemHandler(_target.Object);
62+
var result = await handler.HandleAsync(new OpenTreeItemCommand(node), _ctx);
63+
64+
result.IsSuccess.Should().BeTrue();
65+
_target.Verify(t => t.OpenTreeItem(node), Times.Once);
66+
}
67+
68+
[Fact]
69+
public async Task SelectSearchEntryHandler_HandleAsync_CallsSelectSearchEntry()
70+
{
71+
var entry = new SearchableEntry();
72+
var handler = new SelectSearchEntryHandler(_target.Object);
73+
var result = await handler.HandleAsync(new SelectSearchEntryCommand(entry), _ctx);
74+
75+
result.IsSuccess.Should().BeTrue();
76+
_target.Verify(t => t.SelectSearchEntry(entry), Times.Once);
77+
}
78+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
using FluentAssertions;
2+
using McpServer.Cqrs;
3+
using McpServerManager.Core.Commands;
4+
using McpServerManager.Core.Models;
5+
using McpServerManager.Core.Services;
6+
using Moq;
7+
using Xunit;
8+
9+
namespace McpServerManager.Core.Tests.Commands;
10+
11+
public sealed class ChatHandlerTests
12+
{
13+
private readonly CallContext _ctx = new();
14+
15+
// --- ChatOpenAgentConfig ---
16+
17+
[Fact]
18+
public async Task ChatOpenAgentConfigHandler_HandleAsync_CallsOpenAgentConfigInEditor()
19+
{
20+
var svc = new Mock<IChatConfigFilesService>();
21+
var expected = new ChatFileOpenResult(true, "/path/config.json");
22+
svc.Setup(s => s.OpenAgentConfigInEditor()).Returns(expected);
23+
24+
var handler = new ChatOpenAgentConfigHandler(svc.Object);
25+
var result = await handler.HandleAsync(new ChatOpenAgentConfigCommand(), _ctx);
26+
27+
result.IsSuccess.Should().BeTrue();
28+
result.Value.Should().Be(expected);
29+
svc.Verify(s => s.OpenAgentConfigInEditor(), Times.Once);
30+
}
31+
32+
// --- ChatOpenPromptTemplates ---
33+
34+
[Fact]
35+
public async Task ChatOpenPromptTemplatesHandler_HandleAsync_CallsOpenPromptTemplatesInEditor()
36+
{
37+
var svc = new Mock<IChatConfigFilesService>();
38+
var expected = new ChatFileOpenResult(true, "/path/prompts.yaml");
39+
svc.Setup(s => s.OpenPromptTemplatesInEditor()).Returns(expected);
40+
41+
var handler = new ChatOpenPromptTemplatesHandler(svc.Object);
42+
var result = await handler.HandleAsync(new ChatOpenPromptTemplatesCommand(), _ctx);
43+
44+
result.IsSuccess.Should().BeTrue();
45+
result.Value.Should().Be(expected);
46+
svc.Verify(s => s.OpenPromptTemplatesInEditor(), Times.Once);
47+
}
48+
49+
// --- ChatLoadPrompts ---
50+
51+
[Fact]
52+
public async Task ChatLoadPromptsHandler_HandleAsync_ReturnsPromptTemplates()
53+
{
54+
var svc = new Mock<IChatPromptTemplateService>();
55+
var templates = new List<PromptTemplate>
56+
{
57+
new() { Name = "Summarize", Template = "Summarize this" }
58+
};
59+
svc.Setup(s => s.GetPromptTemplates()).Returns(templates);
60+
61+
var handler = new ChatLoadPromptsHandler(svc.Object);
62+
var result = await handler.HandleAsync(new ChatLoadPromptsCommand(), _ctx);
63+
64+
result.IsSuccess.Should().BeTrue();
65+
result.Value.Should().HaveCount(1);
66+
result.Value![0].Name.Should().Be("Summarize");
67+
}
68+
69+
// --- ChatSubmitPrompt ---
70+
71+
[Fact]
72+
public async Task ChatSubmitPromptHandler_HandleAsync_WithTemplate_ReturnsShouldSendTrue()
73+
{
74+
var prompt = new PromptTemplate { Name = "Test", Template = "Do the thing" };
75+
var handler = new ChatSubmitPromptHandler();
76+
var result = await handler.HandleAsync(new ChatSubmitPromptCommand(prompt), _ctx);
77+
78+
result.IsSuccess.Should().BeTrue();
79+
result.Value!.ShouldSend.Should().BeTrue();
80+
result.Value.PromptText.Should().Be("Do the thing");
81+
}
82+
83+
[Fact]
84+
public async Task ChatSubmitPromptHandler_HandleAsync_NullPrompt_ReturnsShouldSendFalse()
85+
{
86+
var handler = new ChatSubmitPromptHandler();
87+
var result = await handler.HandleAsync(new ChatSubmitPromptCommand(null), _ctx);
88+
89+
result.IsSuccess.Should().BeTrue();
90+
result.Value!.ShouldSend.Should().BeFalse();
91+
result.Value.PromptText.Should().BeEmpty();
92+
}
93+
94+
[Fact]
95+
public async Task ChatSubmitPromptHandler_HandleAsync_EmptyTemplate_ReturnsShouldSendFalse()
96+
{
97+
var prompt = new PromptTemplate { Name = "Empty", Template = " " };
98+
var handler = new ChatSubmitPromptHandler();
99+
var result = await handler.HandleAsync(new ChatSubmitPromptCommand(prompt), _ctx);
100+
101+
result.IsSuccess.Should().BeTrue();
102+
result.Value!.ShouldSend.Should().BeFalse();
103+
}
104+
105+
// --- ChatPopulatePrompt ---
106+
107+
[Fact]
108+
public async Task ChatPopulatePromptHandler_HandleAsync_ReturnsTemplateText()
109+
{
110+
var prompt = new PromptTemplate { Name = "Q", Template = " Ask a question " };
111+
var handler = new ChatPopulatePromptHandler();
112+
var result = await handler.HandleAsync(new ChatPopulatePromptCommand(prompt), _ctx);
113+
114+
result.IsSuccess.Should().BeTrue();
115+
result.Value.Should().Be("Ask a question");
116+
}
117+
118+
[Fact]
119+
public async Task ChatPopulatePromptHandler_HandleAsync_NullPrompt_ReturnsEmpty()
120+
{
121+
var handler = new ChatPopulatePromptHandler();
122+
var result = await handler.HandleAsync(new ChatPopulatePromptCommand(null), _ctx);
123+
124+
result.IsSuccess.Should().BeTrue();
125+
result.Value.Should().BeEmpty();
126+
}
127+
128+
// --- ChatLoadModels ---
129+
130+
[Fact]
131+
public async Task ChatLoadModelsHandler_HandleAsync_ReturnsModels()
132+
{
133+
var svc = new Mock<IChatModelDiscoveryService>();
134+
var models = new List<string> { "llama3", "mistral" };
135+
svc.Setup(s => s.GetAvailableModelsAsync(It.IsAny<CancellationToken>()))
136+
.ReturnsAsync(models);
137+
138+
var handler = new ChatLoadModelsHandler(svc.Object);
139+
var result = await handler.HandleAsync(new ChatLoadModelsQuery("llama3"), _ctx);
140+
141+
result.IsSuccess.Should().BeTrue();
142+
result.Value!.IsReachable.Should().BeTrue();
143+
result.Value.Models.Should().HaveCount(2);
144+
result.Value.SelectedModel.Should().Be("llama3");
145+
}
146+
147+
[Fact]
148+
public async Task ChatLoadModelsHandler_HandleAsync_PreferredNotInList_SelectsFirst()
149+
{
150+
var svc = new Mock<IChatModelDiscoveryService>();
151+
var models = new List<string> { "llama3", "mistral" };
152+
svc.Setup(s => s.GetAvailableModelsAsync(It.IsAny<CancellationToken>()))
153+
.ReturnsAsync(models);
154+
155+
var handler = new ChatLoadModelsHandler(svc.Object);
156+
var result = await handler.HandleAsync(new ChatLoadModelsQuery("gpt-4"), _ctx);
157+
158+
result.IsSuccess.Should().BeTrue();
159+
result.Value!.SelectedModel.Should().Be("llama3");
160+
}
161+
162+
[Fact]
163+
public async Task ChatLoadModelsHandler_HandleAsync_ServiceThrows_ReturnsNotReachable()
164+
{
165+
var svc = new Mock<IChatModelDiscoveryService>();
166+
svc.Setup(s => s.GetAvailableModelsAsync(It.IsAny<CancellationToken>()))
167+
.ThrowsAsync(new HttpRequestException("connection refused"));
168+
169+
var handler = new ChatLoadModelsHandler(svc.Object);
170+
var result = await handler.HandleAsync(new ChatLoadModelsQuery(null), _ctx);
171+
172+
result.IsSuccess.Should().BeTrue();
173+
result.Value!.IsReachable.Should().BeFalse();
174+
result.Value.Models.Should().BeEmpty();
175+
}
176+
177+
// --- ChatSendMessage ---
178+
179+
[Fact]
180+
public async Task ChatSendMessageHandler_HandleAsync_ReturnsReply()
181+
{
182+
var svc = new Mock<IChatSendOrchestrationService>();
183+
svc.Setup(s => s.SendMessageAsync(It.IsAny<ChatSendRequest>(), It.IsAny<IProgress<string>?>(), It.IsAny<CancellationToken>()))
184+
.ReturnsAsync("Hello back!");
185+
186+
var request = new ChatSendRequest("Hi", "context", "llama3");
187+
var handler = new ChatSendMessageHandler(svc.Object);
188+
var result = await handler.HandleAsync(new ChatSendMessageCommand(request), _ctx);
189+
190+
result.IsSuccess.Should().BeTrue();
191+
result.Value!.Success.Should().BeTrue();
192+
result.Value.ReplyText.Should().Be("Hello back!");
193+
result.Value.WasCancelled.Should().BeFalse();
194+
}
195+
196+
[Fact]
197+
public async Task ChatSendMessageHandler_HandleAsync_Cancelled_ReturnsCancelledResult()
198+
{
199+
var svc = new Mock<IChatSendOrchestrationService>();
200+
svc.Setup(s => s.SendMessageAsync(It.IsAny<ChatSendRequest>(), It.IsAny<IProgress<string>?>(), It.IsAny<CancellationToken>()))
201+
.ThrowsAsync(new OperationCanceledException());
202+
203+
var request = new ChatSendRequest("Hi", "context", "llama3");
204+
var handler = new ChatSendMessageHandler(svc.Object);
205+
var result = await handler.HandleAsync(new ChatSendMessageCommand(request), _ctx);
206+
207+
result.IsSuccess.Should().BeTrue();
208+
result.Value!.WasCancelled.Should().BeTrue();
209+
result.Value.Success.Should().BeFalse();
210+
}
211+
212+
[Fact]
213+
public async Task ChatSendMessageHandler_HandleAsync_Error_ReturnsFailure()
214+
{
215+
var svc = new Mock<IChatSendOrchestrationService>();
216+
svc.Setup(s => s.SendMessageAsync(It.IsAny<ChatSendRequest>(), It.IsAny<IProgress<string>?>(), It.IsAny<CancellationToken>()))
217+
.ThrowsAsync(new InvalidOperationException("backend error"));
218+
219+
var request = new ChatSendRequest("Hi", "context", "llama3");
220+
var handler = new ChatSendMessageHandler(svc.Object);
221+
var result = await handler.HandleAsync(new ChatSendMessageCommand(request), _ctx);
222+
223+
result.IsSuccess.Should().BeFalse();
224+
}
225+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using FluentAssertions;
2+
using McpServer.Cqrs;
3+
using McpServerManager.Core.Commands;
4+
using McpServerManager.Core.Models.Json;
5+
using Moq;
6+
using Xunit;
7+
8+
namespace McpServerManager.Core.Tests.Commands;
9+
10+
public sealed class ClipboardHandlerTests
11+
{
12+
private readonly Mock<ICommandTarget> _target = new();
13+
private readonly CallContext _ctx = new();
14+
15+
[Fact]
16+
public async Task CopyTextHandler_HandleAsync_CallsCopyText()
17+
{
18+
_target.Setup(t => t.CopyText("hello")).Returns(Task.CompletedTask);
19+
var handler = new CopyTextHandler(_target.Object);
20+
var result = await handler.HandleAsync(new CopyTextCommand("hello"), _ctx);
21+
22+
result.IsSuccess.Should().BeTrue();
23+
_target.Verify(t => t.CopyText("hello"), Times.Once);
24+
}
25+
26+
[Fact]
27+
public async Task CopyOriginalJsonHandler_HandleAsync_CallsCopyOriginalJson()
28+
{
29+
var entry = new UnifiedRequestEntry();
30+
_target.Setup(t => t.CopyOriginalJson(entry)).Returns(Task.CompletedTask);
31+
var handler = new CopyOriginalJsonHandler(_target.Object);
32+
var result = await handler.HandleAsync(new CopyOriginalJsonCommand(entry), _ctx);
33+
34+
result.IsSuccess.Should().BeTrue();
35+
_target.Verify(t => t.CopyOriginalJson(entry), Times.Once);
36+
}
37+
38+
[Fact]
39+
public async Task CopyOriginalJsonHandler_HandleAsync_NullEntry()
40+
{
41+
_target.Setup(t => t.CopyOriginalJson(null)).Returns(Task.CompletedTask);
42+
var handler = new CopyOriginalJsonHandler(_target.Object);
43+
var result = await handler.HandleAsync(new CopyOriginalJsonCommand(null), _ctx);
44+
45+
result.IsSuccess.Should().BeTrue();
46+
_target.Verify(t => t.CopyOriginalJson(null), Times.Once);
47+
}
48+
}

0 commit comments

Comments
 (0)