Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ csharp_style_expression_bodied_local_functions = false:silent
# CA1305: Specify IFormatProvider
dotnet_diagnostic.CA1305.severity = silent

# CA1848: Use the LoggerMessage delegates (overly complicated for simple logging)
dotnet_diagnostic.CA1848.severity = none

[*.{cs,vb}]
#### Naming styles ####

Expand Down
22 changes: 11 additions & 11 deletions ClippyWeb.Tests/ChatClientFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,47 @@ public void TestCleanup()
}

[TestMethod]
public void IfSessionKeyIsProvidedThenClientIsReturned()
public async Task IfSessionKeyIsProvidedThenClientIsReturned()
{
var factory = new ChatClientFactory(TestApiUrl, TestModel, TestApiKey, _cache);

var client = factory.GetOrCreateClient("test-session");
var client = await factory.GetOrCreateClientAsync("test-session");

Assert.IsNotNull(client);
}

[TestMethod]
public void IfSameSessionKeyIsUsedThenSameClientIsReturned()
public async Task IfSameSessionKeyIsUsedThenSameClientIsReturned()
{
var factory = new ChatClientFactory(TestApiUrl, TestModel, TestApiKey, _cache);

var client1 = factory.GetOrCreateClient("test-session");
var client2 = factory.GetOrCreateClient("test-session");
var client1 = await factory.GetOrCreateClientAsync("test-session");
var client2 = await factory.GetOrCreateClientAsync("test-session");

Assert.AreSame(client1, client2);
}

[TestMethod]
public void IfDifferentSessionKeysAreUsedThenDifferentClientsAreReturned()
public async Task IfDifferentSessionKeysAreUsedThenDifferentClientsAreReturned()
{
var factory = new ChatClientFactory(TestApiUrl, TestModel, TestApiKey, _cache);

var client1 = factory.GetOrCreateClient("session-1");
var client2 = factory.GetOrCreateClient("session-2");
var client1 = await factory.GetOrCreateClientAsync("session-1");
var client2 = await factory.GetOrCreateClientAsync("session-2");

Assert.AreNotSame(client1, client2);
}

[TestMethod]
public void IfMultipleSessionsAreConcurrentThenFactoryIsThreadSafe()
public async Task IfMultipleSessionsAreConcurrentThenFactoryIsThreadSafe()
{
var factory = new ChatClientFactory(TestApiUrl, TestModel, TestApiKey, _cache);
var clients = new List<IChatClient>();
var lockObj = new object();

Parallel.For(0, 10, i =>
Parallel.For(0, 10, async i =>
{
var client = factory.GetOrCreateClient($"session-{i % 3}");
var client = await factory.GetOrCreateClientAsync($"session-{i % 3}");
lock (lockObj)
{
clients.Add(client);
Expand Down
8 changes: 4 additions & 4 deletions ClippyWeb.Tests/ClippyWeb.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest.TestAdapter" Version="3.7.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.7.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="MSTest.TestAdapter" Version="3.11.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.11.1" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
2 changes: 1 addition & 1 deletion ClippyWeb.Tests/Controllers/ChatControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void TestInitialize()
{
_mockChatClient = new Mock<IChatClient>();
_mockChatClientFactory = new Mock<IChatClientFactory>();
_mockChatClientFactory.Setup(f => f.GetOrCreateClient(It.IsAny<string>())).Returns(_mockChatClient.Object);
_mockChatClientFactory.Setup(f => f.GetOrCreateClientAsync(It.IsAny<string>())).ReturnsAsync(_mockChatClient.Object);
_memoryCache = new MemoryCache(new MemoryCacheOptions());
_mockConfiguration = new Mock<IConfiguration>();

Expand Down
89 changes: 89 additions & 0 deletions ClippyWeb.Tests/McpServerRegistryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using SemanticKernelHelper;

using SharedInterfaces;

namespace ClippyWeb.Tests
{
[TestClass]
public class McpServerRegistryTests
{
[TestMethod]
public void WhenNoServersRegisteredThenGetAllReturnsEmpty()
{
var registry = new McpServerRegistry();
var servers = registry.GetAll();

Assert.IsNotNull(servers);
Assert.AreEqual(0, servers.Count());
}

[TestMethod]
public void WhenServerRegisteredThenGetAllReturnsServer()
{
var registry = new McpServerRegistry();
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = true,
Command = "test",
ServerType = "stdio"
};
var server = new StdioMcpServer(config);

registry.Register(server);
var servers = registry.GetAll();

Assert.AreEqual(1, servers.Count());
Assert.AreEqual("test-server", servers.First().Name);
}

[TestMethod]
public void WhenEnabledServerRegisteredThenGetEnabledReturnsServer()
{
var registry = new McpServerRegistry();
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = true,
Command = "test",
ServerType = "stdio"
};
var server = new StdioMcpServer(config);

registry.Register(server);
var enabledServers = registry.GetEnabled();

Assert.AreEqual(1, enabledServers.Count());
}

[TestMethod]
public void WhenDisabledServerRegisteredThenGetEnabledReturnsEmpty()
{
var registry = new McpServerRegistry();
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = false,
Command = "test",
ServerType = "stdio"
};
var server = new StdioMcpServer(config);

registry.Register(server);
var enabledServers = registry.GetEnabled();

Assert.AreEqual(0, enabledServers.Count());
}

[TestMethod]
public void WhenNullServerRegisteredThenThrowsException()
{
var registry = new McpServerRegistry();

Assert.ThrowsExactly<ArgumentNullException>(() => registry.Register(null!));
}
}
}
6 changes: 3 additions & 3 deletions ClippyWeb.Tests/Pages/ErrorModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void IfRequestIdIsSetThenShowRequestIdReturnsTrue()
Assert.IsTrue(_sut.ShowRequestId);
}

[DataTestMethod]
[TestMethod]
[DataRow(null, DisplayName = "Null RequestId")]
[DataRow("", DisplayName = "Empty RequestId")]
public void IfRequestIdIsNullOrEmptyThenShowRequestIdReturnsFalse(string? requestId)
Expand All @@ -84,7 +84,7 @@ public void IfRequestIdIsNullOrEmptyThenShowRequestIdReturnsFalse(string? reques
Assert.IsFalse(result);
}

[DataTestMethod]
[TestMethod]
[DataRow("http://localhost:11434", "http://localhost:11434", DisplayName = "ServiceUrl configured")]
[DataRow(null, "Not configured", DisplayName = "ServiceUrl not configured")]
public void IfServiceUrlConfiguredThenViewDataContainsExpectedValue(string? configuredUrl, string expectedValue)
Expand All @@ -99,7 +99,7 @@ public void IfServiceUrlConfiguredThenViewDataContainsExpectedValue(string? conf
Assert.AreEqual(expectedValue, _sut.ViewData["ServiceUrl"]);
}

[DataTestMethod]
[TestMethod]
[DataRow("llama2", "llama2", DisplayName = "Model configured")]
[DataRow(null, "Not configured", DisplayName = "Model not configured")]
public void IfModelConfiguredThenViewDataContainsExpectedValue(string? configuredModel, string expectedValue)
Expand Down
12 changes: 4 additions & 8 deletions ClippyWeb.Tests/SemanticKernelClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,31 +64,27 @@ public async Task IfNullMessageThenReturnsDefaultResponse()
}

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void IfApiUrlIsNullThenThrowsArgumentNullException()
{
_ = new SemanticKernelClient(null!, TestModel, TestApiKey);
Assert.ThrowsExactly<ArgumentNullException>(() => _ = new SemanticKernelClient(null!, TestModel, TestApiKey));
}

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void IfModelIsNullThenThrowsArgumentNullException()
{
_ = new SemanticKernelClient(TestApiUrl, null!, TestApiKey);
Assert.ThrowsExactly<ArgumentNullException>(() => _ = new SemanticKernelClient(TestApiUrl, null!, TestApiKey));
}

[TestMethod]
[ExpectedException(typeof(UriFormatException))]
public void IfApiUrlIsInvalidThenThrowsUriFormatException()
{
_ = new SemanticKernelClient("not-a-valid-url", TestModel, TestApiKey);
Assert.ThrowsExactly<UriFormatException>(() => _ = new SemanticKernelClient("not-a-valid-url", TestModel, TestApiKey));
}

[TestMethod]
[ExpectedException(typeof(UriFormatException))]
public void IfApiUrlIsNotHttpOrHttpsThenThrowsUriFormatException()
{
_ = new SemanticKernelClient("ftp://localhost:11434", TestModel, TestApiKey);
Assert.ThrowsExactly<UriFormatException>(() => _ = new SemanticKernelClient("ftp://localhost:11434", TestModel, TestApiKey));
}
}
}
86 changes: 86 additions & 0 deletions ClippyWeb.Tests/StdioMcpServerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.ComponentModel;

using SemanticKernelHelper;

using SharedInterfaces;

namespace ClippyWeb.Tests
{
[TestClass]
public class StdioMcpServerTests
{
[TestMethod]
public void WhenConfigurationProvidedThenServerIsCreated()
{
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = true,
Command = "echo",
ServerType = "stdio"
};

var server = new StdioMcpServer(config);

Assert.IsNotNull(server);
Assert.AreEqual("test-server", server.Name);
Assert.AreEqual("Test MCP Server", server.Description);
Assert.IsTrue(server.IsEnabled);
}

[TestMethod]
public void WhenDisabledInConfigThenServerIsDisabled()
{
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = false,
Command = "echo",
ServerType = "stdio"
};

var server = new StdioMcpServer(config);

Assert.IsFalse(server.IsEnabled);
}

[TestMethod]
public void WhenNullConfigurationThenThrowsException()
{
Assert.ThrowsExactly<ArgumentNullException>(() => _ = new StdioMcpServer(null!));
}

[TestMethod]
public async Task WhenInvalidCommandThenInitializeThrowsException()
{
var config = new McpServerConfiguration
{
Name = "test-server",
Description = "Test MCP Server",
Enabled = true,
Command = "invalid-command-that-does-not-exist",
ServerType = "stdio"
};

var server = new StdioMcpServer(config);
await Assert.ThrowsExactlyAsync<Win32Exception>(async () => await server.InitializeAsync());
}

// TODO: This test needs to be refactored to properly test CreatePlugin functionality.
// Current limitation: StdioMcpServer requires a real MCP server process, which makes
// unit testing difficult. Consider:
// 1. Creating an integration test with a real MCP server
// 2. Refactoring StdioMcpServer to accept a process factory for better testability
// 3. Using a test double or creating a mock MCP server process
//
[TestMethod]
[Ignore("Requires a valid MCP server process to test CreatePlugin functionality.")]
public async Task WhenCreatePluginCalledThenPluginIsReturned()
{
// This test would require a valid MCP server command
// For now, it's commented out until proper mocking infrastructure is in place
}
}
}
10 changes: 5 additions & 5 deletions ClippyWeb.Tests/Util/ConnectionValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void TestInitialize()
_sut = new ConnectionValidator(_mockPingService.Object, _mockTcpClientFactory.Object);
}

[DataTestMethod]
[TestMethod]
[DataRow(null, DisplayName = "Null ServiceUrl")]
[DataRow("", DisplayName = "Empty ServiceUrl")]
public async Task IfServiceUrlIsNullOrEmptyThenLogsWarningAndReturns(string? serviceUrl)
Expand All @@ -49,7 +49,7 @@ public async Task IfServiceUrlIsNullOrEmptyThenLogsWarningAndReturns(string? ser
_mockConfiguration.VerifyNoOtherCalls();
}

[DataTestMethod]
[TestMethod]
[DataRow("http://localhost:11434", DisplayName = "Localhost with port")]
[DataRow("http://127.0.0.1:8080", DisplayName = "127.0.0.1 with port")]
[DataRow("http://localhost", DisplayName = "Localhost without port")]
Expand All @@ -69,7 +69,7 @@ public async Task ValidateConnectionAsync_LocalhostTest(string serviceUrl)
_mockTcpClient.Verify(t => t.ConnectAsync(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.AtLeastOnce);
}

[DataTestMethod]
[TestMethod]
[DataRow("http://example.com", DisplayName = "Remote host, no port")]
[DataRow("http://example.com:8080", DisplayName = "Remote host with port")]
[DataRow("http://testhost:5000", DisplayName = "Test host with port")]
Expand All @@ -86,7 +86,7 @@ public async Task ValidateConnectionAsync_RemoteTest(string serviceUrl)
_mockPingService.Verify(t => t.PingAsync(It.IsAny<string>(), It.IsAny<int>()), Times.AtLeastOnce);
}

[DataTestMethod]
[TestMethod]
[DataRow("not-a-valid-uri", DisplayName = "Invalid URI format")]
[DataRow(" ", DisplayName = "Whitespace only")]
public async Task IfServiceUrlIsInvalidUriThenThrowsException(string serviceUrl)
Expand All @@ -95,7 +95,7 @@ public async Task IfServiceUrlIsInvalidUriThenThrowsException(string serviceUrl)
_mockConfiguration.Setup(x => x["ServiceUrl"]).Returns(serviceUrl);

// Act & Assert
await Assert.ThrowsExceptionAsync<UriFormatException>(() => _sut.ValidateConnectionAsync(_mockConfiguration.Object));
await Assert.ThrowsExactlyAsync<UriFormatException>(() => _sut.ValidateConnectionAsync(_mockConfiguration.Object));
}
}
}
Loading