Files
2026-01-03 08:48:24 -05:00

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");
}
}