Skip to content

Commit aa98a9f

Browse files
sharpninjaCopilot
andcommitted
Add 23 integration tests: DI container, command round-trip, factory (P8-3)
- DiContainerTests: verify Dispatcher, ICommandTarget, and all command handlers resolve correctly from the DI container built by UiCoreServiceProviderFactory - CommandRoundTripTests: verify commands dispatch end-to-end through Dispatcher and reach the mock ICommandTarget - FactoryIntegrationTests: verify McpServiceFactory creates all service types with correct types and distinct instances - Add InternalsVisibleTo for test project access to internal factory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 016b8c3 commit aa98a9f

4 files changed

Lines changed: 312 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using FluentAssertions;
2+
using McpServer.Cqrs;
3+
using McpServer.Client;
4+
using McpServerManager.Core.Commands;
5+
using McpServerManager.Core.Services;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Moq;
8+
using Xunit;
9+
10+
namespace McpServerManager.Core.Tests.Integration;
11+
12+
public sealed class CommandRoundTripTests : IDisposable
13+
{
14+
private readonly Mock<ICommandTarget> _target = new();
15+
private readonly ServiceProvider _provider;
16+
private readonly Dispatcher _dispatcher;
17+
18+
public CommandRoundTripTests()
19+
{
20+
var http = new HttpClient();
21+
var options = new McpServerClientOptions { BaseUrl = new Uri("http://localhost:9999") };
22+
var client = new McpServerClient(http, options);
23+
var todoService = new McpTodoService(client, client);
24+
25+
_provider = UiCoreServiceProviderFactory.Build(
26+
_target.Object,
27+
todoService: todoService);
28+
29+
_dispatcher = _provider.GetRequiredService<Dispatcher>();
30+
}
31+
32+
[Fact]
33+
public async Task NavigateBackCommand_DispatchesThroughDispatcher()
34+
{
35+
var result = await _dispatcher.SendAsync(new NavigateBackCommand());
36+
37+
result.IsSuccess.Should().BeTrue();
38+
_target.Verify(t => t.NavigateBack(), Times.Once);
39+
}
40+
41+
[Fact]
42+
public async Task NavigateForwardCommand_DispatchesThroughDispatcher()
43+
{
44+
var result = await _dispatcher.SendAsync(new NavigateForwardCommand());
45+
46+
result.IsSuccess.Should().BeTrue();
47+
_target.Verify(t => t.NavigateForward(), Times.Once);
48+
}
49+
50+
[Fact]
51+
public async Task OpenAgentConfigCommand_DispatchesThroughDispatcher()
52+
{
53+
var result = await _dispatcher.SendAsync(new OpenAgentConfigCommand());
54+
55+
result.IsSuccess.Should().BeTrue();
56+
_target.Verify(t => t.OpenAgentConfig(), Times.Once);
57+
}
58+
59+
[Fact]
60+
public async Task OpenPromptTemplatesCommand_DispatchesThroughDispatcher()
61+
{
62+
var result = await _dispatcher.SendAsync(new OpenPromptTemplatesCommand());
63+
64+
result.IsSuccess.Should().BeTrue();
65+
_target.Verify(t => t.OpenPromptTemplates(), Times.Once);
66+
}
67+
68+
[Fact]
69+
public async Task ArchiveCurrentCommand_DispatchesThroughDispatcher()
70+
{
71+
var result = await _dispatcher.SendAsync(new ArchiveCurrentCommand());
72+
73+
result.IsSuccess.Should().BeTrue();
74+
_target.Verify(t => t.Archive(), Times.Once);
75+
}
76+
77+
[Fact]
78+
public async Task ToggleShowRawMarkdownCommand_DispatchesThroughDispatcher()
79+
{
80+
var result = await _dispatcher.SendAsync(new ToggleShowRawMarkdownCommand());
81+
82+
result.IsSuccess.Should().BeTrue();
83+
_target.Verify(t => t.ToggleShowRawMarkdown(), Times.Once);
84+
}
85+
86+
[Fact]
87+
public async Task PhoneNavigateSectionCommand_DispatchesThroughDispatcher()
88+
{
89+
var result = await _dispatcher.SendAsync(new PhoneNavigateSectionCommand("details"));
90+
91+
result.IsSuccess.Should().BeTrue();
92+
_target.Verify(t => t.PhoneNavigateSection("details"), Times.Once);
93+
}
94+
95+
[Fact]
96+
public async Task MultipleCommands_DispatchSequentially()
97+
{
98+
await _dispatcher.SendAsync(new NavigateBackCommand());
99+
await _dispatcher.SendAsync(new NavigateForwardCommand());
100+
await _dispatcher.SendAsync(new NavigateBackCommand());
101+
102+
_target.Verify(t => t.NavigateBack(), Times.Exactly(2));
103+
_target.Verify(t => t.NavigateForward(), Times.Once);
104+
}
105+
106+
public void Dispose() => _provider.Dispose();
107+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using FluentAssertions;
2+
using McpServer.Cqrs;
3+
using McpServer.Client;
4+
using McpServerManager.Core.Commands;
5+
using McpServerManager.Core.Services;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Moq;
8+
using Xunit;
9+
10+
namespace McpServerManager.Core.Tests.Integration;
11+
12+
public sealed class DiContainerTests : IDisposable
13+
{
14+
private readonly Mock<ICommandTarget> _target = new();
15+
private readonly ServiceProvider _provider;
16+
17+
public DiContainerTests()
18+
{
19+
var http = new HttpClient();
20+
var options = new McpServerClientOptions { BaseUrl = new Uri("http://localhost:9999") };
21+
var client = new McpServerClient(http, options);
22+
var todoService = new McpTodoService(client, client);
23+
24+
_provider = UiCoreServiceProviderFactory.Build(
25+
_target.Object,
26+
todoService: todoService);
27+
}
28+
29+
[Fact]
30+
public void ServiceProvider_Resolves_Dispatcher()
31+
{
32+
var dispatcher = _provider.GetService<Dispatcher>();
33+
dispatcher.Should().NotBeNull();
34+
}
35+
36+
[Fact]
37+
public void ServiceProvider_Resolves_ICommandTarget()
38+
{
39+
var target = _provider.GetService<ICommandTarget>();
40+
target.Should().NotBeNull();
41+
target.Should().BeSameAs(_target.Object);
42+
}
43+
44+
[Fact]
45+
public void ServiceProvider_Resolves_NavigateBackHandler()
46+
{
47+
var handler = _provider.GetService<ICommandHandler<NavigateBackCommand, bool>>();
48+
handler.Should().NotBeNull();
49+
handler.Should().BeOfType<NavigateBackHandler>();
50+
}
51+
52+
[Fact]
53+
public void ServiceProvider_Resolves_NavigateForwardHandler()
54+
{
55+
var handler = _provider.GetService<ICommandHandler<NavigateForwardCommand, bool>>();
56+
handler.Should().NotBeNull();
57+
handler.Should().BeOfType<NavigateForwardHandler>();
58+
}
59+
60+
[Fact]
61+
public void ServiceProvider_Resolves_RefreshViewHandler()
62+
{
63+
var handler = _provider.GetService<ICommandHandler<RefreshViewCommand, bool>>();
64+
handler.Should().NotBeNull();
65+
handler.Should().BeOfType<RefreshViewHandler>();
66+
}
67+
68+
[Fact]
69+
public void ServiceProvider_Resolves_AllNavigationHandlers()
70+
{
71+
_provider.GetService<ICommandHandler<NavigateBackCommand, bool>>()
72+
.Should().NotBeNull("NavigateBackHandler should be registered");
73+
74+
_provider.GetService<ICommandHandler<NavigateForwardCommand, bool>>()
75+
.Should().NotBeNull("NavigateForwardHandler should be registered");
76+
77+
_provider.GetService<ICommandHandler<PhoneNavigateSectionCommand, bool>>()
78+
.Should().NotBeNull("PhoneNavigateSectionHandler should be registered");
79+
}
80+
81+
[Fact]
82+
public void ServiceProvider_Resolves_ClipboardHandlers()
83+
{
84+
_provider.GetService<ICommandHandler<CopyTextCommand, bool>>()
85+
.Should().NotBeNull("CopyTextHandler should be registered");
86+
87+
_provider.GetService<ICommandHandler<CopyOriginalJsonCommand, bool>>()
88+
.Should().NotBeNull("CopyOriginalJsonHandler should be registered");
89+
}
90+
91+
[Fact]
92+
public void ServiceProvider_Resolves_ArchiveHandlers()
93+
{
94+
_provider.GetService<ICommandHandler<ArchiveCurrentCommand, bool>>()
95+
.Should().NotBeNull("ArchiveCurrentHandler should be registered");
96+
97+
_provider.GetService<ICommandHandler<ArchiveTreeItemCommand, bool>>()
98+
.Should().NotBeNull("ArchiveTreeItemHandler should be registered");
99+
}
100+
101+
[Fact]
102+
public void Build_WithoutAnyService_Throws()
103+
{
104+
var act = () => UiCoreServiceProviderFactory.Build(_target.Object);
105+
act.Should().Throw<ArgumentException>();
106+
}
107+
108+
public void Dispose() => _provider.Dispose();
109+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using FluentAssertions;
2+
using McpServer.Client;
3+
using McpServerManager.Core.Services;
4+
using Xunit;
5+
6+
namespace McpServerManager.Core.Tests.Integration;
7+
8+
public sealed class FactoryIntegrationTests
9+
{
10+
private readonly McpServiceFactory _factory = new();
11+
12+
private static McpServerClient CreateClient()
13+
{
14+
var http = new HttpClient();
15+
var options = new McpServerClientOptions { BaseUrl = new Uri("http://localhost:9999") };
16+
return new McpServerClient(http, options);
17+
}
18+
19+
[Fact]
20+
public void CreateSessionLogService_ReturnsWorkingService()
21+
{
22+
var client = CreateClient();
23+
var service = _factory.CreateSessionLogService(client);
24+
25+
service.Should().NotBeNull();
26+
service.Should().BeOfType<McpSessionLogService>();
27+
}
28+
29+
[Fact]
30+
public void CreateTodoService_ReturnsWorkingService()
31+
{
32+
var client = CreateClient();
33+
var service = _factory.CreateTodoService(client, client);
34+
35+
service.Should().NotBeNull();
36+
service.Should().BeOfType<McpTodoService>();
37+
}
38+
39+
[Fact]
40+
public void CreateWorkspaceService_ReturnsWorkingService()
41+
{
42+
var client = CreateClient();
43+
var service = _factory.CreateWorkspaceService(client, new Uri("http://localhost:9999"));
44+
45+
service.Should().NotBeNull();
46+
service.Should().BeOfType<McpWorkspaceService>();
47+
}
48+
49+
[Fact]
50+
public void CreateVoiceService_ReturnsWorkingService()
51+
{
52+
var service = _factory.CreateVoiceService(
53+
baseUrl: "http://localhost:9999",
54+
apiKey: "test-key",
55+
bearerToken: null,
56+
resolveBaseUrl: () => "http://localhost:9999",
57+
resolveBearerToken: () => null,
58+
resolveApiKey: () => "test-key",
59+
resolveWorkspacePath: () => "/workspace");
60+
61+
service.Should().NotBeNull();
62+
service.Should().BeOfType<McpVoiceConversationService>();
63+
}
64+
65+
[Fact]
66+
public void CreateEventStreamService_ReturnsWorkingService()
67+
{
68+
var service = _factory.CreateEventStreamService(
69+
baseUrl: "http://localhost:9999",
70+
apiKey: "test-key",
71+
bearerToken: null,
72+
resolveBaseUrl: () => "http://localhost:9999",
73+
resolveBearerToken: () => null,
74+
resolveApiKey: () => "test-key",
75+
resolveWorkspacePath: () => "/workspace");
76+
77+
service.Should().NotBeNull();
78+
service.Should().BeOfType<McpAgentEventStreamService>();
79+
}
80+
81+
[Fact]
82+
public void CreateSessionLogService_WithDifferentClients_ReturnsDistinctInstances()
83+
{
84+
var client1 = CreateClient();
85+
var client2 = CreateClient();
86+
87+
var service1 = _factory.CreateSessionLogService(client1);
88+
var service2 = _factory.CreateSessionLogService(client2);
89+
90+
service1.Should().NotBeSameAs(service2);
91+
}
92+
}

src/McpServerManager.Core/McpServerManager.Core.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
66
</PropertyGroup>
77

8+
<ItemGroup>
9+
<InternalsVisibleTo Include="McpServerManager.Core.Tests" />
10+
</ItemGroup>
11+
812
<ItemGroup>
913
<AvaloniaResource Include="Assets\**" />
1014
</ItemGroup>

0 commit comments

Comments
 (0)