Skip to content

Commit a60f4da

Browse files
luizfbicalholuizbicalhoaglrockfordlhotka
authored
Breaking change: Blazor StateManager does not use the provided timeout parameter (#3910)
* 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. * `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. * 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`. * 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. * `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.` * 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. * 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. * 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. * 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`. --------- Co-authored-by: Luiz Fernando Bicalho <[email protected]> Co-authored-by: Rockford Lhotka <[email protected]>
1 parent 579c84d commit a60f4da

File tree

7 files changed

+404
-76
lines changed

7 files changed

+404
-76
lines changed

Source/Csla.AspNetCore/Blazor/State/SessionManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ public void PurgeSessions(TimeSpan expiration)
7979
}
8080

8181
// wasm client-side methods
82-
Task<Session> ISessionManager.RetrieveSession() => throw new NotImplementedException();
82+
Task<Session> ISessionManager.RetrieveSession(TimeSpan timeout) => throw new NotImplementedException();
8383
Session ISessionManager.GetCachedSession() => throw new NotImplementedException();
84-
Task ISessionManager.SendSession() => throw new NotImplementedException();
84+
Task ISessionManager.SendSession(TimeSpan timeout) => throw new NotImplementedException();
8585
}
8686
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net8.0</TargetFrameworks>
5+
<IsPackable>false</IsPackable>
6+
<ApplicationIcon />
7+
<OutputType>Library</OutputType>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
15+
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
16+
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
17+
<PackageReference Include="coverlet.collector" Version="6.0.2">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
21+
<PackageReference Include="NSubstitute" Version="5.1.0" />
22+
</ItemGroup>
23+
24+
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
25+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<ProjectReference Include="..\Csla.AspNetCore\Csla.AspNetCore.csproj" />
30+
<ProjectReference Include="..\Csla.Blazor.WebAssembly\Csla.Blazor.WebAssembly.csproj" />
31+
<ProjectReference Include="..\Csla.Blazor\Csla.Blazor.csproj" />
32+
<ProjectReference Include="..\Csla.TestHelpers\Csla.TestHelpers.csproj" />
33+
<ProjectReference Include="..\Csla\Csla.csproj" />
34+
</ItemGroup>
35+
36+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using System;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Csla.Blazor.WebAssembly.State;
6+
using Csla.State;
7+
using System.Net.Http;
8+
using Csla.Blazor.WebAssembly.Configuration;
9+
using Csla.Core;
10+
using Csla.Runtime;
11+
using System.Net;
12+
using Csla.Serialization;
13+
using System.Runtime.Serialization.Formatters.Binary;
14+
using Csla.Serialization.Mobile;
15+
using Microsoft.AspNetCore.Components.Authorization;
16+
using NSubstitute;
17+
18+
namespace Csla.Test.State
19+
{
20+
[TestClass]
21+
public class SessionManagerTests
22+
{
23+
private SessionManager _sessionManager;
24+
private SessionMessage _sessionValue;
25+
26+
[TestInitialize]
27+
public void Initialize()
28+
{
29+
var mockServiceProvider = Substitute.For<IServiceProvider>();
30+
31+
// Mock AuthenticationStateProvider
32+
var mockAuthStateProvider = Substitute.For<AuthenticationStateProvider>();
33+
34+
// Mock IServiceProvider
35+
mockServiceProvider.GetService(typeof(AuthenticationStateProvider)).Returns(mockAuthStateProvider);
36+
37+
_sessionValue = new SessionMessage
38+
{
39+
// Set properties here
40+
// For example:
41+
Principal = new Security.CslaClaimsPrincipal() { },
42+
Session = []
43+
};
44+
45+
// Mock ISerializationFormatter
46+
var mockFormatter = Substitute.For<ISerializationFormatter>();
47+
mockFormatter.Serialize(Arg.Any<Stream>(), Arg.Any<object>());
48+
mockFormatter.Deserialize(Arg.Any<Stream>()).Returns(_sessionValue);
49+
50+
// Mock IServiceProvider
51+
mockServiceProvider.GetService(typeof(Csla.Serialization.Mobile.MobileFormatter)).Returns(mockFormatter);
52+
53+
var mockActivator = Substitute.For<Csla.Server.IDataPortalActivator>();
54+
mockActivator.CreateInstance(Arg.Is<Type>(t => t == typeof(Csla.Serialization.Mobile.MobileFormatter))).Returns(mockFormatter);
55+
mockActivator.InitializeInstance(Arg.Any<object>());
56+
57+
// Mock IServiceProvider
58+
mockServiceProvider.GetService(typeof(Csla.Server.IDataPortalActivator)).Returns(mockActivator);
59+
60+
// Mock IContextManager
61+
var mockContextManager = Substitute.For<IContextManager>();
62+
mockContextManager.IsValid.Returns(true);
63+
64+
// Mock IContextManagerLocal
65+
var mockLocalContextManager = Substitute.For<IContextManagerLocal>();
66+
67+
// Mock IServiceProvider
68+
mockServiceProvider.GetService(typeof(IRuntimeInfo)).Returns(new RuntimeInfo());
69+
70+
// Mock IEnumerable<IContextManager>
71+
var mockContextManagerList = new List<IContextManager> { mockContextManager };
72+
73+
// Mock ApplicationContextAccessor
74+
var mockApplicationContextAccessor = Substitute.For<ApplicationContextAccessor>(mockContextManagerList, mockLocalContextManager, mockServiceProvider);
75+
76+
var _applicationContext = new ApplicationContext(mockApplicationContextAccessor);
77+
78+
_sessionManager = new SessionManager(_applicationContext, GetHttpClient(_sessionValue, _applicationContext), new BlazorWebAssemblyConfigurationOptions { SyncContextWithServer = true });
79+
}
80+
81+
public class TestHttpMessageHandler(SessionMessage session, ApplicationContext _applicationContext) : HttpMessageHandler
82+
{
83+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
84+
{
85+
cancellationToken.ThrowIfCancellationRequested();
86+
var response = new HttpResponseMessage()
87+
{
88+
StatusCode = HttpStatusCode.OK,
89+
Content = new StringContent("{\"ResultStatus\":0, \"SessionData\":\"" + Convert.ToBase64String(GetSession(session, _applicationContext)) + "\"}"),
90+
};
91+
return Task.FromResult(response);
92+
}
93+
}
94+
private static HttpClient GetHttpClient(SessionMessage session, ApplicationContext _applicationContext)
95+
{
96+
var handlerMock = new TestHttpMessageHandler(session,_applicationContext);
97+
// use real http client with mocked handler here
98+
var httpClient = new HttpClient(handlerMock)
99+
{
100+
BaseAddress = new Uri("http://test.com/"),
101+
};
102+
return httpClient;
103+
}
104+
105+
private static byte[] GetSession(SessionMessage session, ApplicationContext _applicationContext)
106+
{
107+
var info = new MobileFormatter(_applicationContext).SerializeToDTO(session);
108+
var ms = new MemoryStream();
109+
new CslaBinaryWriter(_applicationContext).Write(ms, info);
110+
ms.Position = 0;
111+
var array = ms.ToArray();
112+
return array;
113+
}
114+
115+
[TestMethod]
116+
public async Task RetrieveSession_WithTimeoutValue_ShouldNotThrowException()
117+
{
118+
var timeout = TimeSpan.FromHours(1);
119+
var session = await _sessionManager.RetrieveSession(timeout);
120+
Assert.AreEqual(session, _sessionValue.Session);
121+
}
122+
123+
[TestMethod]
124+
public async Task RetrieveSession_WithZeroTimeout_ShouldThrowTimeoutException()
125+
{
126+
var timeout = TimeSpan.Zero;
127+
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.RetrieveSession(timeout));
128+
}
129+
130+
[TestMethod]
131+
public async Task RetrieveSession_WithValidCancellationToken_ShouldNotThrowException()
132+
{
133+
var cts = new CancellationTokenSource();
134+
var session = await _sessionManager.RetrieveSession(cts.Token);
135+
Assert.AreEqual(session, _sessionValue.Session);
136+
}
137+
138+
[TestMethod]
139+
public async Task RetrieveSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
140+
{
141+
var cts = new CancellationTokenSource();
142+
cts.Cancel();
143+
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.RetrieveSession(cts.Token));
144+
}
145+
146+
[TestMethod]
147+
public async Task SendSession_WithTimeoutValue_ShouldNotThrowException()
148+
{
149+
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
150+
151+
var timeout = TimeSpan.FromHours(1);
152+
await _sessionManager.SendSession(timeout);
153+
Assert.IsTrue(true);
154+
}
155+
156+
[TestMethod]
157+
public async Task SendSession_WithZeroTimeout_ShouldThrowTimeoutException()
158+
{
159+
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
160+
161+
var timeout = TimeSpan.Zero;
162+
await Assert.ThrowsExceptionAsync<TimeoutException>(() => _sessionManager.SendSession(timeout));
163+
}
164+
165+
[TestMethod]
166+
public async Task SendSession_WithValidCancellationToken_ShouldNotThrowException()
167+
{
168+
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
169+
170+
var cts = new CancellationTokenSource();
171+
await _sessionManager.SendSession(cts.Token);
172+
Assert.IsTrue(true);
173+
}
174+
175+
[TestMethod]
176+
public async Task SendSession_WithCancelledCancellationToken_ShouldThrowTaskCanceledException()
177+
{
178+
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
179+
180+
var cts = new CancellationTokenSource();
181+
cts.Cancel();
182+
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => _sessionManager.SendSession(cts.Token));
183+
}
184+
185+
186+
[TestMethod]
187+
public async Task RetrieveCachedSessionSession()
188+
{
189+
await _sessionManager.RetrieveSession(TimeSpan.FromHours(1));
190+
191+
var session = _sessionManager.GetCachedSession();
192+
Assert.IsNotNull(session);
193+
}
194+
[TestMethod]
195+
public void RetrieveNullCachedSessionSession()
196+
{
197+
var session = _sessionManager.GetCachedSession();
198+
Assert.IsNull(session);
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)