diff --git a/NEW/tests/JdeScoping.Client.Tests/JdeScoping.Client.Tests.csproj b/NEW/tests/JdeScoping.Client.Tests/JdeScoping.Client.Tests.csproj index a828386..a297514 100644 --- a/NEW/tests/JdeScoping.Client.Tests/JdeScoping.Client.Tests.csproj +++ b/NEW/tests/JdeScoping.Client.Tests/JdeScoping.Client.Tests.csproj @@ -11,6 +11,7 @@ + @@ -22,6 +23,7 @@ + diff --git a/NEW/tests/JdeScoping.Client.Tests/Placeholder.cs b/NEW/tests/JdeScoping.Client.Tests/Placeholder.cs deleted file mode 100644 index dd9f57b..0000000 --- a/NEW/tests/JdeScoping.Client.Tests/Placeholder.cs +++ /dev/null @@ -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 -} diff --git a/NEW/tests/JdeScoping.Client.Tests/Services/CryptoServiceTests.cs b/NEW/tests/JdeScoping.Client.Tests/Services/CryptoServiceTests.cs new file mode 100644 index 0000000..b694daf --- /dev/null +++ b/NEW/tests/JdeScoping.Client.Tests/Services/CryptoServiceTests.cs @@ -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(); + // 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("jdeScopingInterop.rsaEncrypt", Arg.Any()) + .Returns(new ValueTask(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(); + var encryptedBase64 = Convert.ToBase64String(new byte[] { 10, 20, 30, 40 }); + jsRuntime.InvokeAsync("jdeScopingInterop.rsaEncrypt", Arg.Any()) + .Returns(new ValueTask(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(); + var encryptedBase64 = Convert.ToBase64String(new byte[] { 1, 2, 3 }); + jsRuntime.InvokeAsync("jdeScopingInterop.rsaEncrypt", Arg.Any()) + .Returns(new ValueTask(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( + "jdeScopingInterop.rsaEncrypt", + Arg.Is(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(); + jsRuntime.InvokeAsync("jdeScopingInterop.rsaEncrypt", Arg.Any()) + .Returns(callInfo => + { + var args = callInfo.Arg(); + capturedJson = args[1] as string; + return new ValueTask(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(capturedJson); + deserializedModel.ShouldNotBeNull(); + deserializedModel.Username.ShouldBe("myuser"); + deserializedModel.Password.ShouldBe("mypassword"); + } +}