Skip to content
Merged
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
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
- Always use curly braces `{}` for all control structures (if, else, for, foreach, while, etc.), even wrapping single-line statements.
- Insert a single blank line (CRLF) between method and function definitions. Adhere to StyleCop and commonly accepted C# formatting best practices throughout your code.
- Adhere to SOLID design principles and design patterns where applicable.
- Adhere to SOLID design principles and design patterns where applicable. Try to keep code elegant, clean, and succinct.
- Create interfaces for public classes, especially any that might be used for dependency injection. Add XML documentation comments to the interfaces with <inheritdoc/> on classes implementing the interface.
- Add XML documentation comments to all public interfaces (and classes, methods, and properties not covered by <inheritdoc/>. Ensure comments are meaningful and up to date.
- Prefix private fields in a class with an underscore (`_`) and use camelCase.
- Prefer asynchronous methods over synchronous/blocking methods. Avoid blocking calls such as `.Result` or `.Wait()` on tasks.
- Prefer asynchronous methods over synchronous/blocking methods. Avoid blocking calls such as `.Result`, `.Wait()`, `.GetAwaiter()`, etc. on tasks.
Comment thread
rrusson marked this conversation as resolved.
- Always use `.ConfigureAwait(false)` on awaited tasks unless it explicitly requires the synchronization context.
- Write unit tests using the MSTest framework. For mocking dependencies, use MOQ. If mocking requires dependency injection and a suitable mock isn’t available, use Autofac.Extras.Moq’s `AutoMock.GetLoose`.
- Use the `var` keyword for local variable declarations when the type is clear from the right-hand side of the assignment; otherwise, use the explicit type.
- Use the `nameof` operator when referencing identifiers (such as property names, method names, etc.), instead of hardcoded strings.
- Strive for small, single-responsibility methods. Refactor larger methods (>50 lines) into smaller pieces when practical.
- If a class contains many private methods, consider refactoring related functionality into separate classes to improve maintainability and cohesion.
- Use `IEnumerable<T>` or arrays for method parameters and return types when the collection is not intended to be modified. Use `List<T>` only when modification is required.
- Apply consistent exception handling practices across the codebase. Ensure that exceptions are meaningful, logged appropriately, and do not expose sensitive details.
- Apply consistent exception handling practices across the codebase. Ensure that exceptions are meaningful, logged appropriately, and do not expose sensitive details.
- Use Windows CRLF line endings for all code files. Ensure that your development environment is configured to use CRLF line endings to maintain consistency across the codebase.
12 changes: 8 additions & 4 deletions ClippyWeb.Tests/ClippyWeb.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<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="4.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="4.2.1" />
<PackageReference Include="coverlet.collector" Version="10.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
13 changes: 5 additions & 8 deletions ClippyWeb.Tests/SemanticKernelClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SemanticKernelHelper;

namespace ClippyWeb.Tests
Expand Down Expand Up @@ -64,31 +65,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));
}
}
}
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.ThrowsAsync<UriFormatException>(() => _sut.ValidateConnectionAsync(_mockConfiguration.Object));
}
}
}
16 changes: 8 additions & 8 deletions ClippyWeb.Tests/Util/TcpClientWrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
sut.Dispose();

// Assert
Assert.IsTrue(true); // Dispose completed without throwing

Check warning on line 41 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)

Check warning on line 41 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}

/// <summary>
Expand Down Expand Up @@ -111,7 +111,7 @@
try
{
var _ = wrapper.Connected;
Assert.IsTrue(true);

Check warning on line 114 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}
catch (Exception ex)
{
Expand Down Expand Up @@ -175,7 +175,7 @@
catch (ObjectDisposedException)
{
// This is also acceptable behavior after disposal
Assert.IsTrue(true);

Check warning on line 178 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}
}

Expand Down Expand Up @@ -215,7 +215,7 @@

// Assert
// Multiple dispose calls should not throw exceptions
Assert.IsTrue(true);

Check warning on line 218 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}

/// <summary>
Expand Down Expand Up @@ -246,7 +246,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () =>
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
{
await sut.ConnectAsync(host!, port, cancellationToken);
});
Expand All @@ -265,7 +265,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () =>
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand All @@ -284,7 +284,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<SocketException>(async () =>
await Assert.ThrowsAsync<SocketException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand All @@ -303,7 +303,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () =>
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand All @@ -322,7 +322,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () =>
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand All @@ -341,7 +341,7 @@
var cancellationToken = CancellationToken.None;

// Act & Assert
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () =>
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand All @@ -361,7 +361,7 @@
cts.Cancel();

// Act & Assert
await Assert.ThrowsExceptionAsync<TaskCanceledException>(async () =>
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
await sut.ConnectAsync(host, port, cts.Token);
});
Expand All @@ -382,7 +382,7 @@

// Act & Assert
// Empty host should throw an exception from the underlying TcpClient
await Assert.ThrowsExceptionAsync<ArgumentException>(async () =>
await Assert.ThrowsAsync<ArgumentException>(async () =>
{
await sut.ConnectAsync(host, port, cancellationToken);
});
Expand Down Expand Up @@ -414,12 +414,12 @@
catch (SocketException)
{
// Expected when no service is listening
Assert.IsTrue(true);

Check warning on line 417 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}
catch (OperationCanceledException)
{
// Expected if timeout occurs before connection fails
Assert.IsTrue(true);

Check warning on line 422 in ClippyWeb.Tests/Util/TcpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Review or remove the assertion as its condition is known to be always true (https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0032)
}
}

Expand Down
2 changes: 1 addition & 1 deletion ClippyWeb/ClippyWeb.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="MarkdownSharp" Version="2.0.5" />
<PackageReference Include="Microsoft.AspNet.Razor" Version="3.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.6" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions SemanticKernelHelper/ChatClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public IChatClient GetOrCreateClient(string sessionKey)
SlidingExpiration = TimeSpan.FromMinutes(30),
Priority = CacheItemPriority.Normal
};

_cache.Set(cacheKey, newClient, cacheOptions);
return newClient;
}
Expand Down
5 changes: 3 additions & 2 deletions SemanticKernelHelper/SemanticKernelHelper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.68.0" />
<PackageReference Include="Microsoft.Extensions.AI" Version="10.5.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.6" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.74.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion SharedInterfaces/SharedInterfaces.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.6" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion TestConsole/TestConsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.6" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading