Skip to content

Commit 21d2181

Browse files
committed
test: add tests
1 parent 01da7d4 commit 21d2181

File tree

4 files changed

+358
-5
lines changed

4 files changed

+358
-5
lines changed

src/ByteSync.Client/Services/Communications/PushReceivers/SynchronizationProgressPushReceiver.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class SynchronizationProgressPushReceiver : IPushReceiver
1818
private readonly ISynchronizationApiClient _synchronizationApiClient;
1919
private readonly ILogger<SynchronizationProgressPushReceiver> _logger;
2020

21+
protected virtual TimeSpan SynchronizationDataTransmissionTimeout => TimeSpan.FromMinutes(1);
22+
2123
public SynchronizationProgressPushReceiver(IHubPushHandler2 hubPushHandler2, ISessionService sessionService,
2224
ISynchronizationService synchronizationService, ISharedActionsGroupRepository sharedActionsGroupRepository,
2325
ISynchronizationApiClient synchronizationApiClient,
@@ -54,15 +56,15 @@ private async void SynchronizationProgressChanged(SynchronizationProgressPush sy
5456
try
5557
{
5658
// Wait for synchronization data to be transmitted with timeout and cancellation
57-
var timeout = TimeSpan.FromMinutes(1);
5859
var synchronizationData = _synchronizationService.SynchronizationProcessData;
5960

60-
await synchronizationData.SynchronizationDataTransmitted.WaitUntilTrue(timeout,
61+
await synchronizationData.SynchronizationDataTransmitted.WaitUntilTrue(SynchronizationDataTransmissionTimeout,
6162
synchronizationData.CancellationTokenSource.Token);
6263
}
6364
catch (TimeoutException)
6465
{
65-
_logger.LogError("Timeout waiting for synchronization data transmission (1 minute) for session {SessionId}", sessionId);
66+
_logger.LogError("Timeout waiting for synchronization data transmission ({Timeout}) for session {SessionId}",
67+
SynchronizationDataTransmissionTimeout, sessionId);
6668
return;
6769
}
6870
catch (OperationCanceledException)

tests/ByteSync.Client.Tests/Services/Communications/PushReceivers/DataNodePushReceiverTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using Moq;
1010
using NUnit.Framework;
1111

12-
namespace ByteSync.Tests.Services.Communications;
12+
namespace ByteSync.Tests.Services.Communications.PushReceivers;
1313

1414
[TestFixture]
1515
public class DataNodePushReceiverTests

tests/ByteSync.Client.Tests/Services/Communications/PushReceivers/DataSourcePushReceiverTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
using Moq;
1111
using NUnit.Framework;
1212

13-
namespace ByteSync.Tests.Services.Communications;
13+
namespace ByteSync.Tests.Services.Communications.PushReceivers;
1414

1515
[TestFixture]
1616
public class DataSourcePushReceiverTests
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
using System.Reactive.Subjects;
2+
using ByteSync.Business.Synchronizations;
3+
using ByteSync.Common.Business.Sessions.Cloud;
4+
using ByteSync.Common.Business.Synchronizations;
5+
using ByteSync.Interfaces.Controls.Communications.Http;
6+
using ByteSync.Interfaces.Controls.Communications.SignalR;
7+
using ByteSync.Interfaces.Controls.Synchronizations;
8+
using ByteSync.Interfaces.Repositories;
9+
using ByteSync.Interfaces.Services.Sessions;
10+
using ByteSync.Services.Communications.PushReceivers;
11+
using Microsoft.Extensions.Logging;
12+
using Moq;
13+
using NUnit.Framework;
14+
15+
namespace ByteSync.Tests.Services.Communications.PushReceivers;
16+
17+
[TestFixture]
18+
public class SynchronizationProgressPushReceiverTests
19+
{
20+
private Subject<SynchronizationProgressPush> _synchronizationProgressUpdatedSubject = null!;
21+
private Mock<IHubPushHandler2> _hubPushHandlerMock = null!;
22+
private Mock<ISessionService> _sessionServiceMock = null!;
23+
private Mock<ISynchronizationService> _synchronizationServiceMock = null!;
24+
private Mock<ISharedActionsGroupRepository> _sharedActionsGroupRepositoryMock = null!;
25+
private Mock<ISynchronizationApiClient> _synchronizationApiClientMock = null!;
26+
private Mock<ILogger<SynchronizationProgressPushReceiver>> _loggerMock = null!;
27+
private SynchronizationProcessData _synchronizationProcessData = null!;
28+
29+
// ReSharper disable once NotAccessedField.Local
30+
private SynchronizationProgressPushReceiver _synchronizationProgressPushReceiver = null!;
31+
32+
private const string TEST_SESSION_ID = "test-session-id";
33+
34+
[SetUp]
35+
public void SetUp()
36+
{
37+
_synchronizationProgressUpdatedSubject = new Subject<SynchronizationProgressPush>();
38+
39+
_hubPushHandlerMock = new Mock<IHubPushHandler2>();
40+
_sessionServiceMock = new Mock<ISessionService>();
41+
_synchronizationServiceMock = new Mock<ISynchronizationService>();
42+
_sharedActionsGroupRepositoryMock = new Mock<ISharedActionsGroupRepository>();
43+
_synchronizationApiClientMock = new Mock<ISynchronizationApiClient>();
44+
_loggerMock = new Mock<ILogger<SynchronizationProgressPushReceiver>>();
45+
_synchronizationProcessData = new SynchronizationProcessData();
46+
47+
_hubPushHandlerMock.SetupGet(h => h.SynchronizationProgressUpdated)
48+
.Returns(_synchronizationProgressUpdatedSubject);
49+
50+
_synchronizationServiceMock.SetupGet(s => s.SynchronizationProcessData)
51+
.Returns(_synchronizationProcessData);
52+
53+
_synchronizationProgressPushReceiver = new SynchronizationProgressPushReceiver(
54+
_hubPushHandlerMock.Object,
55+
_sessionServiceMock.Object,
56+
_synchronizationServiceMock.Object,
57+
_sharedActionsGroupRepositoryMock.Object,
58+
_synchronizationApiClientMock.Object,
59+
_loggerMock.Object);
60+
}
61+
62+
[TearDown]
63+
public void TearDown()
64+
{
65+
_synchronizationProgressUpdatedSubject.Dispose();
66+
}
67+
68+
[Test]
69+
public async Task SynchronizationProgressChanged_WhenCurrentSessionIsNull_ShouldLogWarningAndReturn()
70+
{
71+
// Arrange
72+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns((CloudSession?)null);
73+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID);
74+
75+
// Act
76+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
77+
78+
// Give time for async operation to complete
79+
await Task.Delay(100);
80+
81+
// Assert
82+
VerifyLoggerWarning("Received a synchronization progress push but there is no current session");
83+
_synchronizationServiceMock.Verify(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
84+
_sharedActionsGroupRepositoryMock.Verify(r => r.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
85+
}
86+
87+
[Test]
88+
public async Task SynchronizationProgressChanged_WhenSessionIdDifferent_ShouldLogWarningAndReturn()
89+
{
90+
// Arrange
91+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
92+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
93+
var differentSessionId = "different-session-id";
94+
var synchronizationProgressPush = CreateSynchronizationProgressPush(differentSessionId);
95+
96+
// Act
97+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
98+
99+
// Give time for async operation to complete
100+
await Task.Delay(100);
101+
102+
// Assert
103+
VerifyLoggerWarning("Received a synchronization progress push for a different session than the current one");
104+
_synchronizationServiceMock.Verify(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
105+
_sharedActionsGroupRepositoryMock.Verify(r => r.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
106+
}
107+
108+
[Test]
109+
public async Task SynchronizationProgressChanged_WhenSuccessful_ShouldProcessPush()
110+
{
111+
// Arrange
112+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
113+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
114+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID);
115+
116+
SetupSynchronizationDataTransmittedSuccess();
117+
118+
// Act
119+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
120+
121+
// Give time for async operation to complete
122+
await Task.Delay(200);
123+
124+
// Assert
125+
_synchronizationServiceMock.Verify(s => s.OnSynchronizationProgressChanged(synchronizationProgressPush), Times.Once);
126+
_sharedActionsGroupRepositoryMock.Verify(r => r.OnSynchronizationProgressChanged(synchronizationProgressPush), Times.Once);
127+
}
128+
129+
[Test]
130+
public async Task SynchronizationProgressChanged_WhenTimeoutWaiting_ShouldLogErrorAndReturn()
131+
{
132+
// Arrange
133+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
134+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
135+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID);
136+
137+
// Create a testable receiver with short timeout
138+
var testableReceiver = new TestableProgressPushReceiver(
139+
_hubPushHandlerMock.Object,
140+
_sessionServiceMock.Object,
141+
_synchronizationServiceMock.Object,
142+
_sharedActionsGroupRepositoryMock.Object,
143+
_synchronizationApiClientMock.Object,
144+
_loggerMock.Object);
145+
146+
SetupSynchronizationDataTransmittedTimeout();
147+
148+
// Act
149+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
150+
151+
// Give time for timeout to occur
152+
await Task.Delay(200);
153+
154+
// Assert
155+
VerifyLoggerError($"Timeout waiting for synchronization data transmission ({testableReceiver.TimeoutForTest}) for session {TEST_SESSION_ID}");
156+
_synchronizationServiceMock.Verify(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
157+
_sharedActionsGroupRepositoryMock.Verify(r => r.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
158+
}
159+
160+
[Test]
161+
public async Task SynchronizationProgressChanged_WhenCancelled_ShouldLogInformationAndReturn()
162+
{
163+
// Arrange
164+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
165+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
166+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID);
167+
168+
SetupSynchronizationDataTransmittedCancellation();
169+
170+
// Act
171+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
172+
173+
// Give time for async operation to complete
174+
await Task.Delay(200);
175+
176+
// Assert
177+
VerifyLoggerInformation($"Synchronization progress processing cancelled for session {TEST_SESSION_ID}");
178+
_synchronizationServiceMock.Verify(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
179+
_sharedActionsGroupRepositoryMock.Verify(r => r.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()), Times.Never);
180+
}
181+
182+
[Test]
183+
public async Task SynchronizationProgressChanged_WhenExceptionThrown_ShouldLogErrorAndCallAssertSynchronizationActionErrors()
184+
{
185+
// Arrange
186+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
187+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
188+
var trackingActionSummaries = new List<TrackingActionSummary>
189+
{
190+
new TrackingActionSummary { ActionsGroupId = "group1" },
191+
new TrackingActionSummary { ActionsGroupId = "group2" }
192+
};
193+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID, trackingActionSummaries);
194+
195+
SetupSynchronizationDataTransmittedSuccess();
196+
_synchronizationServiceMock.Setup(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()))
197+
.ThrowsAsync(new Exception("Test exception"));
198+
199+
// Act
200+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
201+
202+
// Give time for async operation to complete
203+
await Task.Delay(200);
204+
205+
// Assert
206+
VerifyLoggerError("Error processing synchronization progress push");
207+
_synchronizationApiClientMock.Verify(c => c.AssertSynchronizationActionErrors(
208+
TEST_SESSION_ID,
209+
It.Is<List<string>>(list => list.Count == 2 && list.Contains("group1") && list.Contains("group2"))),
210+
Times.Once);
211+
}
212+
213+
[Test]
214+
public async Task SynchronizationProgressChanged_WhenExceptionThrownWithoutTrackingActionSummaries_ShouldLogErrorButNotCallAssertSynchronizationActionErrors()
215+
{
216+
// Arrange
217+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
218+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
219+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID);
220+
221+
SetupSynchronizationDataTransmittedSuccess();
222+
_synchronizationServiceMock.Setup(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()))
223+
.ThrowsAsync(new Exception("Test exception"));
224+
225+
// Act
226+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
227+
228+
// Give time for async operation to complete
229+
await Task.Delay(200);
230+
231+
// Assert
232+
VerifyLoggerError("Error processing synchronization progress push");
233+
_synchronizationApiClientMock.Verify(c => c.AssertSynchronizationActionErrors(It.IsAny<string>(), It.IsAny<List<string>>()), Times.Never);
234+
}
235+
236+
[Test]
237+
public async Task SynchronizationProgressChanged_WhenAssertSynchronizationActionErrorsThrows_ShouldLogSecondError()
238+
{
239+
// Arrange
240+
var currentSession = new CloudSession { SessionId = TEST_SESSION_ID };
241+
_sessionServiceMock.SetupGet(s => s.CurrentSession).Returns(currentSession);
242+
var trackingActionSummaries = new List<TrackingActionSummary>
243+
{
244+
new TrackingActionSummary { ActionsGroupId = "group1" }
245+
};
246+
var synchronizationProgressPush = CreateSynchronizationProgressPush(TEST_SESSION_ID, trackingActionSummaries);
247+
248+
SetupSynchronizationDataTransmittedSuccess();
249+
_synchronizationServiceMock.Setup(s => s.OnSynchronizationProgressChanged(It.IsAny<SynchronizationProgressPush>()))
250+
.ThrowsAsync(new Exception("Test exception"));
251+
_synchronizationApiClientMock.Setup(c => c.AssertSynchronizationActionErrors(It.IsAny<string>(), It.IsAny<List<string>>()))
252+
.ThrowsAsync(new Exception("API exception"));
253+
254+
// Act
255+
_synchronizationProgressUpdatedSubject.OnNext(synchronizationProgressPush);
256+
257+
// Give time for async operation to complete
258+
await Task.Delay(200);
259+
260+
// Assert
261+
VerifyLoggerError("Error processing synchronization progress push");
262+
VerifyLoggerError("Error asserting synchronization action errors");
263+
}
264+
265+
private SynchronizationProgressPush CreateSynchronizationProgressPush(string sessionId, List<TrackingActionSummary>? trackingActionSummaries = null)
266+
{
267+
return new SynchronizationProgressPush
268+
{
269+
SessionId = sessionId,
270+
ProcessedVolume = 1000,
271+
ExchangedVolume = 500,
272+
FinishedActionsCount = 10,
273+
ErrorActionsCount = 1,
274+
Version = DateTimeOffset.UtcNow.Ticks,
275+
TrackingActionSummaries = trackingActionSummaries
276+
};
277+
}
278+
279+
private void SetupSynchronizationDataTransmittedSuccess()
280+
{
281+
_synchronizationProcessData.SynchronizationDataTransmitted.OnNext(true);
282+
}
283+
284+
private void SetupSynchronizationDataTransmittedTimeout()
285+
{
286+
_synchronizationProcessData.SynchronizationDataTransmitted.OnNext(false);
287+
}
288+
289+
private void SetupSynchronizationDataTransmittedCancellation()
290+
{
291+
_synchronizationProcessData.CancellationTokenSource.Cancel();
292+
_synchronizationProcessData.SynchronizationDataTransmitted.OnNext(false);
293+
}
294+
295+
private void VerifyLoggerWarning(string message)
296+
{
297+
_loggerMock.Verify(
298+
x => x.Log(
299+
LogLevel.Warning,
300+
It.IsAny<EventId>(),
301+
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains(message)),
302+
It.IsAny<Exception>(),
303+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
304+
Times.Once);
305+
}
306+
307+
private void VerifyLoggerError(string message)
308+
{
309+
_loggerMock.Verify(
310+
x => x.Log(
311+
LogLevel.Error,
312+
It.IsAny<EventId>(),
313+
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains(message)),
314+
It.IsAny<Exception?>(),
315+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
316+
Times.AtLeastOnce);
317+
}
318+
319+
private void VerifyLoggerInformation(string message)
320+
{
321+
_loggerMock.Verify(
322+
x => x.Log(
323+
LogLevel.Information,
324+
It.IsAny<EventId>(),
325+
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains(message)),
326+
It.IsAny<Exception>(),
327+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
328+
Times.Once);
329+
}
330+
}
331+
332+
// Test helper class that allows overriding the timeout for testing
333+
internal class TestableProgressPushReceiver : SynchronizationProgressPushReceiver
334+
{
335+
protected override TimeSpan SynchronizationDataTransmissionTimeout => TimeSpan.FromMilliseconds(50);
336+
337+
// Public property for test assertion
338+
public TimeSpan TimeoutForTest => SynchronizationDataTransmissionTimeout;
339+
340+
public TestableProgressPushReceiver(
341+
IHubPushHandler2 hubPushHandler2,
342+
ISessionService sessionService,
343+
ISynchronizationService synchronizationService,
344+
ISharedActionsGroupRepository sharedActionsGroupRepository,
345+
ISynchronizationApiClient synchronizationApiClient,
346+
// ReSharper disable once ContextualLoggerProblem
347+
ILogger<SynchronizationProgressPushReceiver> logger)
348+
: base(hubPushHandler2, sessionService, synchronizationService, sharedActionsGroupRepository, synchronizationApiClient, logger)
349+
{
350+
}
351+
}

0 commit comments

Comments
 (0)