From 760e17b655edaf998d19d3b0304bc5d5036d2838 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 15:28:20 -0300 Subject: [PATCH 1/9] Updated ISessionManager and SessionManager with timeout and cancellation support Updated the `ISessionManager` interface and `SessionManager` class to include a `TimeSpan` parameter in the `RetrieveSession` and `SendSession` methods for timeout specification. Overloads of these methods have been added to accept a `CancellationToken` parameter for operation cancellation. The `StateManager` class's `GetState` and `SaveState` methods have also been updated to accept a `TimeSpan` parameter for timeout. Added a new project `Csla.Blazor.WebAssembly.Tests` for unit tests of the `SessionManager` class, including tests for timeout and cancellation scenarios. The solution file and a new project settings file have been updated accordingly. The `SessionManager` class now handles `OperationCanceledException` by rethrowing the exception. --- .../Blazor/State/SessionManager.cs | 4 +- .../Csla.Blazor.WebAssembly.Tests.csproj | 36 +++ .../SessionManagerTests.cs | 205 ++++++++++++++++++ .../State/SessionManager.cs | 79 +++++-- Source/Csla.Blazor/State/StateManager.cs | 12 +- Source/Csla/State/ISessionManager.cs | 4 +- Source/csla.test.sln | 42 ++++ 7 files changed, 358 insertions(+), 24 deletions(-) create mode 100644 Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj create mode 100644 Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs diff --git a/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs b/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs index 6d968673c9..fba25e0108 100644 --- a/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs +++ b/Source/Csla.AspNetCore/Blazor/State/SessionManager.cs @@ -79,8 +79,8 @@ public void PurgeSessions(TimeSpan expiration) } // wasm client-side methods - Task ISessionManager.RetrieveSession() => throw new NotImplementedException(); + Task ISessionManager.RetrieveSession(TimeSpan timeout) => throw new NotImplementedException(); Session ISessionManager.GetCachedSession() => throw new NotImplementedException(); - Task ISessionManager.SendSession() => throw new NotImplementedException(); + Task ISessionManager.SendSession(TimeSpan timeout) => throw new NotImplementedException(); } } diff --git a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj new file mode 100644 index 0000000000..107042eeeb --- /dev/null +++ b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + false + + Library + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs new file mode 100644 index 0000000000..1376cd0ca9 --- /dev/null +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -0,0 +1,205 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading; +using System.Threading.Tasks; +using Csla.Blazor.WebAssembly.State; +using Csla.State; +using Moq; +using System.Net.Http; +using Csla.Blazor.WebAssembly.Configuration; +using Csla.Core; +using Csla.Runtime; +using Moq.Protected; +using System.Net; +using Csla.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Csla.Serialization.Mobile; +using Microsoft.AspNetCore.Components.Authorization; + +namespace Csla.Test.State +{ + [TestClass] + public class SessionManagerTests + { + private SessionManager _sessionManager; + + [TestInitialize] + public void Initialize() + { + var mockServiceProvider = new Mock(); + + // Mock AuthenticationStateProvider + var mockAuthStateProvider = new Mock(); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); + + var session = new SessionMessage + { + // Set properties here + // For example: + Principal = new Security.CslaClaimsPrincipal() { }, + Session = [] + }; + + // Mock ISerializationFormatter + var mockFormatter = new Mock(); + mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(session); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); + + var mockActivator = new Mock(); + mockActivator.Setup(x => x.CreateInstance(It.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter)))).Returns(mockFormatter.Object); + mockActivator.Setup(x => x.InitializeInstance(It.IsAny())); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); + + // Mock IServiceProvider + + // Mock IContextManager + var mockContextManager = new Mock(); + mockContextManager.Setup(x => x.IsValid).Returns(true); + + // Mock IContextManagerLocal + var mockLocalContextManager = new Mock(); + + // Mock IServiceProvider + mockServiceProvider.Setup(x => x.GetService(typeof(IRuntimeInfo))).Returns(new RuntimeInfo()); + + // Mock IEnumerable + var mockContextManagerList = new List { mockContextManager.Object }; + + // Mock ApplicationContextAccessor + var mockApplicationContextAccessor = new Mock(mockContextManagerList, mockLocalContextManager.Object, mockServiceProvider.Object); + + var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); + + + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(session, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); + } + + private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) + { + var handlerMock = new Mock(MockBehavior.Strict); + handlerMock + .Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + // prepare the expected response of the mocked http call + .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => + { + + + if (cancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(cancellationToken); + } + else + { + return new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), + }; + } + }) + .Verifiable(); + + // use real http client with mocked handler here + var httpClient = new HttpClient(handlerMock.Object) + { + BaseAddress = new Uri("http://test.com/"), + }; + return httpClient; + } + + private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext) + { + var info = new MobileFormatter(_applicationContext).SerializeToDTO(session); + + var ms = new MemoryStream(); + + new CslaBinaryWriter(_applicationContext).Write(ms, info); + + ms.Position = 0; + var array = ms.ToArray(); + return array; + } + + [TestMethod] + public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() + { + var timeout = TimeSpan.FromHours(1); + await _sessionManager.RetrieveSession(timeout); + } + + [TestMethod] + public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() + { + var timeout = TimeSpan.Zero; + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + + } + + [TestMethod] + public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() + { + var cts = new CancellationTokenSource(); + await _sessionManager.RetrieveSession(cts.Token); + } + + [TestMethod] + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + } + + [TestMethod] + public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() + { + var timeout = TimeSpan.FromHours(1); + await _sessionManager.SendSession(timeout); + } + + [TestMethod] + public async Task SendSession_WithZeroTimeout_ShouldNotThrowException() + { + var timeout = TimeSpan.Zero; + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + + } + + [TestMethod] + public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() + { + var cts = new CancellationTokenSource(); + await _sessionManager.SendSession(cts.Token); + } + + [TestMethod] + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + } + + + [TestMethod] + public void RetrieveCAchedSessionSession() + { + + _sessionManager.GetCachedSession(); + } + } +} diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index b8c61c0328..4f2fcb08dc 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -44,7 +44,17 @@ public Session GetCachedSession() /// the web server to the wasm client /// if SyncContextWithServer is true. /// - public async Task RetrieveSession() + public Task RetrieveSession(TimeSpan timeout) + { + return RetrieveSession(GetCancellationToken(timeout)); + } + + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + public async Task RetrieveSession(CancellationToken ct) { if (_options.SyncContextWithServer) { @@ -53,25 +63,35 @@ public async Task RetrieveSession() lastTouched = _session.LastTouched; var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - var stateResult = await client.GetFromJsonAsync(url); - if (stateResult.ResultStatus == ResultStatuses.Success) + try { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) + var stateResult = await client.GetFromJsonAsync(url, ct); + + + if (stateResult.ResultStatus == ResultStatuses.Success) { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) + { + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + { + provider.SetPrincipal(message.Principal); + } + } + else // NoUpdates { - provider.SetPrincipal(message.Principal); + _session = GetSession(); } } - else // NoUpdates + catch(OperationCanceledException) { _session = GetSession(); + throw; } } else @@ -86,7 +106,29 @@ public async Task RetrieveSession() /// the wasm client to the web server /// if SyncContextWithServer is true. /// - public async Task SendSession() + public Task SendSession(TimeSpan timeout) + { + return SendSession(GetCancellationToken(timeout)); + } + + private static CancellationToken GetCancellationToken(TimeSpan timeout) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(timeout); + if (timeout <= TimeSpan.Zero) + { + cts.Cancel(); + } + + return cts.Token; + } + + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + public async Task SendSession(CancellationToken ct) { _session.Touch(); if (_options.SyncContextWithServer) @@ -95,7 +137,14 @@ public async Task SendSession() var buffer = new MemoryStream(); formatter.Serialize(buffer, _session); buffer.Position = 0; - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray()); + try + { + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); + } + catch(OperationCanceledException ) + { + throw; + } } } diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 01cb84dc41..576346efe7 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -39,12 +39,14 @@ public Task InitializeAsync(TimeSpan timeout) /// Get state from cache. /// /// Time to wait before timing out - private async Task GetState(TimeSpan timeout) + private async Task GetState(TimeSpan timeout) { - Session session; + Session session = null; var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) - session = await _sessionManager.RetrieveSession(); + session = await _sessionManager.RetrieveSession(timeout); + + return session; } /// @@ -57,12 +59,12 @@ private async Task GetState(TimeSpan timeout) /// at which you know the user is navigating to another /// page. /// - public void SaveState() + public void SaveState(TimeSpan timeout) { var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) { - _sessionManager.SendSession(); + _sessionManager.SendSession(timeout); } } } diff --git a/Source/Csla/State/ISessionManager.cs b/Source/Csla/State/ISessionManager.cs index 6ce61594d0..c089102e62 100644 --- a/Source/Csla/State/ISessionManager.cs +++ b/Source/Csla/State/ISessionManager.cs @@ -18,7 +18,7 @@ public interface ISessionManager /// Retrieves the current user's session from /// the web server to the wasm client. /// - Task RetrieveSession(); + Task RetrieveSession(TimeSpan timeout); /// /// Gets the current user's session from the cache. /// @@ -27,7 +27,7 @@ public interface ISessionManager /// Sends the current user's session from /// the wasm client to the web server. /// - Task SendSession(); + Task SendSession(TimeSpan timeout); #endregion #region Server diff --git a/Source/csla.test.sln b/Source/csla.test.sln index 77fc837132..3db51aa411 100644 --- a/Source/csla.test.sln +++ b/Source/csla.test.sln @@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphMergerTest.Business", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphMergerTest.BusinessTests", "GraphMergerTest\GraphMergerTest.BusinessTests\GraphMergerTest.BusinessTests.csproj", "{FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Blazor.WebAssembly.Tests", "Csla.Blazor.WebAssembly.Tests\Csla.Blazor.WebAssembly.Tests.csproj", "{430DDCF1-1570-4193-9B1E-774303F6FC34}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1006,6 +1008,46 @@ Global {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x64.Build.0 = Debug|Any CPU {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x86.ActiveCfg = Debug|Any CPU {FE652EBA-94E7-4BB2-99C8-18B0382E0D9A}.Testing|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Debug|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Debug|x86.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|Any CPU.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|ARM.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|ARM.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x64.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x64.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x86.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Mono Release|x86.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|Any CPU.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|ARM.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|ARM.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x64.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x64.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x86.ActiveCfg = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Release|x86.Build.0 = Release|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|Any CPU.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|Any CPU.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|ARM.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|ARM.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x64.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x64.Build.0 = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x86.ActiveCfg = Debug|Any CPU + {430DDCF1-1570-4193-9B1E-774303F6FC34}.Testing|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d54e552a5c96f5a268f6692d81cd716693fac91a Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 17:40:23 -0300 Subject: [PATCH 2/9] `Refactor SessionManager and StateManager for improved session handling` Refactored `SessionManagerTests.cs` and `SessionManager.cs` to improve session handling. The `SessionMessage` object has been changed from a local variable to a class-level variable, allowing it to be accessed across different methods. The `RetrieveSession` and `SendSession` methods have been updated to return the session and assert that the returned session is equal to `SessionValue.Session`. The exception type expected in these methods with zero timeout and cancelled cancellation token has been changed from `TaskCanceledException` to `TimeoutException`. The `GetCancellationToken` method has been simplified to create a `CancellationTokenSource` with a timeout directly. In `StateManager.cs`, the `GetState` method has been updated to not return a `Session` object, instead, it just retrieves the session without assigning it to a variable. --- .../SessionManagerTests.cs | 33 +++++++++++-------- .../State/SessionManager.cs | 31 ++++++++--------- Source/Csla.Blazor/State/StateManager.cs | 7 ++-- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 1376cd0ca9..a30751b77d 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -22,6 +22,7 @@ namespace Csla.Test.State public class SessionManagerTests { private SessionManager _sessionManager; + private SessionMessage SessionValue; [TestInitialize] public void Initialize() @@ -34,7 +35,7 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); - var session = new SessionMessage + SessionValue = new SessionMessage { // Set properties here // For example: @@ -45,7 +46,7 @@ public void Initialize() // Mock ISerializationFormatter var mockFormatter = new Mock(); mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(session); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(SessionValue); // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); @@ -78,7 +79,7 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _sessionManager = new SessionManager(_applicationContext, GetHttpClient(session, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); } @@ -138,14 +139,15 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - await _sessionManager.RetrieveSession(timeout); + var session = await _sessionManager.RetrieveSession(timeout); + Assert.AreEqual(session, SessionValue.Session); } [TestMethod] - public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() + public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); } @@ -153,15 +155,17 @@ public async Task RetrieveSession_WithZeroTimeout_ShouldNotThrowException() public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - await _sessionManager.RetrieveSession(cts.Token); + var session = await _sessionManager.RetrieveSession(cts.Token); + Assert.AreEqual(session, SessionValue.Session); + } [TestMethod] - public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] @@ -169,13 +173,15 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); await _sessionManager.SendSession(timeout); + + Assert.IsTrue(true); } [TestMethod] - public async Task SendSession_WithZeroTimeout_ShouldNotThrowException() + public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); } @@ -184,14 +190,15 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException { var cts = new CancellationTokenSource(); await _sessionManager.SendSession(cts.Token); + Assert.IsTrue(true); } [TestMethod] - public async Task SendSession_WithCancelledCancellationToken_ShouldThrowOperationCanceledException() + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index 4f2fcb08dc..a43f5494fd 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -13,6 +13,8 @@ using Csla.State; using Microsoft.AspNetCore.Components.Authorization; using Csla.Blazor.State.Messages; +using System.Reflection; +using System.Threading; namespace Csla.Blazor.WebAssembly.State { @@ -49,12 +51,12 @@ public Task RetrieveSession(TimeSpan timeout) return RetrieveSession(GetCancellationToken(timeout)); } - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(CancellationToken ct) + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + public async Task RetrieveSession(CancellationToken ct) { if (_options.SyncContextWithServer) { @@ -88,10 +90,9 @@ public async Task RetrieveSession(CancellationToken ct) _session = GetSession(); } } - catch(OperationCanceledException) + catch(OperationCanceledException ocex) { - _session = GetSession(); - throw; + throw new TimeoutException ($"{this.GetType().FullName}.RetrieveSession.", ocex); } } else @@ -113,13 +114,7 @@ public Task SendSession(TimeSpan timeout) private static CancellationToken GetCancellationToken(TimeSpan timeout) { - var cts = new CancellationTokenSource(); - cts.CancelAfter(timeout); - if (timeout <= TimeSpan.Zero) - { - cts.Cancel(); - } - + var cts = new CancellationTokenSource(timeout); return cts.Token; } @@ -141,9 +136,9 @@ public async Task SendSession(CancellationToken ct) { await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); } - catch(OperationCanceledException ) + catch(OperationCanceledException ocex) { - throw; + throw new TimeoutException($"{this.GetType().FullName}.SendSession.", ocex); } } } diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 576346efe7..685ea0b28c 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -39,14 +39,11 @@ public Task InitializeAsync(TimeSpan timeout) /// Get state from cache. /// /// Time to wait before timing out - private async Task GetState(TimeSpan timeout) + private async Task GetState(TimeSpan timeout) { - Session session = null; var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) - session = await _sessionManager.RetrieveSession(timeout); - - return session; + _ = await _sessionManager.RetrieveSession(timeout); } /// From 911eb96d0b634d8960c56d3f35e57b7558cc0fea Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Wed, 1 May 2024 17:47:21 -0300 Subject: [PATCH 3/9] Refactor SessionManagerTests and remove certain test methods This commit includes a significant refactoring of the `SessionManagerTests.cs` file. The `GetHttpClient` method has been simplified by removing the creation of a new `MemoryStream` instance and the use of `CslaBinaryWriter`. The stream's position is no longer reset to 0, and the array is directly obtained from the stream. Several test methods have been removed, including `RetrieveSession_WithTimeoutValue_ShouldNotThrowException`, `RetrieveSession_WithValidCancellationToken_ShouldNotThrowException`, and `SendSession_WithZeroTimeout_ShouldThrowTimeoutException`. These tests were checking for specific exceptions or session values. The `SendSession_WithTimeoutValue_ShouldNotThrowException` test method has been modified by removing the assertion that was previously checking if the operation is successful. Lastly, the `RetrieveCachedSessionSession` method has been modified by removing the call to `GetCachedSession` method of `_sessionManager`. --- .../Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index a30751b77d..0a938b3b99 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -125,11 +125,8 @@ private static HttpClient GetHttpClient(SessionMessage session, ApplicationConte private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext) { var info = new MobileFormatter(_applicationContext).SerializeToDTO(session); - var ms = new MemoryStream(); - new CslaBinaryWriter(_applicationContext).Write(ms, info); - ms.Position = 0; var array = ms.ToArray(); return array; @@ -148,7 +145,6 @@ public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); - } [TestMethod] @@ -157,7 +153,6 @@ public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowExcep var cts = new CancellationTokenSource(); var session = await _sessionManager.RetrieveSession(cts.Token); Assert.AreEqual(session, SessionValue.Session); - } [TestMethod] @@ -173,7 +168,6 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); await _sessionManager.SendSession(timeout); - Assert.IsTrue(true); } @@ -182,7 +176,6 @@ public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); - } [TestMethod] @@ -205,7 +198,6 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutE [TestMethod] public void RetrieveCAchedSessionSession() { - _sessionManager.GetCachedSession(); } } From 2b3521943062d18fead86891b892b2f55f0d5f13 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Thu, 2 May 2024 17:43:40 -0300 Subject: [PATCH 4/9] Refactor SessionManager and update related tests Updated `SessionManager.cs` methods `RetrieveSession` and `SendSession` to handle `TaskCanceledException` internally and rethrow as `TimeoutException`. Simplified `SendSession` by removing exception handling and refactored `RetrieveSession` to move `stateResult` handling outside of try-catch block. Renamed test methods in `SessionManagerTests.cs` to reflect these changes and updated expected exception type. --- .../SessionManagerTests.cs | 8 +-- .../State/SessionManager.cs | 67 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 0a938b3b99..4ec715c838 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -156,11 +156,11 @@ public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowExcep } [TestMethod] - public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() + public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] @@ -187,11 +187,11 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException } [TestMethod] - public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTimeoutException() + public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index a43f5494fd..e68aa8d938 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -46,9 +46,16 @@ public Session GetCachedSession() /// the web server to the wasm client /// if SyncContextWithServer is true. /// - public Task RetrieveSession(TimeSpan timeout) + public async Task RetrieveSession(TimeSpan timeout) { - return RetrieveSession(GetCancellationToken(timeout)); + try + { + return await RetrieveSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); + } } /// @@ -64,35 +71,25 @@ public async Task RetrieveSession(CancellationToken ct) if (_session != null) lastTouched = _session.LastTouched; var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - - try + var stateResult = await client.GetFromJsonAsync(url, ct); + if (stateResult.ResultStatus == ResultStatuses.Success) { - var stateResult = await client.GetFromJsonAsync(url, ct); - - - if (stateResult.ResultStatus == ResultStatuses.Success) + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) - { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) - { - provider.SetPrincipal(message.Principal); - } - } - else // NoUpdates + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) { - _session = GetSession(); + provider.SetPrincipal(message.Principal); } } - catch(OperationCanceledException ocex) + else // NoUpdates { - throw new TimeoutException ($"{this.GetType().FullName}.RetrieveSession.", ocex); + _session = GetSession(); } } else @@ -107,9 +104,16 @@ public async Task RetrieveSession(CancellationToken ct) /// the wasm client to the web server /// if SyncContextWithServer is true. /// - public Task SendSession(TimeSpan timeout) + public async Task SendSession(TimeSpan timeout) { - return SendSession(GetCancellationToken(timeout)); + try + { + await SendSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); + } } private static CancellationToken GetCancellationToken(TimeSpan timeout) @@ -132,14 +136,7 @@ public async Task SendSession(CancellationToken ct) var buffer = new MemoryStream(); formatter.Serialize(buffer, _session); buffer.Position = 0; - try - { - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); - } - catch(OperationCanceledException ocex) - { - throw new TimeoutException($"{this.GetType().FullName}.SendSession.", ocex); - } + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); } } From 33a0c175f7069472b1506a9fc0c4d99192f249b7 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Fri, 3 May 2024 18:59:15 -0300 Subject: [PATCH 5/9] `Convert SaveState to async in StateManager.cs` ` ` `The SaveState method in StateManager.cs has been converted from a synchronous method to an asynchronous one. This is indicated by the addition of the async keyword and the change in return type from void to Task. Additionally, the call to _sessionManager.SendSession(timeout) within the SaveState method has been updated to use the await keyword, making this method call awaited, in line with the change to make SaveState an asynchronous method.` --- Source/Csla.Blazor/State/StateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Csla.Blazor/State/StateManager.cs b/Source/Csla.Blazor/State/StateManager.cs index 685ea0b28c..8631eeb92c 100644 --- a/Source/Csla.Blazor/State/StateManager.cs +++ b/Source/Csla.Blazor/State/StateManager.cs @@ -56,12 +56,12 @@ private async Task GetState(TimeSpan timeout) /// at which you know the user is navigating to another /// page. /// - public void SaveState(TimeSpan timeout) + public async Task SaveState(TimeSpan timeout) { var isBrowser = OperatingSystem.IsBrowser(); if (isBrowser) { - _sessionManager.SendSession(timeout); + await _sessionManager.SendSession(timeout); } } } From ce844449c5b3a7d98a4c0ecda951b023bb17cd1e Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 12:07:32 -0300 Subject: [PATCH 6/9] Updated SessionManager and its tests for async operations and better error handling In this commit, several updates were made to the `SessionManager.cs` and `SessionManagerTests.cs` files. The variable `_sessionManager` was renamed to `_SessionManager` in `SessionManagerTests.cs`. The `Initialize` method was converted to an asynchronous method, and the `RetrieveSession` method call in it was updated to use `await`. XML comments were added to the `RetrieveSession`, `SendSession`, and `GetSession` methods in `SessionManager.cs` for better code documentation. The `RetrieveSession` and `SendSession` methods were updated to handle `TaskCanceledException` and throw a `TimeoutException` with a custom message. The `GetSession` method was updated to handle the case where `_session` is `null`, creating and returning a new `Session` object in this case. The `SendSession` method was updated to serialize the `_session` object and send it to the server if `SyncContextWithServer` is `true`. Finally, the `RetrieveSession` method was updated to retrieve the session from the server if `SyncContextWithServer` is `true`, deserializing and storing the retrieved session in `_session` or calling `GetSession` to get or create a new session if the retrieval is unsuccessful. --- .../SessionManagerTests.cs | 30 ++- .../State/SessionManager.cs | 215 +++++++++--------- 2 files changed, 125 insertions(+), 120 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 4ec715c838..def4b1b213 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -21,11 +21,11 @@ namespace Csla.Test.State [TestClass] public class SessionManagerTests { - private SessionManager _sessionManager; + private SessionManager _SessionManager; private SessionMessage SessionValue; [TestInitialize] - public void Initialize() + public async Task Initialize() { var mockServiceProvider = new Mock(); @@ -58,8 +58,6 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); - // Mock IServiceProvider - // Mock IContextManager var mockContextManager = new Mock(); mockContextManager.Setup(x => x.IsValid).Returns(true); @@ -79,8 +77,8 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _sessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); - _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); + _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -97,8 +95,6 @@ private static HttpClient GetHttpClient(SessionMessage session, ApplicationConte // prepare the expected response of the mocked http call .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => { - - if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); @@ -136,7 +132,7 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - var session = await _sessionManager.RetrieveSession(timeout); + var session = await _SessionManager.RetrieveSession(timeout); Assert.AreEqual(session, SessionValue.Session); } @@ -144,14 +140,14 @@ public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(timeout)); } [TestMethod] public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - var session = await _sessionManager.RetrieveSession(cts.Token); + var session = await _SessionManager.RetrieveSession(cts.Token); Assert.AreEqual(session, SessionValue.Session); } @@ -160,14 +156,14 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(cts.Token)); } [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - await _sessionManager.SendSession(timeout); + await _SessionManager.SendSession(timeout); Assert.IsTrue(true); } @@ -175,14 +171,14 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); } [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - await _sessionManager.SendSession(cts.Token); + await _SessionManager.SendSession(cts.Token); Assert.IsTrue(true); } @@ -191,14 +187,14 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanc { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); } [TestMethod] public void RetrieveCAchedSessionSession() { - _sessionManager.GetCachedSession(); + _SessionManager.GetCachedSession(); } } } diff --git a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs index e68aa8d938..36458f3ab4 100644 --- a/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs +++ b/Source/Csla.Blazor.WebAssembly/State/SessionManager.cs @@ -41,80 +41,86 @@ public Session GetCachedSession() return _session; } - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(TimeSpan timeout) - { - try - { - return await RetrieveSession(GetCancellationToken(timeout)); - } - catch (TaskCanceledException tcex) - { - throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); - } - } - - /// - /// Retrieves the current user's session from - /// the web server to the wasm client - /// if SyncContextWithServer is true. - /// - public async Task RetrieveSession(CancellationToken ct) - { - if (_options.SyncContextWithServer) - { - long lastTouched = 0; - if (_session != null) - lastTouched = _session.LastTouched; - var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; - var stateResult = await client.GetFromJsonAsync(url, ct); - if (stateResult.ResultStatus == ResultStatuses.Success) + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + /// The timeout duration for the operation. + /// The retrieved session. + public async Task RetrieveSession(TimeSpan timeout) { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(stateResult.SessionData) - { - Position = 0 - }; - var message = (SessionMessage)formatter.Deserialize(buffer); - _session = message.Session; - if (message.Principal is not null && - ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) - { - provider.SetPrincipal(message.Principal); - } + try + { + return await RetrieveSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(RetrieveSession)}.", tcex); + } } - else // NoUpdates + + /// + /// Retrieves the current user's session from + /// the web server to the wasm client + /// if SyncContextWithServer is true. + /// + /// The cancellation token. + /// The retrieved session. + public async Task RetrieveSession(CancellationToken ct) { - _session = GetSession(); + if (_options.SyncContextWithServer) + { + long lastTouched = 0; + if (_session != null) + lastTouched = _session.LastTouched; + var url = $"{_options.StateControllerName}?lastTouched={lastTouched}"; + var stateResult = await client.GetFromJsonAsync(url, ct); + if (stateResult.ResultStatus == ResultStatuses.Success) + { + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(stateResult.SessionData) + { + Position = 0 + }; + var message = (SessionMessage)formatter.Deserialize(buffer); + _session = message.Session; + if (message.Principal is not null && + ApplicationContext.GetRequiredService() is CslaAuthenticationStateProvider provider) + { + provider.SetPrincipal(message.Principal); + } + } + else // NoUpdates + { + _session = GetSession(); + } + } + else + { + _session = GetSession(); + } + return _session; } - } - else - { - _session = GetSession(); - } - return _session; - } - /// - /// Sends the current user's session from - /// the wasm client to the web server - /// if SyncContextWithServer is true. - /// - public async Task SendSession(TimeSpan timeout) - { - try - { - await SendSession(GetCancellationToken(timeout)); - } - catch (TaskCanceledException tcex) - { - throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); - } - } + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + /// The timeout duration for the operation. + /// A task representing the asynchronous operation. + public async Task SendSession(TimeSpan timeout) + { + try + { + await SendSession(GetCancellationToken(timeout)); + } + catch (TaskCanceledException tcex) + { + throw new TimeoutException($"{this.GetType().FullName}.{nameof(SendSession)}.", tcex); + } + } private static CancellationToken GetCancellationToken(TimeSpan timeout) { @@ -122,42 +128,45 @@ private static CancellationToken GetCancellationToken(TimeSpan timeout) return cts.Token; } - /// - /// Sends the current user's session from - /// the wasm client to the web server - /// if SyncContextWithServer is true. - /// - public async Task SendSession(CancellationToken ct) - { - _session.Touch(); - if (_options.SyncContextWithServer) - { - var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); - var buffer = new MemoryStream(); - formatter.Serialize(buffer, _session); - buffer.Position = 0; - await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); - } - } + /// + /// Sends the current user's session from + /// the wasm client to the web server + /// if SyncContextWithServer is true. + /// + /// The cancellation token. + /// A task representing the asynchronous operation. + public async Task SendSession(CancellationToken ct) + { + _session.Touch(); + if (_options.SyncContextWithServer) + { + var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext); + var buffer = new MemoryStream(); + formatter.Serialize(buffer, _session); + buffer.Position = 0; + await client.PutAsJsonAsync(_options.StateControllerName, buffer.ToArray(), ct); + } + } - /// - /// Gets or creates the session data. - /// - private Session GetSession() - { - Session result; - if (_session != null) - { - result = _session; - } - else - { - result = []; - result.Touch(); - } - return result; - } + /// + /// Gets or creates the session data. + /// + /// The session data. + private Session GetSession() + { + Session result; + if (_session != null) + { + result = _session; + } + else + { + result = []; + result.Touch(); + } + return result; + } // server-side methods Session ISessionManager.GetSession() => throw new NotImplementedException(); From 73e9986a5c247ce6518f92e3d3a2e652e2ee10d5 Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 12:17:23 -0300 Subject: [PATCH 7/9] Subject: Refactored test methods and updated session retrieval Refactored the `Initialize` method in `SessionManagerTests.cs` to be synchronous and removed the `RetrieveSession` call. The `RetrieveSession` call has been added to four test methods to ensure session retrieval before each test. Renamed and converted `RetrieveCAchedSessionSession` to an asynchronous method, adding a `RetrieveSession` call and an assertion for non-null cached sessions. Added a new test method `RetrieveNullCachedSessionSession` to assert null cached sessions. --- .../SessionManagerTests.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index def4b1b213..12dcc03c44 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -25,7 +25,7 @@ public class SessionManagerTests private SessionMessage SessionValue; [TestInitialize] - public async Task Initialize() + public void Initialize() { var mockServiceProvider = new Mock(); @@ -78,7 +78,6 @@ public async Task Initialize() _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -162,6 +161,8 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var timeout = TimeSpan.FromHours(1); await _SessionManager.SendSession(timeout); Assert.IsTrue(true); @@ -170,6 +171,8 @@ public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() [TestMethod] public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var timeout = TimeSpan.Zero; await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); } @@ -177,6 +180,8 @@ public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var cts = new CancellationTokenSource(); await _SessionManager.SendSession(cts.Token); Assert.IsTrue(true); @@ -185,6 +190,8 @@ public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException [TestMethod] public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + var cts = new CancellationTokenSource(); cts.Cancel(); await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); @@ -192,9 +199,18 @@ public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanc [TestMethod] - public void RetrieveCAchedSessionSession() + public async Task RetrieveCachedSessionSession() + { + await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + + var session = _SessionManager.GetCachedSession(); + Assert.IsNotNull(session); + } + [TestMethod] + public void RetrieveNullCachedSessionSession() { - _SessionManager.GetCachedSession(); + var session = _SessionManager.GetCachedSession(); + Assert.IsNull(session); } } } From 5f59d4a50f8c6d9f6827520d87caf8df176f4d1e Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Sat, 4 May 2024 18:09:10 -0300 Subject: [PATCH 8/9] Refactor variable names to follow C# naming convention Updated variable names `_SessionManager` and `SessionValue` to `_sessionManager` and `_sessionValue` respectively, to adhere to the common C# naming convention for private fields. All instances of these variables in the code, including in the `Initialize()`, `Deserialize()`, `RetrieveSession()`, `SendSession()`, and `GetCachedSession()` methods, as well as in test assertions, have been updated accordingly. --- .../SessionManagerTests.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 12dcc03c44..81ff7ab43f 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -21,8 +21,8 @@ namespace Csla.Test.State [TestClass] public class SessionManagerTests { - private SessionManager _SessionManager; - private SessionMessage SessionValue; + private SessionManager _sessionManager; + private SessionMessage _sessionValue; [TestInitialize] public void Initialize() @@ -35,7 +35,7 @@ public void Initialize() // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); - SessionValue = new SessionMessage + _sessionValue = new SessionMessage { // Set properties here // For example: @@ -46,7 +46,7 @@ public void Initialize() // Mock ISerializationFormatter var mockFormatter = new Mock(); mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(SessionValue); + mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(_sessionValue); // Mock IServiceProvider mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); @@ -77,7 +77,7 @@ public void Initialize() var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); - _SessionManager = new SessionManager(_applicationContext, GetHttpClient(SessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); + _sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) @@ -131,23 +131,23 @@ private static byte[] GetSession(SessionMessage session, ApplicationContext _app public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException() { var timeout = TimeSpan.FromHours(1); - var session = await _SessionManager.RetrieveSession(timeout); - Assert.AreEqual(session, SessionValue.Session); + var session = await _sessionManager.RetrieveSession(timeout); + Assert.AreEqual(session, _sessionValue.Session); } [TestMethod] public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException() { var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(timeout)); } [TestMethod] public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException() { var cts = new CancellationTokenSource(); - var session = await _SessionManager.RetrieveSession(cts.Token); - Assert.AreEqual(session, SessionValue.Session); + var session = await _sessionManager.RetrieveSession(cts.Token); + Assert.AreEqual(session, _sessionValue.Session); } [TestMethod] @@ -155,61 +155,61 @@ public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTask { var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _SessionManager.RetrieveSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.RetrieveSession(cts.Token)); } [TestMethod] public async Task SendSession_WithTimeoutValue_ShouldNotThrowException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var timeout = TimeSpan.FromHours(1); - await _SessionManager.SendSession(timeout); + await _sessionManager.SendSession(timeout); Assert.IsTrue(true); } [TestMethod] public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var timeout = TimeSpan.Zero; - await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(timeout)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(timeout)); } [TestMethod] public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var cts = new CancellationTokenSource(); - await _SessionManager.SendSession(cts.Token); + await _sessionManager.SendSession(cts.Token); Assert.IsTrue(true); } [TestMethod] public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); var cts = new CancellationTokenSource(); cts.Cancel(); - await Assert.ThrowsExceptionAsync(() => _SessionManager.SendSession(cts.Token)); + await Assert.ThrowsExceptionAsync(() => _sessionManager.SendSession(cts.Token)); } [TestMethod] public async Task RetrieveCachedSessionSession() { - await _SessionManager.RetrieveSession(TimeSpan.FromHours(1)); + await _sessionManager.RetrieveSession(TimeSpan.FromHours(1)); - var session = _SessionManager.GetCachedSession(); + var session = _sessionManager.GetCachedSession(); Assert.IsNotNull(session); } [TestMethod] public void RetrieveNullCachedSessionSession() { - var session = _SessionManager.GetCachedSession(); + var session = _sessionManager.GetCachedSession(); Assert.IsNull(session); } } From b6c01b9ffac17e7fc3e80b1a95b44a5cee34aa7c Mon Sep 17 00:00:00 2001 From: Luiz Fernando Bicalho Date: Tue, 7 May 2024 17:31:29 -0300 Subject: [PATCH 9/9] Switch from Moq to NSubstitute in Csla.Blazor.WebAssembly.Tests This commit represents a significant shift in the mocking framework used for unit testing in the `Csla.Blazor.WebAssembly.Tests.csproj` project. The `Moq` package has been replaced with `NSubstitute` in the project file and throughout the `SessionManagerTests.cs` file. This includes changes in the way mocks are created, set up, and how return values are specified for mocked methods and properties. Additionally, a new `TestHttpMessageHandler` class has been added to `SessionManagerTests.cs` to mock the behavior of an `HttpClient`. The `GetHttpClient` method has been updated to use this new class, aligning with the switch from `Moq` to `NSubstitute`. --- .../Csla.Blazor.WebAssembly.Tests.csproj | 2 +- .../SessionManagerTests.cs | 83 ++++++++----------- 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj index 107042eeeb..8af20c9d4a 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj +++ b/Source/Csla.Blazor.WebAssembly.Tests/Csla.Blazor.WebAssembly.Tests.csproj @@ -12,13 +12,13 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs index 81ff7ab43f..8788766da0 100644 --- a/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs +++ b/Source/Csla.Blazor.WebAssembly.Tests/SessionManagerTests.cs @@ -4,17 +4,16 @@ using System.Threading.Tasks; using Csla.Blazor.WebAssembly.State; using Csla.State; -using Moq; using System.Net.Http; using Csla.Blazor.WebAssembly.Configuration; using Csla.Core; using Csla.Runtime; -using Moq.Protected; using System.Net; using Csla.Serialization; using System.Runtime.Serialization.Formatters.Binary; using Csla.Serialization.Mobile; using Microsoft.AspNetCore.Components.Authorization; +using NSubstitute; namespace Csla.Test.State { @@ -27,13 +26,13 @@ public class SessionManagerTests [TestInitialize] public void Initialize() { - var mockServiceProvider = new Mock(); + var mockServiceProvider = Substitute.For(); // Mock AuthenticationStateProvider - var mockAuthStateProvider = new Mock(); + var mockAuthStateProvider = Substitute.For(); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(AuthenticationStateProvider))).Returns(mockAuthStateProvider.Object); + mockServiceProvider.GetService(typeof(AuthenticationStateProvider)).Returns(mockAuthStateProvider); _sessionValue = new SessionMessage { @@ -44,73 +43,59 @@ public void Initialize() }; // Mock ISerializationFormatter - var mockFormatter = new Mock(); - mockFormatter.Setup(x => x.Serialize(It.IsAny(), It.IsAny())); - mockFormatter.Setup(x => x.Deserialize(It.IsAny())).Returns(_sessionValue); + var mockFormatter = Substitute.For(); + mockFormatter.Serialize(Arg.Any(), Arg.Any()); + mockFormatter.Deserialize(Arg.Any()).Returns(_sessionValue); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter.Object); + mockServiceProvider.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter)).Returns(mockFormatter); - var mockActivator = new Mock(); - mockActivator.Setup(x => x.CreateInstance(It.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter)))).Returns(mockFormatter.Object); - mockActivator.Setup(x => x.InitializeInstance(It.IsAny())); + var mockActivator = Substitute.For(); + mockActivator.CreateInstance(Arg.Is(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter); + mockActivator.InitializeInstance(Arg.Any()); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(Csla.Server.IDataPortalActivator))).Returns(mockActivator.Object); + mockServiceProvider.GetService(typeof(Csla.Server.IDataPortalActivator)).Returns(mockActivator); // Mock IContextManager - var mockContextManager = new Mock(); - mockContextManager.Setup(x => x.IsValid).Returns(true); + var mockContextManager = Substitute.For(); + mockContextManager.IsValid.Returns(true); // Mock IContextManagerLocal - var mockLocalContextManager = new Mock(); + var mockLocalContextManager = Substitute.For(); // Mock IServiceProvider - mockServiceProvider.Setup(x => x.GetService(typeof(IRuntimeInfo))).Returns(new RuntimeInfo()); + mockServiceProvider.GetService(typeof(IRuntimeInfo)).Returns(new RuntimeInfo()); // Mock IEnumerable - var mockContextManagerList = new List { mockContextManager.Object }; + var mockContextManagerList = new List { mockContextManager }; // Mock ApplicationContextAccessor - var mockApplicationContextAccessor = new Mock(mockContextManagerList, mockLocalContextManager.Object, mockServiceProvider.Object); - - var _applicationContext = new ApplicationContext(mockApplicationContextAccessor.Object); + var mockApplicationContextAccessor = Substitute.For(mockContextManagerList, mockLocalContextManager, mockServiceProvider); + var _applicationContext = new ApplicationContext(mockApplicationContextAccessor); _sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true }); } + public class TestHttpMessageHandler(SessionMessage session, ApplicationContext _applicationContext) : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var response = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), + }; + return Task.FromResult(response); + } + } private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext) { - var handlerMock = new Mock(MockBehavior.Strict); - handlerMock - .Protected() - // Setup the PROTECTED method to mock - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ) - // prepare the expected response of the mocked http call - .ReturnsAsync((HttpRequestMessage request, CancellationToken cancellationToken) => - { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(cancellationToken); - } - else - { - return new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"), - }; - } - }) - .Verifiable(); - + var handlerMock = new TestHttpMessageHandler(session,_applicationContext); // use real http client with mocked handler here - var httpClient = new HttpClient(handlerMock.Object) + var httpClient = new HttpClient(handlerMock) { BaseAddress = new Uri("http://test.com/"), };