test(client): add CryptoService tests
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\JdeScoping.Client\JdeScoping.Client.csproj" />
|
||||
<ProjectReference Include="..\..\src\JdeScoping.Core\JdeScoping.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// This file exists to ensure the test project compiles.
|
||||
// Add tests here as needed.
|
||||
namespace JdeScoping.Client.Tests;
|
||||
|
||||
public class Placeholder
|
||||
{
|
||||
// Tests will be added here
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Text.Json;
|
||||
using JdeScoping.Client.Services;
|
||||
using JdeScoping.Core.Models.Auth;
|
||||
using Microsoft.JSInterop;
|
||||
using NSubstitute;
|
||||
using RichardSzalay.MockHttp;
|
||||
using Shouldly;
|
||||
|
||||
namespace JdeScoping.Client.Tests.Services;
|
||||
|
||||
public class CryptoServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task EncryptLoginAsync_ReturnsBase64String()
|
||||
{
|
||||
// Arrange
|
||||
var publicKeyPem = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgk...\n-----END PUBLIC KEY-----";
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
mockHttp.When("/api/auth/public-key")
|
||||
.Respond("application/json", JsonSerializer.Serialize(new PublicKeyResponse(publicKeyPem)));
|
||||
|
||||
var httpClient = new HttpClient(mockHttp) { BaseAddress = new Uri("http://localhost/") };
|
||||
|
||||
var jsRuntime = Substitute.For<IJSRuntime>();
|
||||
// Mock the JavaScript interop to return a base64 encoded string
|
||||
var expectedBase64 = Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
|
||||
jsRuntime.InvokeAsync<string>("jdeScopingInterop.rsaEncrypt", Arg.Any<object[]>())
|
||||
.Returns(new ValueTask<string>(expectedBase64));
|
||||
|
||||
var service = new CryptoService(httpClient, jsRuntime);
|
||||
var loginModel = new LoginModel { Username = "testuser", Password = "testpass" };
|
||||
|
||||
// Act
|
||||
var result = await service.EncryptLoginAsync(loginModel);
|
||||
|
||||
// Assert - result should be valid base64
|
||||
result.ShouldNotBeNullOrEmpty();
|
||||
var decoded = Convert.FromBase64String(result);
|
||||
decoded.ShouldNotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EncryptLoginAsync_CachesPublicKey_OnlyOneHttpCall()
|
||||
{
|
||||
// Arrange
|
||||
var publicKeyPem = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgk...\n-----END PUBLIC KEY-----";
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
|
||||
// Track how many times the public key endpoint is called
|
||||
var keyRequest = mockHttp.Expect("/api/auth/public-key")
|
||||
.Respond("application/json", JsonSerializer.Serialize(new PublicKeyResponse(publicKeyPem)));
|
||||
|
||||
var httpClient = new HttpClient(mockHttp) { BaseAddress = new Uri("http://localhost/") };
|
||||
|
||||
var jsRuntime = Substitute.For<IJSRuntime>();
|
||||
var encryptedBase64 = Convert.ToBase64String(new byte[] { 10, 20, 30, 40 });
|
||||
jsRuntime.InvokeAsync<string>("jdeScopingInterop.rsaEncrypt", Arg.Any<object[]>())
|
||||
.Returns(new ValueTask<string>(encryptedBase64));
|
||||
|
||||
var service = new CryptoService(httpClient, jsRuntime);
|
||||
var loginModel1 = new LoginModel { Username = "user1", Password = "pass1" };
|
||||
var loginModel2 = new LoginModel { Username = "user2", Password = "pass2" };
|
||||
|
||||
// Act - call EncryptLoginAsync twice
|
||||
await service.EncryptLoginAsync(loginModel1);
|
||||
await service.EncryptLoginAsync(loginModel2);
|
||||
|
||||
// Assert - public key should only be fetched once (cached)
|
||||
mockHttp.GetMatchCount(keyRequest).ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EncryptLoginAsync_CallsJavaScriptInteropWithCorrectParameters()
|
||||
{
|
||||
// Arrange
|
||||
var publicKeyPem = "-----BEGIN PUBLIC KEY-----\nTestKey\n-----END PUBLIC KEY-----";
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
mockHttp.When("/api/auth/public-key")
|
||||
.Respond("application/json", JsonSerializer.Serialize(new PublicKeyResponse(publicKeyPem)));
|
||||
|
||||
var httpClient = new HttpClient(mockHttp) { BaseAddress = new Uri("http://localhost/") };
|
||||
|
||||
var jsRuntime = Substitute.For<IJSRuntime>();
|
||||
var encryptedBase64 = Convert.ToBase64String(new byte[] { 1, 2, 3 });
|
||||
jsRuntime.InvokeAsync<string>("jdeScopingInterop.rsaEncrypt", Arg.Any<object[]>())
|
||||
.Returns(new ValueTask<string>(encryptedBase64));
|
||||
|
||||
var service = new CryptoService(httpClient, jsRuntime);
|
||||
var loginModel = new LoginModel { Username = "testuser", Password = "secret123" };
|
||||
|
||||
// Act
|
||||
await service.EncryptLoginAsync(loginModel);
|
||||
|
||||
// Assert - verify JS interop was called with correct function name
|
||||
await jsRuntime.Received(1).InvokeAsync<string>(
|
||||
"jdeScopingInterop.rsaEncrypt",
|
||||
Arg.Is<object[]>(args =>
|
||||
args.Length == 2 &&
|
||||
args[0] is string &&
|
||||
((string)args[0]) == publicKeyPem &&
|
||||
args[1] is string));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EncryptLoginAsync_SerializesLoginModelToJson()
|
||||
{
|
||||
// Arrange
|
||||
var publicKeyPem = "-----BEGIN PUBLIC KEY-----\nTestKey\n-----END PUBLIC KEY-----";
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
mockHttp.When("/api/auth/public-key")
|
||||
.Respond("application/json", JsonSerializer.Serialize(new PublicKeyResponse(publicKeyPem)));
|
||||
|
||||
var httpClient = new HttpClient(mockHttp) { BaseAddress = new Uri("http://localhost/") };
|
||||
|
||||
string? capturedJson = null;
|
||||
var jsRuntime = Substitute.For<IJSRuntime>();
|
||||
jsRuntime.InvokeAsync<string>("jdeScopingInterop.rsaEncrypt", Arg.Any<object[]>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
var args = callInfo.Arg<object[]>();
|
||||
capturedJson = args[1] as string;
|
||||
return new ValueTask<string>(Convert.ToBase64String(new byte[] { 1 }));
|
||||
});
|
||||
|
||||
var service = new CryptoService(httpClient, jsRuntime);
|
||||
var loginModel = new LoginModel { Username = "myuser", Password = "mypassword" };
|
||||
|
||||
// Act
|
||||
await service.EncryptLoginAsync(loginModel);
|
||||
|
||||
// Assert - verify the JSON contains the login credentials
|
||||
capturedJson.ShouldNotBeNull();
|
||||
capturedJson.ShouldContain("myuser");
|
||||
capturedJson.ShouldContain("mypassword");
|
||||
|
||||
// Verify it's valid JSON that can deserialize back to LoginModel
|
||||
var deserializedModel = JsonSerializer.Deserialize<LoginModel>(capturedJson);
|
||||
deserializedModel.ShouldNotBeNull();
|
||||
deserializedModel.Username.ShouldBe("myuser");
|
||||
deserializedModel.Password.ShouldBe("mypassword");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user