143 lines
6.1 KiB
C#
143 lines
6.1 KiB
C#
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");
|
|
}
|
|
}
|