From 0385a3535abfa60643787a0240077680c987ec59 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Tue, 8 Apr 2025 22:56:06 +0200 Subject: [PATCH 01/22] feat: improve redis lock management --- .../FinalizeJoinCloudSessionCommandHandler.cs | 5 ++- .../Interfaces/Services/ICacheService.cs | 2 - .../Repositories/BaseRepository.cs | 43 ++++++++----------- .../Repositories/SharedFilesRepository.cs | 42 ++++++------------ .../Services/CacheService.cs | 8 ---- 5 files changed, 36 insertions(+), 64 deletions(-) diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs index 5e5ce934d..bf22d6fbd 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs @@ -41,6 +41,9 @@ public async Task Handle(FinalizeJoinCloudSessionRequ SessionMemberData? joiner = null; var transaction = _cacheService.OpenTransaction(); + + await using var sessionRedisLock = await _cacheService.AcquireLockAsync(_cloudSessionsRepository.ComputeCacheKey(_cloudSessionsRepository.ElementName, + parameters.SessionId)); var updateResult = await _cloudSessionsRepository.Update(parameters.SessionId, innerCloudSessionData => { @@ -96,7 +99,7 @@ public async Task Handle(FinalizeJoinCloudSessionRequ { return false; } - }, transaction); + }, transaction, sessionRedisLock); if (updateResult.IsWaitingForTransaction) { diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs index da9002491..9a9f68fb2 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs @@ -6,8 +6,6 @@ namespace ByteSync.ServerCommon.Interfaces.Services; public interface ICacheService { - public RedLockFactory RedLockFactory { get; } - public string Prefix { get; } ITransaction OpenTransaction(); diff --git a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs index ffec17114..5e6f2e70e 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -99,43 +99,36 @@ private async Task> DoUpdate(string key, Func upd IRedLock? redisLock = redisLockParam; bool shouldDispose = false; - if (redisLock == null) - { - redisLock = await _cacheService.RedLockFactory.CreateLockAsync(cacheKey, TimeSpan.FromSeconds(30)); - shouldDispose = true; - } - try { - if (redisLock.IsAcquired) + if (redisLock == null) { - var cachedElement = await GetCachedElement(cacheKey); - - if (cachedElement == null) - { - if (throwIfNotExists) - { - throw new Exception("Could not find element to update"); - } - else - { - return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NotFound); - } - } + redisLock = await _cacheService.AcquireLockAsync(cacheKey); + shouldDispose = true; + } + + var cachedElement = await GetCachedElement(cacheKey); - bool isUpdateDone = updateHandler.Invoke(cachedElement); - if (!isUpdateDone) + if (cachedElement == null) + { + if (throwIfNotExists) { - return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); + throw new Exception("Could not find element to update"); } else { - return await SetElement(cacheKey, cachedElement, database); + return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NotFound); } } + + bool isUpdateDone = updateHandler.Invoke(cachedElement); + if (!isUpdateDone) + { + return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); + } else { - throw new AcquireRedisLockException(key, redisLock); + return await SetElement(cacheKey, cachedElement, database); } } finally diff --git a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs index 042339417..c4641769f 100644 --- a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs @@ -73,41 +73,27 @@ public async Task> Clear(string sessionId) var database = _cacheService.GetDatabase(); - await using var sessionSharedFilesLock = await _cacheService.RedLockFactory.CreateLockAsync(sessionSharedFilesKey, TimeSpan.FromSeconds(30)); + await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); - if (sessionSharedFilesLock.IsAcquired) - { - var redisValues = await database.SetMembersAsync(sessionSharedFilesKey); - List sharedFileDefinitionIds = redisValues.Select(value => value.ToString()).ToList(); + var redisValues = await database.SetMembersAsync(sessionSharedFilesKey); + List sharedFileDefinitionIds = redisValues.Select(value => value.ToString()).ToList(); - foreach (var sharedFileDefinitionId in sharedFileDefinitionIds) - { - var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinitionId); - await using var sharedFileLock = await _cacheService.RedLockFactory.CreateLockAsync(sharedFileCacheKey, TimeSpan.FromSeconds(30)); + foreach (var sharedFileDefinitionId in sharedFileDefinitionIds) + { + var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinitionId); + await using var sharedFileLock = await _cacheService.AcquireLockAsync(sharedFileCacheKey); - if (sharedFileLock.IsAcquired) - { - var sharedFileData = await GetCachedElement(sharedFileCacheKey); + var sharedFileData = await GetCachedElement(sharedFileCacheKey); - if (sharedFileData != null) - { - result.Add(sharedFileData); + if (sharedFileData != null) + { + result.Add(sharedFileData); - await database.KeyDeleteAsync(sharedFileCacheKey); - } - } - else - { - throw new AcquireRedisLockException(sharedFileCacheKey, sharedFileLock); - } + await database.KeyDeleteAsync(sharedFileCacheKey); } - - await database.KeyDeleteAsync(sessionSharedFilesKey); - } - else - { - throw new AcquireRedisLockException(sessionSharedFilesKey, sessionSharedFilesLock); } + + await database.KeyDeleteAsync(sessionSharedFilesKey); return result; } diff --git a/src/ByteSync.ServerCommon/Services/CacheService.cs b/src/ByteSync.ServerCommon/Services/CacheService.cs index a45890ef5..bc8d9640d 100644 --- a/src/ByteSync.ServerCommon/Services/CacheService.cs +++ b/src/ByteSync.ServerCommon/Services/CacheService.cs @@ -30,14 +30,6 @@ public CacheService(IOptions redisSettings, ILoggerFactory logger RedLockRetryConfiguration redLockRetryConfiguration = new RedLockRetryConfiguration(5, 500); _redLockFactory = RedLockFactory.Create(multiplexers, redLockRetryConfiguration, loggerFactory); } - - public RedLockFactory RedLockFactory - { - get - { - return _redLockFactory; - } - } public string Prefix => _redisSettings.Prefix; From dab36233b13484729d0b021d593d707df8f17c07 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 9 Apr 2025 10:32:03 +0200 Subject: [PATCH 02/22] feat: improve error management on YouJoinedSession refactor: use ILogger --- .../DigitalSignaturesChecker.cs | 31 ++++++------------ .../Joining/YouJoinedSessionService.cs | 32 +++++++++---------- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/ByteSync.Client/Services/Communications/DigitalSignaturesChecker.cs b/src/ByteSync.Client/Services/Communications/DigitalSignaturesChecker.cs index 323534106..68319c0f1 100644 --- a/src/ByteSync.Client/Services/Communications/DigitalSignaturesChecker.cs +++ b/src/ByteSync.Client/Services/Communications/DigitalSignaturesChecker.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; -using ByteSync.Common.Business.Sessions.Cloud.Connections; +using ByteSync.Common.Business.Sessions.Cloud.Connections; using ByteSync.Common.Business.Trust.Connections; using ByteSync.Interfaces.Controls.Applications; using ByteSync.Interfaces.Controls.Communications; using ByteSync.Interfaces.Controls.Communications.Http; -using Serilog; namespace ByteSync.Services.Communications; @@ -15,28 +13,27 @@ public class DigitalSignaturesChecker : IDigitalSignaturesChecker private readonly IDigitalSignaturesRepository _digitalSignaturesRepository; private readonly ITrustApiClient _trustApiClient; private readonly IDigitalSignatureComputer _digitalSignatureComputer; + private readonly ILogger _logger; - public DigitalSignaturesChecker(IEnvironmentService environmentService, - IPublicKeysManager publicKeysManager, IDigitalSignaturesRepository digitalSignaturesRepository, - ITrustApiClient trustApiClient, IDigitalSignatureComputer digitalSignatureComputer) + public DigitalSignaturesChecker(IEnvironmentService environmentService, IPublicKeysManager publicKeysManager, + IDigitalSignaturesRepository digitalSignaturesRepository, ITrustApiClient trustApiClient, IDigitalSignatureComputer digitalSignatureComputer, + ILogger logger) { _environmentService = environmentService; _publicKeysManager = publicKeysManager; _digitalSignaturesRepository = digitalSignaturesRepository; _trustApiClient = trustApiClient; _digitalSignatureComputer = digitalSignatureComputer; + _logger = logger; } public async Task CheckExistingMembersDigitalSignatures(string dataId, ICollection clientInstanceIds) { - // Tout le monde est trusté, on fait un Check Auth var signatureCheckInfos = new List(); - - // On enlève, au cas où, le clientInstanceId du client actuel + clientInstanceIds.Remove(_environmentService.ClientInstanceId); foreach (var memberInstanceId in clientInstanceIds) { - // On calcule la signature d'authentification var digitalSignatureCheckInfo = _digitalSignatureComputer .BuildDigitalSignatureCheckInfo(dataId, memberInstanceId, true); @@ -75,7 +72,7 @@ public async Task CheckDigitalSignature(DigitalSignatureCheckInfo digitalSignatu if (isDataOK) { - Log.Information("Digital Signature successfully checked for Client {ClientInstanceId} with Public Key {@PublicKeyInfo}", + _logger.LogInformation("Digital Signature successfully checked for Client {ClientInstanceId} with Public Key {@PublicKeyInfo}", digitalSignatureCheckInfo.Issuer, otherPartyPublicKeyInfo); await _digitalSignaturesRepository.SetDigitalSignatureChecked(digitalSignatureCheckInfo.DataId, digitalSignatureCheckInfo.Issuer); @@ -108,17 +105,7 @@ public async Task CheckDigitalSignature(DigitalSignatureCheckInfo digitalSignatu } else { - Log.Warning("Digital Signature check failed for Client {ClientInstanceId}", digitalSignatureCheckInfo.Issuer); + _logger.LogWarning("Digital Signature check failed for Client {ClientInstanceId}", digitalSignatureCheckInfo.Issuer); } } - - // private void LogUnknownSessionReceived(string? sessionId, [CallerMemberName] string caller = "") - // { - // if (caller.IsNullOrEmpty()) - // { - // caller = "UnknownCaller"; - // } - // - // Log.Error("DigitalSignaturesChecker.{caller}: unknown sessionId received ({sessionId})", caller, sessionId); - // } } \ No newline at end of file diff --git a/src/ByteSync.Client/Services/Sessions/Connecting/Joining/YouJoinedSessionService.cs b/src/ByteSync.Client/Services/Sessions/Connecting/Joining/YouJoinedSessionService.cs index 6842b8bd2..29645f7ea 100644 --- a/src/ByteSync.Client/Services/Sessions/Connecting/Joining/YouJoinedSessionService.cs +++ b/src/ByteSync.Client/Services/Sessions/Connecting/Joining/YouJoinedSessionService.cs @@ -7,12 +7,10 @@ using ByteSync.Interfaces.Controls.Communications.Http; using ByteSync.Interfaces.Controls.Encryptions; using ByteSync.Interfaces.Repositories; -using ByteSync.Interfaces.Services.Sessions; using ByteSync.Interfaces.Services.Sessions.Connecting; using ByteSync.Interfaces.Services.Sessions.Connecting.Joining; -using Serilog; -namespace ByteSync.Services.Sessions.Connecting; +namespace ByteSync.Services.Sessions.Connecting.Joining; public class YouJoinedSessionService : IYouJoinedSessionService { @@ -23,8 +21,8 @@ public class YouJoinedSessionService : IYouJoinedSessionService private readonly IDataEncrypter _dataEncrypter; private readonly ICloudSessionApiClient _cloudSessionApiClient; private readonly IPublicKeysManager _publicKeysManager; - private readonly ISessionService _sessionService; private readonly IAfterJoinSessionService _afterJoinSessionService; + private readonly ICloudSessionConnectionService _cloudSessionConnectionService; private readonly ILogger _logger; private const string UNKNOWN_RECEIVED_SESSION_ID = "unknown received sessionId {sessionId}"; @@ -32,8 +30,8 @@ public class YouJoinedSessionService : IYouJoinedSessionService public YouJoinedSessionService(ICloudSessionConnectionRepository cloudSessionConnectionRepository, IEnvironmentService environmentService, IPublicKeysTruster publicKeysTruster, IDigitalSignaturesChecker digitalSignaturesChecker, - IDataEncrypter dataEncrypter, ICloudSessionApiClient cloudSessionApiClient, IPublicKeysManager publicKeysManager, ISessionService sessionService, - IAfterJoinSessionService afterJoinSessionService, ILogger logger) + IDataEncrypter dataEncrypter, ICloudSessionApiClient cloudSessionApiClient, IPublicKeysManager publicKeysManager, + IAfterJoinSessionService afterJoinSessionService, ICloudSessionConnectionService cloudSessionConnectionService, ILogger logger) { _cloudSessionConnectionRepository = cloudSessionConnectionRepository; _environmentService = environmentService; @@ -42,8 +40,8 @@ public YouJoinedSessionService(ICloudSessionConnectionRepository cloudSessionCon _dataEncrypter = dataEncrypter; _cloudSessionApiClient = cloudSessionApiClient; _publicKeysManager = publicKeysManager; - _sessionService = sessionService; _afterJoinSessionService = afterJoinSessionService; + _cloudSessionConnectionService = cloudSessionConnectionService; _logger = logger; } @@ -138,24 +136,26 @@ public async Task Process(CloudSessionResult cloudSessionResult, ValidateJoinClo var lobbySessionDetails = await _cloudSessionConnectionRepository .GetTempLobbySessionDetails(cloudSessionResult.CloudSession.SessionId); - await _afterJoinSessionService.Process( - new AfterJoinSessionRequest(cloudSessionResult, lobbySessionDetails, false)); - + var afterJoinSessionRequest = new AfterJoinSessionRequest(cloudSessionResult, lobbySessionDetails, false); + await _afterJoinSessionService.Process(afterJoinSessionRequest); await _cloudSessionConnectionRepository.SetJoinSessionResultReceived(cloudSessionResult.CloudSession.SessionId); _cloudSessionConnectionRepository.SetConnectionStatus(SessionConnectionStatus.InSession); - - // ReSharper disable once PossibleNullReferenceException - Log.Information("JoinSession: {CloudSession}", cloudSessionResult.SessionId); + + _logger.LogInformation("JoinSession: {CloudSession}", cloudSessionResult.SessionId); } catch (Exception ex) { - Log.Error(ex, "OnYouJoinedSession"); + _logger.LogError(ex, "OnYouJoinedSession"); - _sessionService.ClearCloudSession(); + var joinSessionError = new JoinSessionError + { + Exception = ex, + Status = JoinSessionStatus.UnexpectedError + }; - _cloudSessionConnectionRepository.SetConnectionStatus(SessionConnectionStatus.NoSession); + await _cloudSessionConnectionService.OnJoinSessionError(joinSessionError); } } } \ No newline at end of file From 15b295b9e2adf5d5bdab0d8cdfbc89b89dca9eb5 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 9 Apr 2025 20:00:24 +0200 Subject: [PATCH 03/22] refactor: introduce EntityType & ICacheKeyFactory in Repositories System --- .../Business/Repositories/CacheKey.cs | 12 ++++ .../FinalizeJoinCloudSessionCommandHandler.cs | 5 +- .../StartInventoryCommandHandler.cs | 7 +-- .../Entities/EntityType.cs | 15 +++++ .../Factories/CacheKeyFactory.cs | 57 ++++++++++++++++++ .../Interfaces/Factories/ICacheKeyFactory.cs | 9 +++ .../Interfaces/Repositories/IRepository.cs | 9 ++- .../Interfaces/Services/ICacheService.cs | 13 +++-- .../Repositories/BaseRepository.cs | 58 ++++++++++--------- ...ClientSoftwareVersionSettingsRepository.cs | 3 +- .../Repositories/ClientsRepository.cs | 3 +- .../CloudSessionProfileRepository.cs | 2 +- .../Repositories/CloudSessionsRepository.cs | 10 ++-- .../Repositories/InventoryRepository.cs | 3 +- .../Repositories/LobbyRepository.cs | 6 +- .../Repositories/SharedFilesRepository.cs | 38 ++++++------ .../Repositories/SynchronizationRepository.cs | 2 +- .../Repositories/TrackingActionRepository.cs | 11 ++-- .../Services/CacheService.cs | 31 ++++++++-- .../Helpers/TestSettingsInitializer.cs | 4 +- .../SharedFilesRepositoryTests.cs | 6 +- .../Services/AuthServiceTests.cs | 3 - .../ClientSoftwareVersionServiceTests.cs | 1 - .../Services/SynchronizationServiceTests.cs | 1 - 24 files changed, 217 insertions(+), 92 deletions(-) create mode 100644 src/ByteSync.ServerCommon/Business/Repositories/CacheKey.cs create mode 100644 src/ByteSync.ServerCommon/Entities/EntityType.cs create mode 100644 src/ByteSync.ServerCommon/Factories/CacheKeyFactory.cs create mode 100644 src/ByteSync.ServerCommon/Interfaces/Factories/ICacheKeyFactory.cs diff --git a/src/ByteSync.ServerCommon/Business/Repositories/CacheKey.cs b/src/ByteSync.ServerCommon/Business/Repositories/CacheKey.cs new file mode 100644 index 000000000..dbcdca131 --- /dev/null +++ b/src/ByteSync.ServerCommon/Business/Repositories/CacheKey.cs @@ -0,0 +1,12 @@ +using ByteSync.ServerCommon.Entities; + +namespace ByteSync.ServerCommon.Business.Repositories; + +public class CacheKey +{ + public required EntityType EntityType { get; init; } + + public required string EntityId { get; init; } + + public required string Value { get; init; } +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs index bf22d6fbd..6efc7c3d7 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs @@ -1,7 +1,9 @@ using ByteSync.Common.Business.Sessions.Cloud.Connections; using ByteSync.Common.Helpers; using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Helpers; +using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Mappers; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -42,8 +44,7 @@ public async Task Handle(FinalizeJoinCloudSessionRequ var transaction = _cacheService.OpenTransaction(); - await using var sessionRedisLock = await _cacheService.AcquireLockAsync(_cloudSessionsRepository.ComputeCacheKey(_cloudSessionsRepository.ElementName, - parameters.SessionId)); + await using var sessionRedisLock = await _cacheService.AcquireLockAsync(EntityType.Session, parameters.SessionId); var updateResult = await _cloudSessionsRepository.Update(parameters.SessionId, innerCloudSessionData => { diff --git a/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs index bf71c1d88..afe710e0a 100644 --- a/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs @@ -2,6 +2,7 @@ using ByteSync.Common.Business.Sessions; using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; using ByteSync.ServerCommon.Interfaces.Services.Clients; @@ -42,11 +43,9 @@ public async Task Handle(StartInventoryRequest request, Ca var sessionId = request.SessionId; var client = request.Client; - await using var sessionRedisLock = await _cacheService.AcquireLockAsync(_cloudSessionsRepository.ComputeCacheKey(_cloudSessionsRepository.ElementName, - sessionId)); + await using var sessionRedisLock = await _cacheService.AcquireLockAsync(EntityType.Session, sessionId); - await using var inventoryRedisLock = await _cacheService.AcquireLockAsync(_inventoryRepository.ComputeCacheKey(_inventoryRepository.ElementName, - sessionId)); + await using var inventoryRedisLock = await _cacheService.AcquireLockAsync(EntityType.Inventory, sessionId); var transaction = _cacheService.OpenTransaction(); diff --git a/src/ByteSync.ServerCommon/Entities/EntityType.cs b/src/ByteSync.ServerCommon/Entities/EntityType.cs new file mode 100644 index 000000000..a16fa3a3d --- /dev/null +++ b/src/ByteSync.ServerCommon/Entities/EntityType.cs @@ -0,0 +1,15 @@ +namespace ByteSync.ServerCommon.Entities; + +public enum EntityType +{ + Session, + Inventory, + Synchronization, + SharedFile, + SessionSharedFiles, + TrackingAction, + Client, + ClientSoftwareVersionSettings, + CloudSessionProfile, + Lobby +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Factories/CacheKeyFactory.cs b/src/ByteSync.ServerCommon/Factories/CacheKeyFactory.cs new file mode 100644 index 000000000..46bb95924 --- /dev/null +++ b/src/ByteSync.ServerCommon/Factories/CacheKeyFactory.cs @@ -0,0 +1,57 @@ +using System.Text; +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Interfaces.Factories; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Factories; + +public class CacheKeyFactory : ICacheKeyFactory +{ + private readonly string _prefix; + + public CacheKeyFactory(IOptions redisSettings) + { + _prefix = redisSettings.Value.Prefix; + } + + public CacheKey Create(EntityType entityType, string entityId) + { + string entityTypeName = GetEntityTypeName(entityType); + + StringBuilder sb = new StringBuilder(_prefix); + sb.Append(':'); + sb.Append(entityTypeName); + sb.Append(':'); + sb.Append(entityId); + var cacheKeyValue = sb.ToString(); + + var cacheKey = new CacheKey + { + EntityType = entityType, + EntityId = entityId, + Value = cacheKeyValue + }; + + return cacheKey; + } + + private string GetEntityTypeName(EntityType entityType) + { + return entityType switch + { + EntityType.Session => "Session", + EntityType.Inventory => "Inventory", + EntityType.Synchronization => "Synchronization", + EntityType.SharedFile => "SharedFile", + EntityType.SessionSharedFiles => "SessionSharedFiles", + EntityType.TrackingAction => "TrackingAction", + EntityType.Client => "Client", + EntityType.ClientSoftwareVersionSettings => "ClientSoftwareVersionSettings", + EntityType.CloudSessionProfile => "CloudSessionProfile", + EntityType.Lobby => "Lobby", + _ => throw new ArgumentOutOfRangeException(nameof(entityType), entityType, null) + }; + } +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Interfaces/Factories/ICacheKeyFactory.cs b/src/ByteSync.ServerCommon/Interfaces/Factories/ICacheKeyFactory.cs new file mode 100644 index 000000000..fd7853232 --- /dev/null +++ b/src/ByteSync.ServerCommon/Interfaces/Factories/ICacheKeyFactory.cs @@ -0,0 +1,9 @@ +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; + +namespace ByteSync.ServerCommon.Interfaces.Factories; + +public interface ICacheKeyFactory +{ + CacheKey Create(EntityType entityType, string entityId); +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index 729770bcb..feb6257df 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -1,4 +1,5 @@ using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; using RedLockNet; using StackExchange.Redis; @@ -6,9 +7,9 @@ namespace ByteSync.ServerCommon.Interfaces.Repositories; public interface IRepository { - string ComputeCacheKey(params string[] keyParts); + // string ComputeCacheKey(params string[] keyParts); - string ElementName { get; } + EntityType EntityType { get; } Task Get(string key); @@ -22,9 +23,11 @@ public interface IRepository Task> UpdateIfExists(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null); + Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null); + Task> Save(string key, T element, ITransaction? transaction = null); - Task> SetElement(string cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); + Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); Task Delete(string key); diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs index 9a9f68fb2..fb4447fd2 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs @@ -1,18 +1,23 @@ -using RedLockNet; -using RedLockNet.SERedis; +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; +using RedLockNet; using StackExchange.Redis; namespace ByteSync.ServerCommon.Interfaces.Services; public interface ICacheService { - public string Prefix { get; } + // public string Prefix { get; } ITransaction OpenTransaction(); IDatabaseAsync GetDatabase(); IDatabaseAsync GetDatabase(ITransaction? transaction); + + Task AcquireLockAsync(EntityType entityType, string entityId); + + Task AcquireLockAsync(CacheKey cacheKey); - Task AcquireLockAsync(string key); + CacheKey ComputeCacheKey(EntityType entityType, string s); } \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs index 5e6f2e70e..f616461bb 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -1,8 +1,7 @@ -using System.Text; -using ByteSync.Common.Controls.Json; +using ByteSync.Common.Controls.Json; using ByteSync.Common.Helpers; using ByteSync.ServerCommon.Business.Repositories; -using ByteSync.ServerCommon.Exceptions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; using RedLockNet; @@ -19,23 +18,23 @@ protected BaseRepository(ICacheService cacheService) _cacheService = cacheService; } - private string Prefix => _cacheService.Prefix; + // private string Prefix => _cacheService.Prefix; - public abstract string ElementName { get; } + public abstract EntityType EntityType { get; } private TimeSpan Expiry => TimeSpan.FromDays(2); - public string ComputeCacheKey(params string[] keyParts) - { - StringBuilder sb = new StringBuilder(Prefix); - - foreach (var keyPart in keyParts) - { - sb.Append($":{keyPart}"); - } - - return sb.ToString(); - } + // public string ComputeCacheKey(params string[] keyParts) + // { + // StringBuilder sb = new StringBuilder(Prefix); + // + // foreach (var keyPart in keyParts) + // { + // sb.Append($":{keyPart}"); + // } + // + // return sb.ToString(); + // } public Task Get(string key) { @@ -44,7 +43,7 @@ public string ComputeCacheKey(params string[] keyParts) public async Task Get(string key, ITransaction? transaction) { - var cacheKey = ComputeCacheKey(ElementName, key); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); var cachedElement = await GetCachedElement(cacheKey); return cachedElement; @@ -57,7 +56,7 @@ public async Task> AddOrUpdate(string key, Func ha public async Task> AddOrUpdate(string key, Func handler, ITransaction? transaction) { - var cacheKey = ComputeCacheKey(ElementName, key); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); IDatabaseAsync database = _cacheService.GetDatabase(transaction); await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); @@ -93,7 +92,7 @@ public async Task> UpdateIfExists(string key, Func> DoUpdate(string key, Func updateHandler, bool throwIfNotExists, ITransaction? transaction, IRedLock? redisLockParam) { - var cacheKey = ComputeCacheKey(ElementName, key); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); IDatabaseAsync database = _cacheService.GetDatabase(transaction); IRedLock? redisLock = redisLockParam; @@ -142,18 +141,23 @@ private async Task> DoUpdate(string key, Func upd public async Task> Save(string key, T element, ITransaction? transaction = null) { - var cacheKey = ComputeCacheKey(ElementName, key); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + return await Save(cacheKey, element, transaction); + } + + public async Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null) + { IDatabaseAsync database = _cacheService.GetDatabase(transaction); await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); return await SetElement(cacheKey, element, database); } - protected async Task GetCachedElement(string cacheKey) + protected async Task GetCachedElement(CacheKey cacheKey) { T? cachedElement = default(T); - string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey); + string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement.IsNotEmpty()) { cachedElement = JsonHelper.Deserialize(serializedElement!); @@ -162,19 +166,19 @@ public async Task> Save(string key, T element, ITransactio return cachedElement; } - public async Task> SetElement(string cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) + public async Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) { string serializedElement = JsonHelper.Serialize(createdOrUpdatedElement); if (database is ITransaction) { - _ = database.StringSetAsync(cacheKey, serializedElement, Expiry); + _ = database.StringSetAsync(cacheKey.Value, serializedElement, Expiry); return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.WaitingForTransaction); } else { - await database.StringSetAsync(cacheKey, serializedElement, Expiry); + await database.StringSetAsync(cacheKey.Value, serializedElement, Expiry); return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.Saved); } @@ -187,9 +191,9 @@ public async Task Delete(string key) public async Task Delete(string key, ITransaction? transaction) { - var cacheKey = ComputeCacheKey(ElementName, key); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); IDatabaseAsync database = _cacheService.GetDatabase(transaction); - await database.KeyDeleteAsync(cacheKey); + await database.KeyDeleteAsync(cacheKey.Value); } } \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs b/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs index 60ff3e8ca..e150a6a5c 100644 --- a/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs @@ -1,4 +1,5 @@ using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -10,7 +11,7 @@ public ClientSoftwareVersionSettingsRepository(ICacheService cacheService) : bas { } - public override string ElementName => "ClientSoftwareVersionSettings"; + public override EntityType EntityType => EntityType.ClientSoftwareVersionSettings; public const string UniqueKey = "Unique"; diff --git a/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs b/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs index 13c6fd56e..966add37f 100644 --- a/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs @@ -1,5 +1,6 @@ using ByteSync.Common.Business.EndPoints; using ByteSync.ServerCommon.Business.Auth; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -16,7 +17,7 @@ public ClientsRepository(ICacheService cacheService, IClientsGroupIdFactory clie _clientsGroupIdFactory = clientsGroupIdFactory; } - public override string ElementName => "Client"; + public override EntityType EntityType => EntityType.Client; public Task Get(ByteSyncEndpoint byteSyncEndpoint) { diff --git a/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs b/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs index ee78bc739..1aa53c6e0 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs @@ -10,5 +10,5 @@ public CloudSessionProfileRepository(ICacheService cacheService) : base(cacheSer { } - public override string ElementName { get; } = "CloudSessionProfile"; + public override EntityType EntityType { get; } = EntityType.CloudSessionProfile; } \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs index c967dfdb4..134c8fe9c 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs @@ -1,6 +1,8 @@ using ByteSync.Common.Helpers; using ByteSync.ServerCommon.Business.Auth; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; using StackExchange.Redis; @@ -14,12 +16,12 @@ public CloudSessionsRepository(ICacheService cacheService) : base(cacheService) } - private string ComputeSessionCacheKey(CloudSessionData cloudSessionData) + private CacheKey ComputeSessionCacheKey(CloudSessionData cloudSessionData) { - return ComputeCacheKey("Session", cloudSessionData.SessionId); + return _cacheService.ComputeCacheKey(EntityType, cloudSessionData.SessionId); } - public override string ElementName => "Session"; + public override EntityType EntityType => EntityType.Session; public Task GetSessionMember(string sessionId, Client client) { @@ -51,7 +53,7 @@ public async Task AddCloudSession(CloudSessionData cloudSessio var cacheKey = ComputeSessionCacheKey(cloudSessionData); await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); - string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey); + string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement == null || serializedElement.IsEmpty()) { await SetElement(cacheKey, cloudSessionData, transaction); diff --git a/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs b/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs index 40d6237ac..c5fdaf39a 100644 --- a/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs @@ -1,4 +1,5 @@ using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -10,7 +11,7 @@ public InventoryRepository(ICacheService cacheService) : base(cacheService) { } - public override string ElementName { get; } = "Inventory"; + public override EntityType EntityType { get; } = EntityType.Inventory; public async Task GetInventoryMember(string sessionId, string clientInstanceId) { diff --git a/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs b/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs index 9cb085376..ade29441d 100644 --- a/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs @@ -12,11 +12,11 @@ public LobbyRepository(ICacheService cacheService) : base(cacheService) { } - public override string ElementName { get; } = "Lobby"; + public override EntityType EntityType { get; } = EntityType.Lobby; public async Task> QuitLobby(string lobbyId, string clientInstanceId, ITransaction transaction) { - var cacheKey = ComputeCacheKey(ElementName, lobbyId); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, lobbyId); await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); var lobby = await GetCachedElement(cacheKey); @@ -41,7 +41,7 @@ public async Task> QuitLobby(string lobbyId, string cl if (deleteLobby) { - await transaction.KeyDeleteAsync(cacheKey); + await transaction.KeyDeleteAsync(cacheKey.Value); return new UpdateEntityResult(lobby, UpdateEntityStatus.Deleted); } diff --git a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs index c4641769f..4c5c86155 100644 --- a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs @@ -1,5 +1,7 @@ using ByteSync.Common.Business.SharedFiles; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Exceptions; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -12,32 +14,32 @@ public SharedFilesRepository(ICacheService cacheService) : base(cacheService) { } - private string ComputeSharedFileCacheKey(SharedFileDefinition sharedFileDefinition) + private CacheKey ComputeSharedFileCacheKey(SharedFileDefinition sharedFileDefinition) { return ComputeSharedFileCacheKey(sharedFileDefinition.Id); } - private string ComputeSharedFileCacheKey(string sharedFileDefinitionId) + private CacheKey ComputeSharedFileCacheKey(string sharedFileDefinitionId) { - return ComputeCacheKey("SharedFile", sharedFileDefinitionId); + return _cacheService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinitionId); } - public override string ElementName { get; } = "SharedFile"; + public override EntityType EntityType { get; } = EntityType.SharedFile; - private string ComputeSessionSharedFilesKey(SharedFileDefinition sharedFileDefinition) + private CacheKey ComputeSessionSharedFilesKey(SharedFileDefinition sharedFileDefinition) { return ComputeSessionSharedFilesKey(sharedFileDefinition.SessionId); } - private string ComputeSessionSharedFilesKey(string sessionId) + private CacheKey ComputeSessionSharedFilesKey(string sessionId) { - return ComputeCacheKey("SessionSharedFiles", sessionId); + return _cacheService.ComputeCacheKey(EntityType.SessionSharedFiles, sessionId); } public async Task AddOrUpdate(SharedFileDefinition sharedFileDefinition, Func updateHandler) { - string sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); - string sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); + var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); + var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); var database = _cacheService.GetDatabase(); @@ -47,13 +49,13 @@ public async Task AddOrUpdate(SharedFileDefinition sharedFileDefinition, Func> Clear(string sessionId) { List result = new List(); - string sessionSharedFilesKey = ComputeSessionSharedFilesKey(sessionId); + var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sessionId); var database = _cacheService.GetDatabase(); await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); - var redisValues = await database.SetMembersAsync(sessionSharedFilesKey); + var redisValues = await database.SetMembersAsync(sessionSharedFilesKey.Value); List sharedFileDefinitionIds = redisValues.Select(value => value.ToString()).ToList(); foreach (var sharedFileDefinitionId in sharedFileDefinitionIds) @@ -89,11 +91,11 @@ public async Task> Clear(string sessionId) { result.Add(sharedFileData); - await database.KeyDeleteAsync(sharedFileCacheKey); + await database.KeyDeleteAsync(sharedFileCacheKey.Value); } } - await database.KeyDeleteAsync(sessionSharedFilesKey); + await database.KeyDeleteAsync(sessionSharedFilesKey.Value); return result; } diff --git a/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs b/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs index 58bc605eb..4484bc34c 100644 --- a/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs @@ -14,7 +14,7 @@ public SynchronizationRepository(ICacheService cacheService, IActionsGroupDefini _actionsGroupDefinitionsRepository = actionsGroupDefinitionsRepository; } - public override string ElementName => "Synchronization"; + public override EntityType EntityType => EntityType.Synchronization; public async Task AddSynchronization(SynchronizationEntity synchronizationEntity, List actionsGroupDefinitions) { diff --git a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs index 4a84fac5b..6a71f046d 100644 --- a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs @@ -25,17 +25,18 @@ public TrackingActionRepository(ICacheService cacheService, IActionsGroupDefinit _logger = logger; } - public override string ElementName => "TrackingAction"; + public override EntityType EntityType => EntityType.TrackingAction; public async Task GetOrBuild(string sessionId, string actionsGroupId) { - var cacheKey = ComputeCacheKey(ElementName, $"{sessionId}_{actionsGroupId}"); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, $"{sessionId}_{actionsGroupId}"); + await using var actionsGroupIdLock = await _cacheService.AcquireLockAsync(cacheKey); return await DoGetOrBuild(sessionId, actionsGroupId, cacheKey); } - private async Task DoGetOrBuild(string sessionId, string actionsGroupId, string cacheKey) + private async Task DoGetOrBuild(string sessionId, string actionsGroupId, CacheKey cacheKey) { var trackingActionEntity = await Get($"{sessionId}_{actionsGroupId}"); @@ -52,7 +53,7 @@ private async Task DoGetOrBuild(string sessionId, string a public async Task AddOrUpdate(string sessionId, List actionsGroupIds, Func updateHandler) { - var synchronizationCacheKey = _synchronizationRepository.ComputeCacheKey(_synchronizationRepository.ElementName, sessionId); + var synchronizationCacheKey = _cacheService.ComputeCacheKey(EntityType.Synchronization, sessionId); await using var synchronizationLock = await _cacheService.AcquireLockAsync(synchronizationCacheKey); var synchronizationEntity = (await _synchronizationRepository.Get(sessionId))!; @@ -69,7 +70,7 @@ public async Task AddOrUpdate(string sessionId, List redisSettings, ILoggerFactory loggerFactory) + public CacheService(IOptions redisSettings, ICacheKeyFactory cacheKeyFactory, ILoggerFactory loggerFactory) { _redisSettings = redisSettings.Value; + _cacheKeyFactory = cacheKeyFactory; _connectionMultiplexer = ConnectionMultiplexer.Connect(_redisSettings.ConnectionString); @@ -31,7 +36,7 @@ public CacheService(IOptions redisSettings, ILoggerFactory logger _redLockFactory = RedLockFactory.Create(multiplexers, redLockRetryConfiguration, loggerFactory); } - public string Prefix => _redisSettings.Prefix; + // public string Prefix => _redisSettings.Prefix; public ITransaction OpenTransaction() { @@ -50,9 +55,16 @@ public IDatabaseAsync GetDatabase(ITransaction? transaction) return database; } - public async Task AcquireLockAsync(string key) + public async Task AcquireLockAsync(EntityType entityType, string entityId) { - var redisLock = await _redLockFactory.CreateLockAsync(key, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(1)); + var cacheKey = ComputeCacheKey(entityType, entityId); + + return await AcquireLockAsync(cacheKey); + } + + public async Task AcquireLockAsync(CacheKey cacheKey) + { + var redisLock = await _redLockFactory.CreateLockAsync(cacheKey.Value, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(1)); if (redisLock.IsAcquired) { @@ -60,7 +72,14 @@ public async Task AcquireLockAsync(string key) } else { - throw new AcquireRedisLockException(key, redisLock); + throw new AcquireRedisLockException(cacheKey.Value, redisLock); } } + + public CacheKey ComputeCacheKey(EntityType entityType, string entityId) + { + CacheKey cacheKey = _cacheKeyFactory.Create(entityType, entityId); + + return cacheKey; + } } \ No newline at end of file diff --git a/tests/ByteSync.ServerCommon.Tests/Helpers/TestSettingsInitializer.cs b/tests/ByteSync.ServerCommon.Tests/Helpers/TestSettingsInitializer.cs index b74a395de..8c136b5ed 100644 --- a/tests/ByteSync.ServerCommon.Tests/Helpers/TestSettingsInitializer.cs +++ b/tests/ByteSync.ServerCommon.Tests/Helpers/TestSettingsInitializer.cs @@ -1,7 +1,5 @@ -using Azure.Identity; -using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Business.Settings; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.AzureAppConfiguration; namespace ByteSync.ServerCommon.Tests.Helpers; diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs index ad378e77f..005e5c16f 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs @@ -1,11 +1,10 @@ using ByteSync.Common.Business.SharedFiles; using ByteSync.ServerCommon.Business.Sessions; -using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Factories; using ByteSync.ServerCommon.Repositories; using ByteSync.ServerCommon.Services; using ByteSync.ServerCommon.Tests.Helpers; using FakeItEasy; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -29,8 +28,9 @@ public async Task AddOrUpdate_IntegrationTest() { // Arrange var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); var loggerFactoryMock = A.Fake(); - var cacheService = new CacheService(Options.Create(redisSettings), loggerFactoryMock); + var cacheService = new CacheService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); var repository = new SharedFilesRepository(cacheService); var sharedFileDefinition = new SharedFileDefinition diff --git a/tests/ByteSync.ServerCommon.Tests/Services/AuthServiceTests.cs b/tests/ByteSync.ServerCommon.Tests/Services/AuthServiceTests.cs index 1210a2dce..3fbbd2e54 100644 --- a/tests/ByteSync.ServerCommon.Tests/Services/AuthServiceTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Services/AuthServiceTests.cs @@ -6,11 +6,8 @@ using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Settings; using ByteSync.ServerCommon.Interfaces.Factories; -using ByteSync.ServerCommon.Interfaces.Misc; using ByteSync.ServerCommon.Interfaces.Repositories; -using ByteSync.ServerCommon.Interfaces.Services; using ByteSync.ServerCommon.Interfaces.Services.Clients; -using ByteSync.ServerCommon.Services; using ByteSync.ServerCommon.Services.Clients; using FakeItEasy; diff --git a/tests/ByteSync.ServerCommon.Tests/Services/ClientSoftwareVersionServiceTests.cs b/tests/ByteSync.ServerCommon.Tests/Services/ClientSoftwareVersionServiceTests.cs index 1fe2deaa0..4d82838e4 100644 --- a/tests/ByteSync.ServerCommon.Tests/Services/ClientSoftwareVersionServiceTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Services/ClientSoftwareVersionServiceTests.cs @@ -3,7 +3,6 @@ using ByteSync.ServerCommon.Business.Settings; using ByteSync.ServerCommon.Interfaces.Loaders; using ByteSync.ServerCommon.Interfaces.Repositories; -using ByteSync.ServerCommon.Services; using ByteSync.ServerCommon.Services.Clients; using FakeItEasy; using FluentAssertions; diff --git a/tests/ByteSync.ServerCommon.Tests/Services/SynchronizationServiceTests.cs b/tests/ByteSync.ServerCommon.Tests/Services/SynchronizationServiceTests.cs index 7ea422efa..9491afaf8 100644 --- a/tests/ByteSync.ServerCommon.Tests/Services/SynchronizationServiceTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Services/SynchronizationServiceTests.cs @@ -1,5 +1,4 @@ using ByteSync.Common.Business.Actions; -using ByteSync.Common.Business.Sessions.Cloud; using ByteSync.Common.Business.SharedFiles; using ByteSync.Common.Business.Synchronizations; using ByteSync.ServerCommon.Business.Auth; From 306e1be3392d292270c24965e2a97ca7d39c33e7 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 9 Apr 2025 20:02:19 +0200 Subject: [PATCH 04/22] refactor: cleanup --- .../Repositories/BaseRepository.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs index f616461bb..0cb11d706 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -18,24 +18,10 @@ protected BaseRepository(ICacheService cacheService) _cacheService = cacheService; } - // private string Prefix => _cacheService.Prefix; - public abstract EntityType EntityType { get; } private TimeSpan Expiry => TimeSpan.FromDays(2); - // public string ComputeCacheKey(params string[] keyParts) - // { - // StringBuilder sb = new StringBuilder(Prefix); - // - // foreach (var keyPart in keyParts) - // { - // sb.Append($":{keyPart}"); - // } - // - // return sb.ToString(); - // } - public Task Get(string key) { return Get(key, null); @@ -155,7 +141,7 @@ public async Task> Save(CacheKey cacheKey, T element, ITra protected async Task GetCachedElement(CacheKey cacheKey) { - T? cachedElement = default(T); + T? cachedElement = null; string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement.IsNotEmpty()) From 8f2e56a1c2a357c453d2bc0a14aa87f461ab1c3d Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 9 Apr 2025 20:03:54 +0200 Subject: [PATCH 05/22] refactor: cleanup --- src/ByteSync.ServerCommon/Services/CacheService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ByteSync.ServerCommon/Services/CacheService.cs b/src/ByteSync.ServerCommon/Services/CacheService.cs index faeffdb74..5b9cd028c 100644 --- a/src/ByteSync.ServerCommon/Services/CacheService.cs +++ b/src/ByteSync.ServerCommon/Services/CacheService.cs @@ -36,8 +36,6 @@ public CacheService(IOptions redisSettings, ICacheKeyFactory cach _redLockFactory = RedLockFactory.Create(multiplexers, redLockRetryConfiguration, loggerFactory); } - // public string Prefix => _redisSettings.Prefix; - public ITransaction OpenTransaction() { return _connectionMultiplexer.GetDatabase().CreateTransaction(); From 97737b6a47f86a6eec234b6ce7b17f047aaa9d44 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 9 Apr 2025 20:11:35 +0200 Subject: [PATCH 06/22] refactor: cleanup --- .../Interfaces/Repositories/IRepository.cs | 2 -- src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index feb6257df..66cc9ccb7 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -7,8 +7,6 @@ namespace ByteSync.ServerCommon.Interfaces.Repositories; public interface IRepository { - // string ComputeCacheKey(params string[] keyParts); - EntityType EntityType { get; } Task Get(string key); diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs index fb4447fd2..db11bc1e3 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs @@ -7,8 +7,6 @@ namespace ByteSync.ServerCommon.Interfaces.Services; public interface ICacheService { - // public string Prefix { get; } - ITransaction OpenTransaction(); IDatabaseAsync GetDatabase(); From 291dee866e6e6ce198e33a4a86575201a03994da Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 13:27:05 +0200 Subject: [PATCH 07/22] refactor: refactor BaseRepository / CacheRepository / RedisInfrastructureService --- .../CreateSessionCommandHandler.cs | 8 +- .../FinalizeJoinCloudSessionCommandHandler.cs | 11 +- .../QuitSessionCommandHandler.cs | 8 +- .../UpdateSessionSettingsCommandHandler.cs | 2 +- .../StartInventoryCommandHandler.cs | 12 +- .../Lobbies/QuitLobbyCommandHandler.cs | 8 +- .../Lobbies/TryJoinLobbyCommandHandler.cs | 8 +- .../Repositories/ICacheRepository.cs | 21 +++ .../Repositories/IClientsRepository.cs | 1 - .../Interfaces/Repositories/IRepository.cs | 8 +- .../Services/ICloudSessionsService.cs | 3 +- .../Interfaces/Services/IInventoryService.cs | 6 +- .../Interfaces/Services/ILobbyService.cs | 1 - ...vice.cs => IRedisInfrastructureService.cs} | 2 +- .../Repositories/BaseRepository.cs | 149 +++--------------- .../Repositories/CacheRepository.cs | 131 +++++++++++++++ ...ClientSoftwareVersionSettingsRepository.cs | 3 +- .../Repositories/ClientsRepository.cs | 4 +- .../CloudSessionProfileRepository.cs | 3 +- .../Repositories/CloudSessionsRepository.cs | 15 +- .../Repositories/InventoryRepository.cs | 3 +- .../Repositories/LobbyRepository.cs | 14 +- .../Repositories/SharedFilesRepository.cs | 58 ++++--- .../Repositories/SynchronizationRepository.cs | 3 +- .../Repositories/TrackingActionRepository.cs | 33 ++-- .../Services/LobbyService.cs | 4 - ...rvice.cs => RedisInfrastructureService.cs} | 4 +- .../Autofac/BaseElementTypeModule.cs | 55 +++++++ .../TestHelpers/Autofac/RepositoriesModule.cs | 28 +++- .../CreateSessionCommandHandlerTests.cs | 8 +- ...lizeJoinCloudSessionCommandHandlerTests.cs | 8 +- .../QuitSessionCommandHandlerTests.cs | 8 +- ...pdateSessionSettingsCommandHandlerTests.cs | 6 +- .../StartInventoryCommandHandlerTests.cs | 6 +- .../SharedFilesRepositoryTests.cs | 85 +++++++++- 35 files changed, 468 insertions(+), 259 deletions(-) create mode 100644 src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs rename src/ByteSync.ServerCommon/Interfaces/Services/{ICacheService.cs => IRedisInfrastructureService.cs} (92%) create mode 100644 src/ByteSync.ServerCommon/Repositories/CacheRepository.cs rename src/ByteSync.ServerCommon/Services/{CacheService.cs => RedisInfrastructureService.cs} (92%) diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/CreateSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/CreateSessionCommandHandler.cs index acd2513fa..5c18d81a2 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/CreateSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/CreateSessionCommandHandler.cs @@ -14,17 +14,17 @@ public class CreateSessionCommandHandler : IRequestHandler _logger; public CreateSessionCommandHandler(ICloudSessionsRepository cloudSessionsRepository, IClientsGroupsService clientsGroupsService, - IClientsRepository clientsRepository, ICloudSessionsService cloudSessionsService, ICacheService cacheService, + IClientsRepository clientsRepository, ICloudSessionsService cloudSessionsService, IRedisInfrastructureService redisInfrastructureService, ILogger logger) { _cloudSessionsRepository = cloudSessionsRepository; _clientsGroupsService = clientsGroupsService; _cloudSessionsService = cloudSessionsService; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _logger = logger; } @@ -33,7 +33,7 @@ public async Task Handle(CreateSessionRequest request, Cance var createCloudSessionParameters = request.CreateCloudSessionParameters; var client = request.Client; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); CloudSessionData cloudSessionData; SessionMemberData creatorData; diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs index 6efc7c3d7..d5221c411 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs @@ -3,7 +3,6 @@ using ByteSync.ServerCommon.Business.Sessions; using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Helpers; -using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Mappers; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -19,18 +18,18 @@ public class FinalizeJoinCloudSessionCommandHandler : IRequestHandler _logger; public FinalizeJoinCloudSessionCommandHandler(ICloudSessionsRepository cloudSessionsRepository, ISessionMemberMapper sessionMemberMapper, IInvokeClientsService invokeClientsService, IClientsGroupsService clientsGroupsService, - ICacheService cacheService, ILogger logger) + IRedisInfrastructureService redisInfrastructureService, ILogger logger) { _cloudSessionsRepository = cloudSessionsRepository; _invokeClientsService = invokeClientsService; _clientsGroupsService = clientsGroupsService; _sessionMemberMapper = sessionMemberMapper; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _logger = logger; } @@ -42,9 +41,9 @@ public async Task Handle(FinalizeJoinCloudSessionRequ FinalizeJoinSessionStatuses? finalizeJoinSessionStatus = null; SessionMemberData? joiner = null; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); - await using var sessionRedisLock = await _cacheService.AcquireLockAsync(EntityType.Session, parameters.SessionId); + await using var sessionRedisLock = await _redisInfrastructureService.AcquireLockAsync(EntityType.Session, parameters.SessionId); var updateResult = await _cloudSessionsRepository.Update(parameters.SessionId, innerCloudSessionData => { diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/QuitSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/QuitSessionCommandHandler.cs index acbdd6aa8..593a1ea6d 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/QuitSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/QuitSessionCommandHandler.cs @@ -14,19 +14,19 @@ public class QuitSessionCommandHandler : IRequestHandler private readonly ICloudSessionsRepository _cloudSessionsRepository; private readonly IInventoryRepository _inventoryRepository; private readonly ISynchronizationRepository _synchronizationRepository; - private readonly ICacheService _cacheService; + private readonly IRedisInfrastructureService _redisInfrastructureService; private readonly ISessionMemberMapper _sessionMemberMapper; private readonly IClientsGroupsService _clientsGroupsService; private readonly IInvokeClientsService _invokeClientsService; public QuitSessionCommandHandler(ICloudSessionsRepository cloudSessionsRepository, IInventoryRepository inventoryRepository, - ISynchronizationRepository synchronizationRepository, ICacheService cacheService, ISessionMemberMapper sessionMemberMapper, + ISynchronizationRepository synchronizationRepository, IRedisInfrastructureService redisInfrastructureService, ISessionMemberMapper sessionMemberMapper, IClientsGroupsService clientsGroupsService, IInvokeClientsService invokeClientsService) { _cloudSessionsRepository = cloudSessionsRepository; _inventoryRepository = inventoryRepository; _synchronizationRepository = synchronizationRepository; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _sessionMemberMapper = sessionMemberMapper; _clientsGroupsService = clientsGroupsService; _invokeClientsService = invokeClientsService; @@ -37,7 +37,7 @@ public async Task Handle(QuitSessionRequest request, CancellationToken cancellat CloudSessionData? innerCloudSessionData = null; SessionMemberData? innerQuitter = null; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); var updateSessionResult = await _cloudSessionsRepository.Update(request.SessionId, cloudSessionData => { diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/UpdateSessionSettingsCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/UpdateSessionSettingsCommandHandler.cs index 32b75d1bc..d2ae50113 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/UpdateSessionSettingsCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/UpdateSessionSettingsCommandHandler.cs @@ -17,7 +17,7 @@ public class UpdateSessionSettingsCommandHandler : IRequestHandler logger) { _cloudSessionsRepository = cloudSessionsRepository; diff --git a/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs index afe710e0a..630697b9c 100644 --- a/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/Inventories/StartInventoryCommandHandler.cs @@ -19,7 +19,7 @@ public class StartInventoryCommandHandler : IRequestHandler _logger; public StartInventoryCommandHandler( @@ -27,14 +27,14 @@ public StartInventoryCommandHandler( ICloudSessionsRepository cloudSessionsRepository, ISharedFilesService sharedFilesService, IInvokeClientsService invokeClientsService, - ICacheService cacheService, + IRedisInfrastructureService redisInfrastructureService, ILogger logger) { _inventoryRepository = inventoryRepository; _cloudSessionsRepository = cloudSessionsRepository; _sharedFilesService = sharedFilesService; _invokeClientsService = invokeClientsService; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _logger = logger; } @@ -43,11 +43,11 @@ public async Task Handle(StartInventoryRequest request, Ca var sessionId = request.SessionId; var client = request.Client; - await using var sessionRedisLock = await _cacheService.AcquireLockAsync(EntityType.Session, sessionId); + await using var sessionRedisLock = await _redisInfrastructureService.AcquireLockAsync(EntityType.Session, sessionId); - await using var inventoryRedisLock = await _cacheService.AcquireLockAsync(EntityType.Inventory, sessionId); + await using var inventoryRedisLock = await _redisInfrastructureService.AcquireLockAsync(EntityType.Inventory, sessionId); - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); UpdateEntityResult? inventoryUpdateResult = null; diff --git a/src/ByteSync.ServerCommon/Commands/Lobbies/QuitLobbyCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Lobbies/QuitLobbyCommandHandler.cs index 2e09f1b62..1a04dad2c 100644 --- a/src/ByteSync.ServerCommon/Commands/Lobbies/QuitLobbyCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/Lobbies/QuitLobbyCommandHandler.cs @@ -11,16 +11,16 @@ public class QuitLobbyCommandHandler : IRequestHandler private readonly ILobbyRepository _lobbyRepository; private readonly IClientsGroupsService _clientsGroupsService; private readonly IInvokeClientsService _invokeClientsService; - private readonly ICacheService _cacheService; + private readonly IRedisInfrastructureService _redisInfrastructureService; private readonly ILogger _logger; public QuitLobbyCommandHandler(ILobbyRepository lobbyRepository, IClientsGroupsService clientsGroupsService, IInvokeClientsService invokeClientsService, - ICacheService cacheService, ILogger logger) + IRedisInfrastructureService redisInfrastructureService, ILogger logger) { _lobbyRepository = lobbyRepository; _clientsGroupsService = clientsGroupsService; _invokeClientsService = invokeClientsService; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _logger = logger; } @@ -29,7 +29,7 @@ public async Task Handle(QuitLobbyRequest request, CancellationToken cance var lobbyId = request.LobbyId; var client = request.Client; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); var result = await _lobbyRepository.QuitLobby(lobbyId, client.ClientInstanceId, transaction); diff --git a/src/ByteSync.ServerCommon/Commands/Lobbies/TryJoinLobbyCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Lobbies/TryJoinLobbyCommandHandler.cs index cc8a29101..761f3232c 100644 --- a/src/ByteSync.ServerCommon/Commands/Lobbies/TryJoinLobbyCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/Lobbies/TryJoinLobbyCommandHandler.cs @@ -19,19 +19,19 @@ public class TryJoinLobbyCommandHandler : IRequestHandler _logger; public TryJoinLobbyCommandHandler(ICloudSessionProfileRepository cloudSessionProfileRepository, ILobbyRepository lobbyRepository, IInvokeClientsService invokeClientsService, IClientsGroupsService clientsGroupsService, ILobbyFactory lobbyFactory, - ICacheService cacheService, ILogger logger) + IRedisInfrastructureService redisInfrastructureService, ILogger logger) { _cloudSessionProfileRepository = cloudSessionProfileRepository; _lobbyRepository = lobbyRepository; _invokeClientsService = invokeClientsService; _clientsGroupsService = clientsGroupsService; _lobbyFactory = lobbyFactory; - _cacheService = cacheService; + _redisInfrastructureService = redisInfrastructureService; _logger = logger; } @@ -43,7 +43,7 @@ public async Task Handle(TryJoinLobbyRequest request, Cancellat JoinLobbyResult? joinLobbyResult = null; bool? isConnected = null; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); CloudSessionProfileEntity? cloudSessionProfile = null; diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs new file mode 100644 index 000000000..991c173fa --- /dev/null +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs @@ -0,0 +1,21 @@ +using ByteSync.ServerCommon.Business.Repositories; +using RedLockNet; +using StackExchange.Redis; + +namespace ByteSync.ServerCommon.Interfaces.Repositories; + +public interface ICacheRepository where T : class +{ + Task Get(CacheKey cacheKey, ITransaction? transaction = null); + + Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null); + + Task> Update(CacheKey cacheKey, Func updateHandler, bool throwIfNotExists, ITransaction? transaction = null, + IRedLock? redisLock = null); + + Task> AddOrUpdate(CacheKey cacheKey, Func handler, ITransaction? transaction = null); + + Task Delete(CacheKey cacheKey, ITransaction? transaction = null); + + Task AcquireLockAsync(CacheKey cacheKey); +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IClientsRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IClientsRepository.cs index 86fffed47..8f374a238 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IClientsRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IClientsRepository.cs @@ -1,6 +1,5 @@ using ByteSync.Common.Business.EndPoints; using ByteSync.ServerCommon.Business.Auth; -using StackExchange.Redis; namespace ByteSync.ServerCommon.Interfaces.Repositories; diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index 66cc9ccb7..7729b0879 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -11,7 +11,7 @@ public interface IRepository Task Get(string key); - Task Get(string key, ITransaction? transaction); + Task Get(CacheKey cacheKey); Task> AddOrUpdate(string key, Func handler); @@ -21,11 +21,11 @@ public interface IRepository Task> UpdateIfExists(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null); + Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> Save(string key, T element, ITransaction? transaction = null); + Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); + // Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); Task Delete(string key); diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICloudSessionsService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ICloudSessionsService.cs index a6a6e32fe..f368ce604 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICloudSessionsService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/ICloudSessionsService.cs @@ -1,5 +1,4 @@ -using ByteSync.Common.Business.Sessions; -using ByteSync.Common.Business.Sessions.Cloud; +using ByteSync.Common.Business.Sessions.Cloud; using ByteSync.Common.Business.Sessions.Cloud.Connections; using ByteSync.ServerCommon.Business.Auth; using ByteSync.ServerCommon.Business.Sessions; diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/IInventoryService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/IInventoryService.cs index cfa017996..086821d71 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/IInventoryService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/IInventoryService.cs @@ -1,8 +1,4 @@ -using ByteSync.Common.Business.Inventories; -using ByteSync.Common.Business.Sessions.Cloud; -using ByteSync.ServerCommon.Business.Auth; - -namespace ByteSync.ServerCommon.Interfaces.Services; +namespace ByteSync.ServerCommon.Interfaces.Services; public interface IInventoryService { diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ILobbyService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ILobbyService.cs index 31678cdfc..b7ece7396 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ILobbyService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/ILobbyService.cs @@ -1,5 +1,4 @@ using ByteSync.Common.Business.Lobbies; -using ByteSync.Common.Business.Lobbies.Connections; using ByteSync.ServerCommon.Business.Auth; namespace ByteSync.ServerCommon.Interfaces.Services; diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/IRedisInfrastructureService.cs similarity index 92% rename from src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs rename to src/ByteSync.ServerCommon/Interfaces/Services/IRedisInfrastructureService.cs index db11bc1e3..7bfcf9921 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Services/IRedisInfrastructureService.cs @@ -5,7 +5,7 @@ namespace ByteSync.ServerCommon.Interfaces.Services; -public interface ICacheService +public interface IRedisInfrastructureService { ITransaction OpenTransaction(); diff --git a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs index 0cb11d706..0d32b9076 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -1,6 +1,4 @@ -using ByteSync.Common.Controls.Json; -using ByteSync.Common.Helpers; -using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -11,28 +9,26 @@ namespace ByteSync.ServerCommon.Repositories; public abstract class BaseRepository : IRepository where T : class { - protected readonly ICacheService _cacheService; + protected readonly IRedisInfrastructureService _cacheService; + protected readonly ICacheRepository _cacheRepository; - protected BaseRepository(ICacheService cacheService) + protected BaseRepository(IRedisInfrastructureService cacheService, ICacheRepository cacheRepository) { _cacheService = cacheService; + _cacheRepository = cacheRepository; } public abstract EntityType EntityType { get; } - private TimeSpan Expiry => TimeSpan.FromDays(2); - - public Task Get(string key) + public async Task Get(string key) { - return Get(key, null); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + return await Get(cacheKey); } - public async Task Get(string key, ITransaction? transaction) + public async Task Get(CacheKey cacheKey) { - var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); - - var cachedElement = await GetCachedElement(cacheKey); - return cachedElement; + return await _cacheRepository.Get(cacheKey); } public async Task> AddOrUpdate(string key, Func handler) @@ -43,132 +39,39 @@ public async Task> AddOrUpdate(string key, Func ha public async Task> AddOrUpdate(string key, Func handler, ITransaction? transaction) { var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); - - var cachedElement = await GetCachedElement(cacheKey); - var createdOrUpdatedElement = handler.Invoke(cachedElement); - - if (createdOrUpdatedElement == null) - { - return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); - } - else - { - return await SetElement(cacheKey, createdOrUpdatedElement, database); - } + return await _cacheRepository.AddOrUpdate(cacheKey, handler, transaction); } public async Task> Update(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null) { - var updateEntityResult = await DoUpdate(key, updateHandler, true, transaction, redisLock); - - return updateEntityResult; + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + return await _cacheRepository.Update(cacheKey, updateHandler, true, transaction, redisLock); } public async Task> UpdateIfExists(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null) - { - var updateEntityResult = await DoUpdate(key, updateHandler, false, transaction, redisLock); - - return updateEntityResult; - } - - private async Task> DoUpdate(string key, Func updateHandler, bool throwIfNotExists, ITransaction? transaction, - IRedLock? redisLockParam) { var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - - IRedLock? redisLock = redisLockParam; - bool shouldDispose = false; - - try - { - if (redisLock == null) - { - redisLock = await _cacheService.AcquireLockAsync(cacheKey); - shouldDispose = true; - } - - var cachedElement = await GetCachedElement(cacheKey); - - if (cachedElement == null) - { - if (throwIfNotExists) - { - throw new Exception("Could not find element to update"); - } - else - { - return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NotFound); - } - } - - bool isUpdateDone = updateHandler.Invoke(cachedElement); - if (!isUpdateDone) - { - return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); - } - else - { - return await SetElement(cacheKey, cachedElement, database); - } - } - finally - { - if (shouldDispose && redisLock != null) - { - await redisLock.DisposeAsync(); - } - } + return await _cacheRepository.Update(cacheKey, updateHandler, false, transaction, redisLock); } - - public async Task> Save(string key, T element, ITransaction? transaction = null) + + public async Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null) { var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); - return await Save(cacheKey, element, transaction); + return await _cacheRepository.Save(cacheKey, element, transaction, redisLock); } - public async Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null) - { - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - - await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); - return await SetElement(cacheKey, element, database); - } - - protected async Task GetCachedElement(CacheKey cacheKey) + public async Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null) { - T? cachedElement = null; - - string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey.Value); - if (serializedElement.IsNotEmpty()) - { - cachedElement = JsonHelper.Deserialize(serializedElement!); - } - - return cachedElement; + return await _cacheRepository.Save(cacheKey, element, transaction, redisLock); } - public async Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) - { - string serializedElement = JsonHelper.Serialize(createdOrUpdatedElement); - - if (database is ITransaction) - { - _ = database.StringSetAsync(cacheKey.Value, serializedElement, Expiry); - - return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.WaitingForTransaction); - } - else - { - await database.StringSetAsync(cacheKey.Value, serializedElement, Expiry); - - return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.Saved); - } - } + // public Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) + // { + // // Cette méthode est maintenue pour compatibilité avec l'interface + // throw new NotImplementedException("Cette méthode est dépréciée. Utilisez Save à la place."); + // } public async Task Delete(string key) { @@ -178,8 +81,6 @@ public async Task Delete(string key) public async Task Delete(string key, ITransaction? transaction) { var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - - await database.KeyDeleteAsync(cacheKey.Value); + await _cacheRepository.Delete(cacheKey, transaction); } } \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs b/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs new file mode 100644 index 000000000..ed5f6ace7 --- /dev/null +++ b/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs @@ -0,0 +1,131 @@ +using ByteSync.Common.Controls.Json; +using ByteSync.Common.Helpers; +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Interfaces.Repositories; +using ByteSync.ServerCommon.Interfaces.Services; +using RedLockNet; +using StackExchange.Redis; + +namespace ByteSync.ServerCommon.Repositories; + +public class CacheRepository : ICacheRepository where T : class +{ + private readonly IRedisInfrastructureService _redisInfrastructureService; + private readonly TimeSpan _expiry = TimeSpan.FromDays(2); + + public CacheRepository(IRedisInfrastructureService redisInfrastructureService) + { + _redisInfrastructureService = redisInfrastructureService; + } + + public async Task Get(CacheKey cacheKey, ITransaction? transaction = null) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + string? serializedElement = await database.StringGetAsync(cacheKey.Value); + + if (serializedElement.IsNullOrEmpty()) + { + return null; + } + + return JsonHelper.Deserialize(serializedElement!); + } + + public async Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + bool shouldDispose = redisLock == null; + redisLock ??= await _redisInfrastructureService.AcquireLockAsync(cacheKey); + + try + { + return await SaveInternal(cacheKey, element, database); + } + finally + { + if (shouldDispose) + { + await redisLock.DisposeAsync(); + } + } + } + + public async Task> Update(CacheKey cacheKey, Func updateHandler, bool throwIfNotExists, + ITransaction? transaction = null, IRedLock? redisLock = null) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + bool shouldDispose = redisLock == null; + redisLock ??= await _redisInfrastructureService.AcquireLockAsync(cacheKey); + + try + { + var cachedElement = await Get(cacheKey, transaction); + + if (cachedElement == null) + { + if (throwIfNotExists) + { + throw new Exception("Could not find element to update"); + } + return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NotFound); + } + + bool isUpdateDone = updateHandler.Invoke(cachedElement); + if (!isUpdateDone) + { + return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); + } + + return await SaveInternal(cacheKey, cachedElement, database); + } + finally + { + if (shouldDispose) + { + await redisLock.DisposeAsync(); + } + } + } + + public async Task> AddOrUpdate(CacheKey cacheKey, Func handler, ITransaction? transaction = null) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + + await using var redisLock = await _redisInfrastructureService.AcquireLockAsync(cacheKey); + + var cachedElement = await Get(cacheKey, transaction); + var createdOrUpdatedElement = handler.Invoke(cachedElement); + + if (createdOrUpdatedElement == null) + { + return new UpdateEntityResult(cachedElement, UpdateEntityStatus.NoOperation); + } + + return await SaveInternal(cacheKey, createdOrUpdatedElement, database); + } + + public async Task Delete(CacheKey cacheKey, ITransaction? transaction = null) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + await database.KeyDeleteAsync(cacheKey.Value); + } + + public async Task AcquireLockAsync(CacheKey cacheKey) + { + return await _redisInfrastructureService.AcquireLockAsync(cacheKey); + } + + private async Task> SaveInternal(CacheKey cacheKey, T element, IDatabaseAsync database) + { + string serializedElement = JsonHelper.Serialize(element); + + if (database is ITransaction) + { + _ = database.StringSetAsync(cacheKey.Value, serializedElement, _expiry); + return new UpdateEntityResult(element, UpdateEntityStatus.WaitingForTransaction); + } + + await database.StringSetAsync(cacheKey.Value, serializedElement, _expiry); + return new UpdateEntityResult(element, UpdateEntityStatus.Saved); + } +} \ No newline at end of file diff --git a/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs b/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs index e150a6a5c..a72101c8c 100644 --- a/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/ClientSoftwareVersionSettingsRepository.cs @@ -7,7 +7,8 @@ namespace ByteSync.ServerCommon.Repositories; public class ClientSoftwareVersionSettingsRepository : BaseRepository, IClientSoftwareVersionSettingsRepository { - public ClientSoftwareVersionSettingsRepository(ICacheService cacheService) : base(cacheService) + public ClientSoftwareVersionSettingsRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } diff --git a/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs b/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs index 966add37f..bc6ceeb6e 100644 --- a/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs @@ -4,7 +4,6 @@ using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; -using StackExchange.Redis; namespace ByteSync.ServerCommon.Repositories; @@ -12,7 +11,8 @@ public class ClientsRepository : BaseRepository, IClientsRepository { private readonly IClientsGroupIdFactory _clientsGroupIdFactory; - public ClientsRepository(ICacheService cacheService, IClientsGroupIdFactory clientsGroupIdFactory) : base(cacheService) + public ClientsRepository(IRedisInfrastructureService redisInfrastructureService, ICacheRepository cacheRepository, + IClientsGroupIdFactory clientsGroupIdFactory) : base(redisInfrastructureService, cacheRepository) { _clientsGroupIdFactory = clientsGroupIdFactory; } diff --git a/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs b/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs index 1aa53c6e0..0e3f400bf 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs @@ -6,7 +6,8 @@ namespace ByteSync.ServerCommon.Repositories; public class CloudSessionProfileRepository : BaseRepository, ICloudSessionProfileRepository { - public CloudSessionProfileRepository(ICacheService cacheService) : base(cacheService) + public CloudSessionProfileRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } diff --git a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs index 134c8fe9c..40bbd9067 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs @@ -11,14 +11,17 @@ namespace ByteSync.ServerCommon.Repositories; public class CloudSessionsRepository : BaseRepository, ICloudSessionsRepository { - public CloudSessionsRepository(ICacheService cacheService) : base(cacheService) - { + private readonly IRedisInfrastructureService _redisInfrastructureService; + public CloudSessionsRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) + { + _redisInfrastructureService = redisInfrastructureService; } private CacheKey ComputeSessionCacheKey(CloudSessionData cloudSessionData) { - return _cacheService.ComputeCacheKey(EntityType, cloudSessionData.SessionId); + return _redisInfrastructureService.ComputeCacheKey(EntityType, cloudSessionData.SessionId); } public override EntityType EntityType => EntityType.Session; @@ -51,12 +54,12 @@ public async Task AddCloudSession(CloudSessionData cloudSessio cloudSessionData.SessionId = generateSessionIdHandler.Invoke(); var cacheKey = ComputeSessionCacheKey(cloudSessionData); - await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); + await using var redisLock = await _redisInfrastructureService.AcquireLockAsync(cacheKey); - string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey.Value); + string? serializedElement = await _redisInfrastructureService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement == null || serializedElement.IsEmpty()) { - await SetElement(cacheKey, cloudSessionData, transaction); + await Save(cacheKey, cloudSessionData, transaction); isNewSessionOk = true; } } diff --git a/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs b/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs index c5fdaf39a..bb3d47c78 100644 --- a/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs @@ -7,7 +7,8 @@ namespace ByteSync.ServerCommon.Repositories; public class InventoryRepository : BaseRepository, IInventoryRepository { - public InventoryRepository(ICacheService cacheService) : base(cacheService) + public InventoryRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } diff --git a/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs b/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs index ade29441d..4416c9b0c 100644 --- a/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/LobbyRepository.cs @@ -8,18 +8,22 @@ namespace ByteSync.ServerCommon.Repositories; public class LobbyRepository : BaseRepository, ILobbyRepository { - public LobbyRepository(ICacheService cacheService) : base(cacheService) + private readonly IRedisInfrastructureService _redisInfrastructureService; + + public LobbyRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { + _redisInfrastructureService = redisInfrastructureService; } public override EntityType EntityType { get; } = EntityType.Lobby; public async Task> QuitLobby(string lobbyId, string clientInstanceId, ITransaction transaction) { - var cacheKey = _cacheService.ComputeCacheKey(EntityType, lobbyId); - await using var redisLock = await _cacheService.AcquireLockAsync(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType, lobbyId); + await using var redisLock = await _redisInfrastructureService.AcquireLockAsync(cacheKey); - var lobby = await GetCachedElement(cacheKey); + var lobby = await Get(cacheKey); bool updateLobby = false; bool deleteLobby = false; @@ -47,7 +51,7 @@ public async Task> QuitLobby(string lobbyId, string cl } else if (updateLobby) { - return await SetElement(cacheKey, lobby!, transaction); + return await Save(cacheKey, lobby!, transaction); } else { diff --git a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs index 4c5c86155..e815e4a41 100644 --- a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs @@ -2,7 +2,6 @@ using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Sessions; using ByteSync.ServerCommon.Entities; -using ByteSync.ServerCommon.Exceptions; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -10,8 +9,12 @@ namespace ByteSync.ServerCommon.Repositories; public class SharedFilesRepository : BaseRepository, ISharedFilesRepository { - public SharedFilesRepository(ICacheService cacheService) : base(cacheService) + private readonly IRedisInfrastructureService _redisInfrastructureService; + + public SharedFilesRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { + _redisInfrastructureService = redisInfrastructureService; } private CacheKey ComputeSharedFileCacheKey(SharedFileDefinition sharedFileDefinition) @@ -21,7 +24,7 @@ private CacheKey ComputeSharedFileCacheKey(SharedFileDefinition sharedFileDefini private CacheKey ComputeSharedFileCacheKey(string sharedFileDefinitionId) { - return _cacheService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinitionId); + return _redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinitionId); } public override EntityType EntityType { get; } = EntityType.SharedFile; @@ -33,23 +36,27 @@ private CacheKey ComputeSessionSharedFilesKey(SharedFileDefinition sharedFileDef private CacheKey ComputeSessionSharedFilesKey(string sessionId) { - return _cacheService.ComputeCacheKey(EntityType.SessionSharedFiles, sessionId); + return _redisInfrastructureService.ComputeCacheKey(EntityType.SessionSharedFiles, sessionId); } public async Task AddOrUpdate(SharedFileDefinition sharedFileDefinition, Func updateHandler) { var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); - - var database = _cacheService.GetDatabase(); - await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); - await using var sharedFileLock = await _cacheService.AcquireLockAsync(sharedFileCacheKey); + var transaction = _redisInfrastructureService.OpenTransaction(); + // var transaction = _redisInfrastructureService.GetDatabase(); + + await using var sessionSharedFilesLock = await _redisInfrastructureService.AcquireLockAsync(sessionSharedFilesKey); + await using var sharedFileLock = await _redisInfrastructureService.AcquireLockAsync(sharedFileCacheKey); - var cachedElement = await GetCachedElement(sharedFileCacheKey); + var cachedElement = await Get(sharedFileCacheKey); var element = updateHandler.Invoke(cachedElement); - await SetElement(sharedFileCacheKey, element, database); - await database.SetAddAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + await Save(sharedFileCacheKey, element, transaction, sessionSharedFilesLock); + // await Save(sharedFileCacheKey, element, null, sessionSharedFilesLock); + _ = transaction.SetAddAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + + await transaction.ExecuteAsync(); } public async Task Forget(SharedFileDefinition sharedFileDefinition) @@ -57,14 +64,15 @@ public async Task Forget(SharedFileDefinition sharedFileDefinition) var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); - var database = _cacheService.GetDatabase(); + var transaction = _redisInfrastructureService.OpenTransaction(); - - await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); - await using var sharedFileLock = await _cacheService.AcquireLockAsync(sharedFileCacheKey); + await using var sessionSharedFilesLock = await _redisInfrastructureService.AcquireLockAsync(sessionSharedFilesKey); + await using var sharedFileLock = await _redisInfrastructureService.AcquireLockAsync(sharedFileCacheKey); - await database.KeyDeleteAsync(sharedFileCacheKey.Value); - await database.SetRemoveAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + _ = transaction.KeyDeleteAsync(sharedFileCacheKey.Value); + _ = transaction.SetRemoveAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + + await transaction.ExecuteAsync(); } public async Task> Clear(string sessionId) @@ -72,30 +80,32 @@ public async Task> Clear(string sessionId) List result = new List(); var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sessionId); - - var database = _cacheService.GetDatabase(); - await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); + await using var sessionSharedFilesLock = await _redisInfrastructureService.AcquireLockAsync(sessionSharedFilesKey); + var database = _redisInfrastructureService.GetDatabase(); var redisValues = await database.SetMembersAsync(sessionSharedFilesKey.Value); List sharedFileDefinitionIds = redisValues.Select(value => value.ToString()).ToList(); + var transaction = _redisInfrastructureService.OpenTransaction(); foreach (var sharedFileDefinitionId in sharedFileDefinitionIds) { var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinitionId); - await using var sharedFileLock = await _cacheService.AcquireLockAsync(sharedFileCacheKey); + await using var sharedFileLock = await _redisInfrastructureService.AcquireLockAsync(sharedFileCacheKey); - var sharedFileData = await GetCachedElement(sharedFileCacheKey); + var sharedFileData = await Get(sharedFileCacheKey); if (sharedFileData != null) { result.Add(sharedFileData); - await database.KeyDeleteAsync(sharedFileCacheKey.Value); + _ = transaction.KeyDeleteAsync(sharedFileCacheKey.Value); } } - await database.KeyDeleteAsync(sessionSharedFilesKey.Value); + _ = transaction.KeyDeleteAsync(sessionSharedFilesKey.Value); + + await transaction.ExecuteAsync(); return result; } diff --git a/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs b/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs index 4484bc34c..1126fe4cb 100644 --- a/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs @@ -9,7 +9,8 @@ public class SynchronizationRepository : BaseRepository, { private readonly IActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; - public SynchronizationRepository(ICacheService cacheService, IActionsGroupDefinitionsRepository actionsGroupDefinitionsRepository) : base(cacheService) + public SynchronizationRepository(IRedisInfrastructureService redisInfrastructureService, ICacheRepository cacheRepository, + IActionsGroupDefinitionsRepository actionsGroupDefinitionsRepository) : base(redisInfrastructureService, cacheRepository) { _actionsGroupDefinitionsRepository = actionsGroupDefinitionsRepository; } diff --git a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs index 6a71f046d..79ef59728 100644 --- a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs @@ -1,5 +1,4 @@ -using ByteSync.Common.Business.Actions; -using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Repositories; @@ -10,16 +9,16 @@ namespace ByteSync.ServerCommon.Repositories; public class TrackingActionRepository : BaseRepository, ITrackingActionRepository { - private readonly IActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; + private readonly IRedisInfrastructureService _redisInfrastructureService; private readonly ISynchronizationRepository _synchronizationRepository; private readonly ITrackingActionEntityFactory _trackingActionEntityFactory; private readonly ILogger _logger; - - public TrackingActionRepository(ICacheService cacheService, IActionsGroupDefinitionsRepository actionsGroupDefinitionsRepository, - ISynchronizationRepository synchronizationRepository, ITrackingActionEntityFactory trackingActionEntityFactory, - ILogger logger) : base(cacheService) + + public TrackingActionRepository(IRedisInfrastructureService redisInfrastructureService, ISynchronizationRepository synchronizationRepository, + ITrackingActionEntityFactory trackingActionEntityFactory, ICacheRepository cacheRepository, + ILogger logger) : base(redisInfrastructureService, cacheRepository) { - _actionsGroupDefinitionsRepository = actionsGroupDefinitionsRepository; + _redisInfrastructureService = redisInfrastructureService; _synchronizationRepository = synchronizationRepository; _trackingActionEntityFactory = trackingActionEntityFactory; _logger = logger; @@ -29,9 +28,9 @@ public TrackingActionRepository(ICacheService cacheService, IActionsGroupDefinit public async Task GetOrBuild(string sessionId, string actionsGroupId) { - var cacheKey = _cacheService.ComputeCacheKey(EntityType, $"{sessionId}_{actionsGroupId}"); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType, $"{sessionId}_{actionsGroupId}"); - await using var actionsGroupIdLock = await _cacheService.AcquireLockAsync(cacheKey); + await using var actionsGroupIdLock = await _redisInfrastructureService.AcquireLockAsync(cacheKey); return await DoGetOrBuild(sessionId, actionsGroupId, cacheKey); } @@ -53,11 +52,11 @@ private async Task DoGetOrBuild(string sessionId, string a public async Task AddOrUpdate(string sessionId, List actionsGroupIds, Func updateHandler) { - var synchronizationCacheKey = _cacheService.ComputeCacheKey(EntityType.Synchronization, sessionId); - await using var synchronizationLock = await _cacheService.AcquireLockAsync(synchronizationCacheKey); + var synchronizationCacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); + await using var synchronizationLock = await _redisInfrastructureService.AcquireLockAsync(synchronizationCacheKey); var synchronizationEntity = (await _synchronizationRepository.Get(sessionId))!; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); var locks = new List(); @@ -70,8 +69,8 @@ public async Task AddOrUpdate(string sessionId, List AddOrUpdate(string sessionId, List AddOrUpdate(string sessionId, List redisSettings, ICacheKeyFactory cacheKeyFactory, ILoggerFactory loggerFactory) + public RedisInfrastructureService(IOptions redisSettings, ICacheKeyFactory cacheKeyFactory, ILoggerFactory loggerFactory) { _redisSettings = redisSettings.Value; _cacheKeyFactory = cacheKeyFactory; diff --git a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs index 0757d71f7..dfd53f98c 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs @@ -23,6 +23,20 @@ protected override void Load(ContainerBuilder builder) .AsImplementedInterfaces() .AsSelf() .InstancePerLifetimeScope(); + + var genericRepositoryTypes_ = GlobalTestSetup.ByteSyncServerCommonAssembly.GetTypes() + .Where(t => t.Name.EndsWith(ElementsType) && t.IsGenericTypeDefinition); + + var genericRepositoryTypes = GlobalTestSetup.ByteSyncServerCommonAssembly.GetTypes() + .Where(t => t.Name.Contains(ElementsType + "`") && t.IsGenericTypeDefinition && !t.IsInterface); + + foreach (var genericType in genericRepositoryTypes) + { + builder.RegisterGeneric(genericType) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + } } else { @@ -36,5 +50,46 @@ protected override void Load(ContainerBuilder builder) .InstancePerLifetimeScope(); } } + + /* + foreach (var type in SpecificTypes) + { + // builder.Register(_ => Create.Fake(type)) + // .As(type) + // .AsImplementedInterfaces() + // .InstancePerLifetimeScope(); + + // builder.RegisterType(type) + // .AsImplementedInterfaces() + // .InstancePerLifetimeScope(); + // builder.Register(type) + // .As(type) + // .AsImplementedInterfaces() + // .InstancePerLifetimeScope(); + + + if (type.IsGenericTypeDefinition) + { + // Utiliser RegisterGeneric pour les types génériques ouverts + builder.RegisterGeneric(type) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + } + else + { + // Garder RegisterType pour les types concrets + builder.RegisterType(type) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + } + }*/ + } + + protected virtual IEnumerable SpecificTypes + { + get + { + return new List(); + } } } \ No newline at end of file diff --git a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs index af7381aa3..5154b961f 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs @@ -1,11 +1,35 @@ -namespace ByteSync.Functions.IntegrationTests.TestHelpers.Autofac; +using ByteSync.ServerCommon.Factories; +using ByteSync.ServerCommon.Interfaces.Repositories; +using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; + +namespace ByteSync.Functions.IntegrationTests.TestHelpers.Autofac; public class RepositoriesModule : BaseElementTypeModule { public RepositoriesModule(bool useConcrete) : base(useConcrete) { - + } protected override string ElementsType => "Repository"; + + protected override IEnumerable SpecificTypes + { + get + { + if (UseConcrete) + { + return [ + //typeof(RedisInfrastructureService), + typeof(CacheRepository<>), + //typeof(CacheKeyFactory) + ]; + } + else + { + return new List(); + } + } + } } \ No newline at end of file diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/CreateSessionCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/CreateSessionCommandHandlerTests.cs index f66f42a23..9f1f7b2ea 100644 --- a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/CreateSessionCommandHandlerTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/CreateSessionCommandHandlerTests.cs @@ -20,7 +20,7 @@ public class CreateSessionCommandHandlerTests private IClientsGroupsService _mockClientsGroupsService; private IClientsRepository _mockClientsRepository; private ICloudSessionsService _mockCloudSessionsService; - private ICacheService _mockCacheService; + private IRedisInfrastructureService _mockRedisInfrastructureService; private ILogger _mockLogger; private ITransaction _mockTransaction; private CreateSessionCommandHandler _createSessionCommandHandler; @@ -32,18 +32,18 @@ public void Setup() _mockClientsGroupsService = A.Fake(); _mockClientsRepository = A.Fake(); _mockCloudSessionsService = A.Fake(); - _mockCacheService = A.Fake(); + _mockRedisInfrastructureService = A.Fake(); _mockLogger = A.Fake>(); _mockTransaction = A.Fake(); - A.CallTo(() => _mockCacheService.OpenTransaction()).Returns(_mockTransaction); + A.CallTo(() => _mockRedisInfrastructureService.OpenTransaction()).Returns(_mockTransaction); _createSessionCommandHandler = new CreateSessionCommandHandler( _mockCloudSessionsRepository, _mockClientsGroupsService, _mockClientsRepository, _mockCloudSessionsService, - _mockCacheService, + _mockRedisInfrastructureService, _mockLogger); } diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandlerTests.cs index 4d19f7138..5f0f56d10 100644 --- a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandlerTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandlerTests.cs @@ -24,7 +24,7 @@ public class FinalizeJoinCloudSessionCommandHandlerTests private ISessionMemberMapper _mockSessionMemberMapper; private IInvokeClientsService _mockInvokeClientsService; private IClientsGroupsService _mockClientsGroupsService; - private ICacheService _mockCacheService; + private IRedisInfrastructureService _mockRedisInfrastructureService; private ILogger _mockLogger; private ITransaction _mockTransaction; private FinalizeJoinCloudSessionCommandHandler _handler; @@ -37,12 +37,12 @@ public void Setup() _mockSessionMemberMapper = A.Fake(); _mockInvokeClientsService = A.Fake(); _mockClientsGroupsService = A.Fake(); - _mockCacheService = A.Fake(); + _mockRedisInfrastructureService = A.Fake(); _mockLogger = A.Fake>(); _mockTransaction = A.Fake(); _byteSyncPush = A.Fake(); - A.CallTo(() => _mockCacheService.OpenTransaction()).Returns(_mockTransaction); + A.CallTo(() => _mockRedisInfrastructureService.OpenTransaction()).Returns(_mockTransaction); A.CallTo(() => _mockInvokeClientsService.SessionGroup(A.Ignored)) .Returns(_byteSyncPush); @@ -51,7 +51,7 @@ public void Setup() _mockSessionMemberMapper, _mockInvokeClientsService, _mockClientsGroupsService, - _mockCacheService, + _mockRedisInfrastructureService, _mockLogger); } diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/QuitSessionCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/QuitSessionCommandHandlerTests.cs index 92c47c6dd..f2e51d66a 100644 --- a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/QuitSessionCommandHandlerTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/QuitSessionCommandHandlerTests.cs @@ -24,7 +24,7 @@ public class QuitSessionCommandHandlerTests private ICloudSessionsRepository _mockCloudSessionsRepository; private IInventoryRepository _mockInventoryRepository; private ISynchronizationRepository _mockSynchronizationRepository; - private ICacheService _mockCacheService; + private IRedisInfrastructureService _mockRedisInfrastructureService; private ISessionMemberMapper _mockSessionMemberMapper; private IClientsGroupsService _mockClientsGroupsService; private IInvokeClientsService _mockInvokeClientsService; @@ -39,16 +39,16 @@ public void Setup() _mockCloudSessionsRepository = A.Fake(); _mockInventoryRepository = A.Fake(); _mockSynchronizationRepository = A.Fake(); - _mockCacheService = A.Fake(); + _mockRedisInfrastructureService = A.Fake(); _mockSessionMemberMapper = A.Fake(); _mockClientsGroupsService = A.Fake(); _mockInvokeClientsService = A.Fake(); _mockTransaction = A.Fake(); - A.CallTo(() => _mockCacheService.OpenTransaction()).Returns(_mockTransaction); + A.CallTo(() => _mockRedisInfrastructureService.OpenTransaction()).Returns(_mockTransaction); _quitSessionCommandHandler = new QuitSessionCommandHandler(_mockCloudSessionsRepository, _mockInventoryRepository, - _mockSynchronizationRepository, _mockCacheService, _mockSessionMemberMapper, _mockClientsGroupsService, _mockInvokeClientsService); + _mockSynchronizationRepository, _mockRedisInfrastructureService, _mockSessionMemberMapper, _mockClientsGroupsService, _mockInvokeClientsService); } [TestCase(true)] diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/UpdateSessionSettingsCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/UpdateSessionSettingsCommandHandlerTests.cs index fdf0266b7..7df1d75b9 100644 --- a/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/UpdateSessionSettingsCommandHandlerTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Commands/CloudSessions/UpdateSessionSettingsCommandHandlerTests.cs @@ -24,7 +24,7 @@ public class UpdateSessionSettingsCommandHandlerTests private ICloudSessionsRepository _mockCloudSessionsRepository; private IInventoryRepository _mockInventoryRepository; private ISynchronizationRepository _mockSynchronizationRepository; - private ICacheService _mockCacheService; + private IRedisInfrastructureService _mockRedisInfrastructureService; private ISessionMemberMapper _mockSessionMemberMapper; private IInvokeClientsService _mockInvokeClientsService; private ILogger _mockLogger; @@ -36,7 +36,7 @@ public void Setup() _mockCloudSessionsRepository = A.Fake(); _mockInventoryRepository = A.Fake(); _mockSynchronizationRepository = A.Fake(); - _mockCacheService = A.Fake(); + _mockRedisInfrastructureService = A.Fake(); _mockSessionMemberMapper = A.Fake(); _mockInvokeClientsService = A.Fake(); _mockLogger = A.Fake>(); @@ -45,7 +45,7 @@ public void Setup() _mockCloudSessionsRepository, _mockInventoryRepository, _mockSynchronizationRepository, - _mockCacheService, + _mockRedisInfrastructureService, _mockSessionMemberMapper, _mockInvokeClientsService, _mockLogger); diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/Inventories/StartInventoryCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/Inventories/StartInventoryCommandHandlerTests.cs index 63a018bee..211f566fb 100644 --- a/tests/ByteSync.ServerCommon.Tests/Commands/Inventories/StartInventoryCommandHandlerTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Commands/Inventories/StartInventoryCommandHandlerTests.cs @@ -22,7 +22,7 @@ public class StartInventoryCommandHandlerTests private IInventoryRepository _mockInventoryRepository; private ISharedFilesService _mockSharedFilesService; private IInvokeClientsService _mockInvokeClientsService; - private ICacheService _mockCacheService; + private IRedisInfrastructureService _mockRedisInfrastructureService; private ILogger _mockLogger; private StartInventoryCommandHandler _startInventoryCommandHandler; @@ -39,11 +39,11 @@ public void SetUp() _mockInventoryRepository = A.Fake(); _mockSharedFilesService = A.Fake(options => options.Strict()); _mockInvokeClientsService = A.Fake(); - _mockCacheService = A.Fake(); + _mockRedisInfrastructureService = A.Fake(); _mockLogger = A.Fake>(); _startInventoryCommandHandler = new StartInventoryCommandHandler(_mockInventoryRepository, _mockCloudSessionsRepository, _mockSharedFilesService, - _mockInvokeClientsService, _mockCacheService, _mockLogger); + _mockInvokeClientsService, _mockRedisInfrastructureService, _mockLogger); } [Test] diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs index 005e5c16f..b8f64a4ad 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs @@ -1,10 +1,12 @@ using ByteSync.Common.Business.SharedFiles; using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Factories; using ByteSync.ServerCommon.Repositories; using ByteSync.ServerCommon.Services; using ByteSync.ServerCommon.Tests.Helpers; using FakeItEasy; +using FluentAssertions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -18,20 +20,84 @@ public SharedFilesRepositoryTests() } [Test] - public void Test() + public async Task AddOrUpdate_IntegrationTest() { - Assert.Pass(); + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); + + var sharedFileDefinition = new SharedFileDefinition + { + SessionId = "testSession_" + DateTime.Now.Ticks, + Id = "testSharedFile_" + DateTime.Now.Ticks, + }; + + Func updateHandler = _ => + { + var sharedFileData = new SharedFileData(sharedFileDefinition, new List()); + + return sharedFileData; + }; + + // Act + await repository.AddOrUpdate(sharedFileDefinition, updateHandler); + + // Assert + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await cacheRepository.Get(cacheKey); + value.Should().NotBeNull(); + value.Should().BeOfType(); + value.SharedFileDefinition.Should().BeEquivalentTo(sharedFileDefinition); } [Test] - public async Task AddOrUpdate_IntegrationTest() + public async Task Forget_IntegrationTest() + { + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); + + var sharedFileDefinition = new SharedFileDefinition + { + SessionId = "testSession_" + DateTime.Now.Ticks, + Id = "testSharedFile_" + DateTime.Now.Ticks, + }; + + Func updateHandler = _ => + { + var sharedFileData = new SharedFileData(sharedFileDefinition, new List()); + + return sharedFileData; + }; + + // Act + await repository.AddOrUpdate(sharedFileDefinition, updateHandler); + await repository.Forget(sharedFileDefinition); + + // Assert + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await cacheRepository.Get(cacheKey); + value.Should().BeNull(); + } + + [Test] + public async Task Clear_IntegrationTest() { // Arrange var redisSettings = TestSettingsInitializer.GetRedisSettings(); var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); var loggerFactoryMock = A.Fake(); - var cacheService = new CacheService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var repository = new SharedFilesRepository(cacheService); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); var sharedFileDefinition = new SharedFileDefinition { @@ -39,17 +105,20 @@ public async Task AddOrUpdate_IntegrationTest() Id = "testSharedFile_" + DateTime.Now.Ticks, }; - Func updateHandler = sharedFileData => + Func updateHandler = _ => { - sharedFileData = new SharedFileData(sharedFileDefinition, new List()); + var sharedFileData = new SharedFileData(sharedFileDefinition, new List()); return sharedFileData; }; // Act await repository.AddOrUpdate(sharedFileDefinition, updateHandler); + await repository.Clear(sharedFileDefinition.SessionId); // Assert - // Verify that the data was updated correctly in the repository + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await cacheRepository.Get(cacheKey); + value.Should().BeNull(); } } \ No newline at end of file From ae9cd9a936dc2d16fe80ef28027cf29e3e6ab880 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 13:42:10 +0200 Subject: [PATCH 08/22] refactor: cleanup --- .../Interfaces/Repositories/IRepository.cs | 2 - .../Repositories/BaseRepository.cs | 6 --- .../Autofac/BaseElementTypeModule.cs | 44 ------------------- .../TestHelpers/Autofac/RepositoriesModule.cs | 26 +---------- 4 files changed, 1 insertion(+), 77 deletions(-) diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index 7729b0879..de9a97774 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -25,8 +25,6 @@ public interface IRepository Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null); - // Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); - Task Delete(string key); Task Delete(string key, ITransaction? transaction); diff --git a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs index 0d32b9076..b7351e173 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -67,12 +67,6 @@ public async Task> Save(CacheKey cacheKey, T element, ITra return await _cacheRepository.Save(cacheKey, element, transaction, redisLock); } - // public Task> SetElement(CacheKey cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) - // { - // // Cette méthode est maintenue pour compatibilité avec l'interface - // throw new NotImplementedException("Cette méthode est dépréciée. Utilisez Save à la place."); - // } - public async Task Delete(string key) { await Delete(key, null); diff --git a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs index dfd53f98c..1b299ef30 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs @@ -24,9 +24,6 @@ protected override void Load(ContainerBuilder builder) .AsSelf() .InstancePerLifetimeScope(); - var genericRepositoryTypes_ = GlobalTestSetup.ByteSyncServerCommonAssembly.GetTypes() - .Where(t => t.Name.EndsWith(ElementsType) && t.IsGenericTypeDefinition); - var genericRepositoryTypes = GlobalTestSetup.ByteSyncServerCommonAssembly.GetTypes() .Where(t => t.Name.Contains(ElementsType + "`") && t.IsGenericTypeDefinition && !t.IsInterface); @@ -50,46 +47,5 @@ protected override void Load(ContainerBuilder builder) .InstancePerLifetimeScope(); } } - - /* - foreach (var type in SpecificTypes) - { - // builder.Register(_ => Create.Fake(type)) - // .As(type) - // .AsImplementedInterfaces() - // .InstancePerLifetimeScope(); - - // builder.RegisterType(type) - // .AsImplementedInterfaces() - // .InstancePerLifetimeScope(); - // builder.Register(type) - // .As(type) - // .AsImplementedInterfaces() - // .InstancePerLifetimeScope(); - - - if (type.IsGenericTypeDefinition) - { - // Utiliser RegisterGeneric pour les types génériques ouverts - builder.RegisterGeneric(type) - .AsImplementedInterfaces() - .InstancePerLifetimeScope(); - } - else - { - // Garder RegisterType pour les types concrets - builder.RegisterType(type) - .AsImplementedInterfaces() - .InstancePerLifetimeScope(); - } - }*/ - } - - protected virtual IEnumerable SpecificTypes - { - get - { - return new List(); - } } } \ No newline at end of file diff --git a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs index 5154b961f..4dff184e7 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs @@ -1,9 +1,4 @@ -using ByteSync.ServerCommon.Factories; -using ByteSync.ServerCommon.Interfaces.Repositories; -using ByteSync.ServerCommon.Repositories; -using ByteSync.ServerCommon.Services; - -namespace ByteSync.Functions.IntegrationTests.TestHelpers.Autofac; +namespace ByteSync.Functions.IntegrationTests.TestHelpers.Autofac; public class RepositoriesModule : BaseElementTypeModule { @@ -13,23 +8,4 @@ public RepositoriesModule(bool useConcrete) : base(useConcrete) } protected override string ElementsType => "Repository"; - - protected override IEnumerable SpecificTypes - { - get - { - if (UseConcrete) - { - return [ - //typeof(RedisInfrastructureService), - typeof(CacheRepository<>), - //typeof(CacheKeyFactory) - ]; - } - else - { - return new List(); - } - } - } } \ No newline at end of file From 849559588f9a49341036efc202879081dbdde55b Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 18:52:50 +0200 Subject: [PATCH 09/22] tests: add unit tests refactor: cleanup --- .../Repositories/SharedFilesRepository.cs | 2 - .../Factories/CacheKeyFactoryTests.cs | 83 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 tests/ByteSync.ServerCommon.Tests/Factories/CacheKeyFactoryTests.cs diff --git a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs index e815e4a41..cce115be2 100644 --- a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs @@ -45,7 +45,6 @@ public async Task AddOrUpdate(SharedFileDefinition sharedFileDefinition, Func _redisSettings; + private const string TestPrefix = "test-prefix"; + + [SetUp] + public void Setup() + { + _redisSettings = A.Fake>(); + A.CallTo(() => _redisSettings.Value).Returns(new RedisSettings { Prefix = TestPrefix }); + + _cacheKeyFactory = new CacheKeyFactory(_redisSettings); + } + + [Test] + [TestCase(EntityType.Session, "session123", "Session")] + [TestCase(EntityType.Inventory, "inv456", "Inventory")] + [TestCase(EntityType.Synchronization, "sync789", "Synchronization")] + [TestCase(EntityType.SharedFile, "shared123", "SharedFile")] + [TestCase(EntityType.SessionSharedFiles, "sessionFiles123", "SessionSharedFiles")] + [TestCase(EntityType.TrackingAction, "action123", "TrackingAction")] + [TestCase(EntityType.Client, "client123", "Client")] + [TestCase(EntityType.ClientSoftwareVersionSettings, "version123", "ClientSoftwareVersionSettings")] + [TestCase(EntityType.CloudSessionProfile, "profile123", "CloudSessionProfile")] + [TestCase(EntityType.Lobby, "lobby123", "Lobby")] + public void Create_ShouldGenerateCacheKey_WithCorrectFormat(EntityType entityType, string entityId, string expectedEntityTypeName) + { + // Arrange + var expectedCacheKeyValue = $"{TestPrefix}:{expectedEntityTypeName}:{entityId}"; + + // Act + var result = _cacheKeyFactory.Create(entityType, entityId); + + // Assert + result.Should().NotBeNull(); + result.EntityType.Should().Be(entityType); + result.EntityId.Should().Be(entityId); + result.Value.Should().Be(expectedCacheKeyValue); + } + + [Test] + public void Create_WithDifferentPrefix_ShouldUseConfiguredPrefix() + { + // Arrange + const string customPrefix = "custom-prefix"; + var customRedisSettings = A.Fake>(); + A.CallTo(() => customRedisSettings.Value).Returns(new RedisSettings { Prefix = customPrefix }); + + var factory = new CacheKeyFactory(customRedisSettings); + const string entityId = "123"; + const EntityType entityType = EntityType.Session; + + // Act + var result = factory.Create(entityType, entityId); + + // Assert + result.Value.Should().StartWith(customPrefix); + result.Value.Should().Be($"{customPrefix}:Session:{entityId}"); + } + + [Test] + public void Create_WithInvalidEntityType_ShouldThrowArgumentOutOfRangeException() + { + // Arrange + const string entityId = "123"; + const EntityType invalidEntityType = (EntityType)999; + + // Act & Assert + FluentActions.Invoking(() => _cacheKeyFactory.Create(invalidEntityType, entityId)) + .Should().Throw() + .WithParameterName("entityType"); + } +} From a39cd2d47fb36fcf7a6e750a960ebd54c888a0b2 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 19:05:12 +0200 Subject: [PATCH 10/22] feat: fix AddCloudSession test: add integration tests --- .../Repositories/CloudSessionsRepository.cs | 2 +- .../CloudSessionsRepositoryTests.cs | 170 ++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs diff --git a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs index 40bbd9067..00f0402ad 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionsRepository.cs @@ -59,7 +59,7 @@ public async Task AddCloudSession(CloudSessionData cloudSessio string? serializedElement = await _redisInfrastructureService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement == null || serializedElement.IsEmpty()) { - await Save(cacheKey, cloudSessionData, transaction); + await Save(cacheKey, cloudSessionData, transaction, redisLock); isNewSessionOk = true; } } diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs new file mode 100644 index 000000000..cc2e98434 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs @@ -0,0 +1,170 @@ +using ByteSync.ServerCommon.Business.Auth; +using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; +using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; +using ByteSync.ServerCommon.Tests.Helpers; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Tests.Repositories; + +public class CloudSessionsRepositoryTests +{ + [Test] + public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() + { + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); + + var sessionId = "testSession_" + DateTime.Now.Ticks; + var clientInstanceId = "testClient_" + DateTime.Now.Ticks; + + var sessionMember = new SessionMemberData + { + ClientInstanceId = clientInstanceId + }; + + var cloudSessionData = new CloudSessionData + { + SessionId = sessionId, + SessionMembers = new List { sessionMember } + }; + + // Save the session data first + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await cacheRepository.Save(cacheKey, cloudSessionData); + + // Act + var result = await repository.GetSessionMember(sessionId, clientInstanceId); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(sessionMember); + } + + [Test] + public async Task GetSessionMember_WithClient_ReturnsCorrectSessionMember() + { + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); + + var sessionId = "testSession_" + DateTime.Now.Ticks; + var clientInstanceId = "testClient_" + DateTime.Now.Ticks; + + var client = new Client + { + ClientInstanceId = clientInstanceId + }; + + var sessionMember = new SessionMemberData + { + ClientInstanceId = clientInstanceId + }; + + var cloudSessionData = new CloudSessionData + { + SessionId = sessionId, + SessionMembers = new List { sessionMember } + }; + + // Save the session data first + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await cacheRepository.Save(cacheKey, cloudSessionData); + + // Act + var result = await repository.GetSessionMember(sessionId, client); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(sessionMember); + } + + [Test] + public async Task GetSessionPreMember_WithValidData_ReturnsCorrectPreSessionMember() + { + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); + + var sessionId = "testSession_" + DateTime.Now.Ticks; + var clientInstanceId = "testClient_" + DateTime.Now.Ticks; + + var preSessionMember = new SessionMemberData + { + ClientInstanceId = clientInstanceId + }; + + var cloudSessionData = new CloudSessionData + { + SessionId = sessionId, + PreSessionMembers = new List { preSessionMember } + }; + + // Save the session data first + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await cacheRepository.Save(cacheKey, cloudSessionData); + + // Act + var result = await repository.GetSessionPreMember(sessionId, clientInstanceId); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(preSessionMember); + } + + [Test] + public async Task AddCloudSession_IntegrationTest() + { + // Arrange + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); + + var sessionId = "testSession_" + DateTime.Now.Ticks; + + var cloudSessionData = new CloudSessionData + { + SessionId = "temporary", // This will be overwritten by the generateSessionIdHandler + SessionMembers = new List(), + PreSessionMembers = new List() + }; + + Func generateSessionIdHandler = () => sessionId; + var transaction = redisInfrastructureService.OpenTransaction(); + + // Act + var result = await repository.AddCloudSession(cloudSessionData, generateSessionIdHandler, transaction); + await transaction.ExecuteAsync(); + + // Assert + result.Should().NotBeNull(); + result.SessionId.Should().Be(sessionId); + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + var value = await cacheRepository.Get(cacheKey); + value.Should().NotBeNull(); + value.Should().BeOfType(); + value.SessionId.Should().Be(sessionId); + } +} From 835d63d43b36549cceb3261e36967b31a9e2014f Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 19:43:31 +0200 Subject: [PATCH 11/22] test: add integration tests - ClientsRepositoryTests --- .../Repositories/ClientsRepositoryTests.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/ByteSync.ServerCommon.Tests/Repositories/ClientsRepositoryTests.cs diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/ClientsRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/ClientsRepositoryTests.cs new file mode 100644 index 000000000..e24bff259 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/ClientsRepositoryTests.cs @@ -0,0 +1,108 @@ +using ByteSync.Common.Business.EndPoints; +using ByteSync.ServerCommon.Business.Auth; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; +using ByteSync.ServerCommon.Interfaces.Factories; +using ByteSync.ServerCommon.Interfaces.Services; +using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; +using ByteSync.ServerCommon.Tests.Helpers; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Tests.Repositories; + +public class ClientsRepositoryTests +{ + private IRedisInfrastructureService _redisInfrastructureService; + private CacheRepository _cacheRepository; + private IClientsGroupIdFactory _clientsGroupIdFactory; + private ClientsRepository _repository; + + [SetUp] + public void Setup() + { + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + _redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + _cacheRepository = new CacheRepository(_redisInfrastructureService); + _clientsGroupIdFactory = A.Fake(); + _repository = new ClientsRepository(_redisInfrastructureService, _cacheRepository, _clientsGroupIdFactory); + } + + [Test] + public async Task Get_WithByteSyncEndpoint_ShouldReturnClient() + { + // Arrange + var clientInstanceId = "testClient_" + DateTime.Now.Ticks; + var byteSyncEndpoint = new ByteSyncEndpoint { ClientInstanceId = clientInstanceId }; + + var client = new Client + { + ClientInstanceId = clientInstanceId + }; + + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Client, clientInstanceId); + await _cacheRepository.Save(cacheKey, client); + + // Act + var result = await _repository.Get(byteSyncEndpoint); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(client); + } + + [Test] + public async Task Get_WithNonExistingClientId_ShouldReturnNull() + { + // Arrange + var nonExistingClientId = "nonExistingClient_" + DateTime.Now.Ticks; + var byteSyncEndpoint = new ByteSyncEndpoint { ClientInstanceId = nonExistingClientId }; + + // Act + var result = await _repository.Get(byteSyncEndpoint); + + // Assert + result.Should().BeNull(); + } + + [Test] + public async Task GetClientsWithoutConnectionId_ShouldReturnEmptySet() + { + // Act + var result = await _repository.GetClientsWithoutConnectionId(); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEmpty(); + } + + [Test] + public async Task RemoveClient_ShouldDeleteClientFromCache() + { + // Arrange + var clientInstanceId = "testClient_" + DateTime.Now.Ticks; + var client = new Client + { + ClientInstanceId = clientInstanceId + }; + + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Client, clientInstanceId); + await _cacheRepository.Save(cacheKey, client); + + // Vérification que le client est présent dans le cache avant de le supprimer + var beforeDelete = await _cacheRepository.Get(cacheKey); + beforeDelete.Should().NotBeNull(); + + // Act + await _repository.RemoveClient(client); + + // Assert + var afterDelete = await _cacheRepository.Get(cacheKey); + afterDelete.Should().BeNull(); + } +} From 0efa4fdbe9897408de487718ebb1bf5ada242602 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 19:46:52 +0200 Subject: [PATCH 12/22] tests: add integration tests - InventoryRepositoryTests --- .../Repositories/InventoryRepositoryTests.cs | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs new file mode 100644 index 000000000..b1e61a741 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs @@ -0,0 +1,153 @@ +using ByteSync.ServerCommon.Business.Sessions; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; +using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; +using ByteSync.ServerCommon.Tests.Helpers; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Tests.Repositories; + +public class InventoryRepositoryTests +{ + [Test] + public async Task Get_IntegrationTest() + { + // Arrange + var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + var inventoryData = new InventoryData + { + SessionId = sessionId, + InventoryMembers = [new() { ClientInstanceId = "client1" }] + }; + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await cacheRepository.Save(cacheKey, inventoryData); + + // Act + var result = await repository.Get(sessionId); + + // Assert + result.Should().NotBeNull(); + result.SessionId.Should().Be(sessionId); + result.InventoryMembers.Should().HaveCount(1); + result.InventoryMembers[0].ClientInstanceId.Should().Be("client1"); + } + + [Test] + public async Task GetInventoryMember_IntegrationTest() + { + // Arrange + var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + string clientId1 = "client1"; + string clientId2 = "client2"; + + var inventoryData = new InventoryData + { + SessionId = sessionId, + InventoryMembers = + [ + new() { ClientInstanceId = clientId1 }, + new() { ClientInstanceId = clientId2 } + ] + }; + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await cacheRepository.Save(cacheKey, inventoryData); + + // Act + var result = await repository.GetInventoryMember(sessionId, clientId2); + + // Assert + result.Should().NotBeNull(); + result.ClientInstanceId.Should().Be(clientId2); + } + + [Test] + public async Task GetInventoryMember_WhenMemberNotFound_ReturnsNull() + { + // Arrange + var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + var inventoryData = new InventoryData + { + SessionId = sessionId, + InventoryMembers = [new() { ClientInstanceId = "client1" }] + }; + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await cacheRepository.Save(cacheKey, inventoryData); + + // Act + var result = await repository.GetInventoryMember(sessionId, "nonExistentClient"); + + // Assert + result.Should().BeNull(); + } + + [Test] + public async Task GetInventoryMember_WhenInventoryNotFound_ReturnsNull() + { + // Arrange + var (repository, _, _) = SetupRepositoryAndDependencies(); + + string nonExistentSessionId = "nonExistentSession_" + DateTime.Now.Ticks; + + // Act + var result = await repository.GetInventoryMember(nonExistentSessionId, "anyClient"); + + // Assert + result.Should().BeNull(); + } + + [Test] + public async Task Save_IntegrationTest() + { + // Arrange + var (repository, _, redisInfrastructureService) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + var inventoryData = new InventoryData + { + SessionId = sessionId, + InventoryMembers = [new() { ClientInstanceId = "client1" }] + }; + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + + // Act + await repository.Save(cacheKey, inventoryData); + var result = await repository.Get(sessionId); + + // Assert + result.Should().NotBeNull(); + result.SessionId.Should().Be(sessionId); + result.InventoryMembers.Should().HaveCount(1); + result.InventoryMembers[0].ClientInstanceId.Should().Be("client1"); + } + + private (InventoryRepository, CacheRepository, RedisInfrastructureService) SetupRepositoryAndDependencies() + { + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + + var redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + var cacheRepository = new CacheRepository(redisInfrastructureService); + var repository = new InventoryRepository(redisInfrastructureService, cacheRepository); + + return (repository, cacheRepository, redisInfrastructureService); + } +} From 2ac079b4077cb4aaf9a5abcbbcd9b04c151e8c8b Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 19:51:00 +0200 Subject: [PATCH 13/22] test: add integration tests - SynchronizationRepositoryTests.cs --- .../SynchronizationRepositoryTests.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs new file mode 100644 index 000000000..6c10f4533 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs @@ -0,0 +1,99 @@ +using ByteSync.Common.Business.Actions; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; +using ByteSync.ServerCommon.Interfaces.Repositories; +using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; +using ByteSync.ServerCommon.Tests.Helpers; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Tests.Repositories; + +public class SynchronizationRepositoryTests +{ + [Test] + public async Task AddSynchronization_IntegrationTest() + { + // Arrange + var (repository, cacheRepository, redisInfrastructureService, actionsGroupDefRepo) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; + + var actionsGroupDefinitions = new List + { + new() { ActionsGroupId = "group1" }, + new() { ActionsGroupId = "group2" } + }; + + // Act + await repository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); + + // Assert + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); + var savedEntity = await cacheRepository.Get(cacheKey); + + savedEntity.Should().NotBeNull(); + savedEntity.Should().BeEquivalentTo(synchronizationEntity); + + A.CallTo(() => actionsGroupDefRepo.AddOrUpdateActionsGroupDefinitions( + sessionId, + A>.That.IsSameSequenceAs(actionsGroupDefinitions))) + .MustHaveHappenedOnceExactly(); + } + + [Test] + public async Task ResetSession_IntegrationTest() + { + // Arrange + var (repository, cacheRepository, redisInfrastructureService, actionsGroupDefRepo) = SetupRepositoryAndDependencies(); + + string sessionId = "testSession_" + DateTime.Now.Ticks; + var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; + + var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); + await cacheRepository.Save(cacheKey, synchronizationEntity); + + // Verify entity exists before resetting + var entityBeforeReset = await cacheRepository.Get(cacheKey); + entityBeforeReset.Should().NotBeNull(); + + // Act + await repository.ResetSession(sessionId); + + // Assert + var entityAfterReset = await cacheRepository.Get(cacheKey); + entityAfterReset.Should().BeNull(); + + A.CallTo(() => actionsGroupDefRepo.DeleteActionsGroupDefinitions(sessionId)) + .MustHaveHappenedOnceExactly(); + } + + private (SynchronizationRepository, + CacheRepository, + RedisInfrastructureService, + IActionsGroupDefinitionsRepository) SetupRepositoryAndDependencies() + { + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + + var redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + var cacheRepository = new CacheRepository(redisInfrastructureService); + var actionsGroupDefinitionsRepository = A.Fake(); + + var repository = new SynchronizationRepository( + redisInfrastructureService, + cacheRepository, + actionsGroupDefinitionsRepository); + + return (repository, cacheRepository, redisInfrastructureService, actionsGroupDefinitionsRepository); + } +} From 33404abc8cbcd8ab4657f5dd77740a7d274f51a7 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 19:55:56 +0200 Subject: [PATCH 14/22] refactor: improve tests --- .../SharedFilesRepositoryTests.cs | 66 ++++++++-------- .../SynchronizationRepositoryTests.cs | 76 +++++++++---------- 2 files changed, 68 insertions(+), 74 deletions(-) diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs index b8f64a4ad..4d6170021 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs @@ -12,24 +12,34 @@ namespace ByteSync.ServerCommon.Tests.Repositories; +[TestFixture] public class SharedFilesRepositoryTests { - public SharedFilesRepositoryTests() + private SharedFilesRepository _repository; + private CacheRepository _cacheRepository; + private RedisInfrastructureService _redisInfrastructureService; + + [SetUp] + public void SetUp() { - + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + + _redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + _cacheRepository = new CacheRepository(_redisInfrastructureService); + + _repository = new SharedFilesRepository(_redisInfrastructureService, _cacheRepository); } [Test] public async Task AddOrUpdate_IntegrationTest() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); - var sharedFileDefinition = new SharedFileDefinition { SessionId = "testSession_" + DateTime.Now.Ticks, @@ -44,11 +54,11 @@ public async Task AddOrUpdate_IntegrationTest() }; // Act - await repository.AddOrUpdate(sharedFileDefinition, updateHandler); + await _repository.AddOrUpdate(sharedFileDefinition, updateHandler); // Assert - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); - var value = await cacheRepository.Get(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await _cacheRepository.Get(cacheKey); value.Should().NotBeNull(); value.Should().BeOfType(); value.SharedFileDefinition.Should().BeEquivalentTo(sharedFileDefinition); @@ -58,13 +68,6 @@ public async Task AddOrUpdate_IntegrationTest() public async Task Forget_IntegrationTest() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); - var sharedFileDefinition = new SharedFileDefinition { SessionId = "testSession_" + DateTime.Now.Ticks, @@ -79,12 +82,12 @@ public async Task Forget_IntegrationTest() }; // Act - await repository.AddOrUpdate(sharedFileDefinition, updateHandler); - await repository.Forget(sharedFileDefinition); + await _repository.AddOrUpdate(sharedFileDefinition, updateHandler); + await _repository.Forget(sharedFileDefinition); // Assert - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); - var value = await cacheRepository.Get(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await _cacheRepository.Get(cacheKey); value.Should().BeNull(); } @@ -92,13 +95,6 @@ public async Task Forget_IntegrationTest() public async Task Clear_IntegrationTest() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new SharedFilesRepository(redisInfrastructureService, cacheRepository); - var sharedFileDefinition = new SharedFileDefinition { SessionId = "testSession_" + DateTime.Now.Ticks, @@ -113,12 +109,12 @@ public async Task Clear_IntegrationTest() }; // Act - await repository.AddOrUpdate(sharedFileDefinition, updateHandler); - await repository.Clear(sharedFileDefinition.SessionId); + await _repository.AddOrUpdate(sharedFileDefinition, updateHandler); + await _repository.Clear(sharedFileDefinition.SessionId); // Assert - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); - var value = await cacheRepository.Get(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.SharedFile, sharedFileDefinition.Id); + var value = await _cacheRepository.Get(cacheKey); value.Should().BeNull(); } -} \ No newline at end of file +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs index 6c10f4533..d3904aba4 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs @@ -12,14 +12,39 @@ namespace ByteSync.ServerCommon.Tests.Repositories; +[TestFixture] public class SynchronizationRepositoryTests { + private SynchronizationRepository _repository; + private CacheRepository _cacheRepository; + private RedisInfrastructureService _redisInfrastructureService; + private IActionsGroupDefinitionsRepository _actionsGroupDefRepo; + + [SetUp] + public void SetUp() + { + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + + _redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + _cacheRepository = new CacheRepository(_redisInfrastructureService); + _actionsGroupDefRepo = A.Fake(); + + _repository = new SynchronizationRepository( + _redisInfrastructureService, + _cacheRepository, + _actionsGroupDefRepo); + } + [Test] public async Task AddSynchronization_IntegrationTest() { // Arrange - var (repository, cacheRepository, redisInfrastructureService, actionsGroupDefRepo) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; @@ -30,16 +55,16 @@ public async Task AddSynchronization_IntegrationTest() }; // Act - await repository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); + await _repository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); // Assert - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); - var savedEntity = await cacheRepository.Get(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); + var savedEntity = await _cacheRepository.Get(cacheKey); savedEntity.Should().NotBeNull(); savedEntity.Should().BeEquivalentTo(synchronizationEntity); - A.CallTo(() => actionsGroupDefRepo.AddOrUpdateActionsGroupDefinitions( + A.CallTo(() => _actionsGroupDefRepo.AddOrUpdateActionsGroupDefinitions( sessionId, A>.That.IsSameSequenceAs(actionsGroupDefinitions))) .MustHaveHappenedOnceExactly(); @@ -49,51 +74,24 @@ public async Task AddSynchronization_IntegrationTest() public async Task ResetSession_IntegrationTest() { // Arrange - var (repository, cacheRepository, redisInfrastructureService, actionsGroupDefRepo) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); - await cacheRepository.Save(cacheKey, synchronizationEntity); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId); + await _cacheRepository.Save(cacheKey, synchronizationEntity); // Verify entity exists before resetting - var entityBeforeReset = await cacheRepository.Get(cacheKey); + var entityBeforeReset = await _cacheRepository.Get(cacheKey); entityBeforeReset.Should().NotBeNull(); // Act - await repository.ResetSession(sessionId); + await _repository.ResetSession(sessionId); // Assert - var entityAfterReset = await cacheRepository.Get(cacheKey); + var entityAfterReset = await _cacheRepository.Get(cacheKey); entityAfterReset.Should().BeNull(); - A.CallTo(() => actionsGroupDefRepo.DeleteActionsGroupDefinitions(sessionId)) + A.CallTo(() => _actionsGroupDefRepo.DeleteActionsGroupDefinitions(sessionId)) .MustHaveHappenedOnceExactly(); } - - private (SynchronizationRepository, - CacheRepository, - RedisInfrastructureService, - IActionsGroupDefinitionsRepository) SetupRepositoryAndDependencies() - { - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - - var redisInfrastructureService = new RedisInfrastructureService( - Options.Create(redisSettings), - cacheKeyFactory, - loggerFactoryMock); - - var cacheRepository = new CacheRepository(redisInfrastructureService); - var actionsGroupDefinitionsRepository = A.Fake(); - - var repository = new SynchronizationRepository( - redisInfrastructureService, - cacheRepository, - actionsGroupDefinitionsRepository); - - return (repository, cacheRepository, redisInfrastructureService, actionsGroupDefinitionsRepository); - } } From e7ab34d6f2d91be20588863902cd105b3b235f0e Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 20:36:01 +0200 Subject: [PATCH 15/22] test: improve tests --- .../CloudSessionsRepositoryTests.cs | 74 +++++++++--------- .../Repositories/InventoryRepositoryTests.cs | 75 +++++++++---------- 2 files changed, 69 insertions(+), 80 deletions(-) diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs index cc2e98434..d71aaaab6 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs @@ -12,19 +12,34 @@ namespace ByteSync.ServerCommon.Tests.Repositories; +[TestFixture] public class CloudSessionsRepositoryTests { - [Test] - public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() + private CloudSessionsRepository _repository; + private CacheRepository _cacheRepository; + private RedisInfrastructureService _redisInfrastructureService; + + [SetUp] + public void SetUp() { - // Arrange var redisSettings = TestSettingsInitializer.GetRedisSettings(); var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); + + _redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + _cacheRepository = new CacheRepository(_redisInfrastructureService); + + _repository = new CloudSessionsRepository(_redisInfrastructureService, _cacheRepository); + } + [Test] + public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() + { + // Arrange var sessionId = "testSession_" + DateTime.Now.Ticks; var clientInstanceId = "testClient_" + DateTime.Now.Ticks; @@ -40,11 +55,11 @@ public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() }; // Save the session data first - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); - await cacheRepository.Save(cacheKey, cloudSessionData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await _cacheRepository.Save(cacheKey, cloudSessionData); // Act - var result = await repository.GetSessionMember(sessionId, clientInstanceId); + var result = await _repository.GetSessionMember(sessionId, clientInstanceId); // Assert result.Should().NotBeNull(); @@ -55,13 +70,6 @@ public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() public async Task GetSessionMember_WithClient_ReturnsCorrectSessionMember() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); - var sessionId = "testSession_" + DateTime.Now.Ticks; var clientInstanceId = "testClient_" + DateTime.Now.Ticks; @@ -82,11 +90,11 @@ public async Task GetSessionMember_WithClient_ReturnsCorrectSessionMember() }; // Save the session data first - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); - await cacheRepository.Save(cacheKey, cloudSessionData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await _cacheRepository.Save(cacheKey, cloudSessionData); // Act - var result = await repository.GetSessionMember(sessionId, client); + var result = await _repository.GetSessionMember(sessionId, client); // Assert result.Should().NotBeNull(); @@ -97,13 +105,6 @@ public async Task GetSessionMember_WithClient_ReturnsCorrectSessionMember() public async Task GetSessionPreMember_WithValidData_ReturnsCorrectPreSessionMember() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); - var sessionId = "testSession_" + DateTime.Now.Ticks; var clientInstanceId = "testClient_" + DateTime.Now.Ticks; @@ -119,11 +120,11 @@ public async Task GetSessionPreMember_WithValidData_ReturnsCorrectPreSessionMemb }; // Save the session data first - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); - await cacheRepository.Save(cacheKey, cloudSessionData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + await _cacheRepository.Save(cacheKey, cloudSessionData); // Act - var result = await repository.GetSessionPreMember(sessionId, clientInstanceId); + var result = await _repository.GetSessionPreMember(sessionId, clientInstanceId); // Assert result.Should().NotBeNull(); @@ -134,13 +135,6 @@ public async Task GetSessionPreMember_WithValidData_ReturnsCorrectPreSessionMemb public async Task AddCloudSession_IntegrationTest() { // Arrange - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new CloudSessionsRepository(redisInfrastructureService, cacheRepository); - var sessionId = "testSession_" + DateTime.Now.Ticks; var cloudSessionData = new CloudSessionData @@ -151,18 +145,18 @@ public async Task AddCloudSession_IntegrationTest() }; Func generateSessionIdHandler = () => sessionId; - var transaction = redisInfrastructureService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); // Act - var result = await repository.AddCloudSession(cloudSessionData, generateSessionIdHandler, transaction); + var result = await _repository.AddCloudSession(cloudSessionData, generateSessionIdHandler, transaction); await transaction.ExecuteAsync(); // Assert result.Should().NotBeNull(); result.SessionId.Should().Be(sessionId); - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); - var value = await cacheRepository.Get(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Session, sessionId); + var value = await _cacheRepository.Get(cacheKey); value.Should().NotBeNull(); value.Should().BeOfType(); value.SessionId.Should().Be(sessionId); diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs index b1e61a741..e899ab7ac 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs @@ -11,14 +11,34 @@ namespace ByteSync.ServerCommon.Tests.Repositories; +[TestFixture] public class InventoryRepositoryTests { + private InventoryRepository _repository; + private CacheRepository _cacheRepository; + private RedisInfrastructureService _redisInfrastructureService; + + [SetUp] + public void SetUp() + { + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + + _redisInfrastructureService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + _cacheRepository = new CacheRepository(_redisInfrastructureService); + + _repository = new InventoryRepository(_redisInfrastructureService, _cacheRepository); + } + [Test] public async Task Get_IntegrationTest() { // Arrange - var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; var inventoryData = new InventoryData { @@ -26,11 +46,11 @@ public async Task Get_IntegrationTest() InventoryMembers = [new() { ClientInstanceId = "client1" }] }; - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); - await cacheRepository.Save(cacheKey, inventoryData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await _cacheRepository.Save(cacheKey, inventoryData); // Act - var result = await repository.Get(sessionId); + var result = await _repository.Get(sessionId); // Assert result.Should().NotBeNull(); @@ -43,8 +63,6 @@ public async Task Get_IntegrationTest() public async Task GetInventoryMember_IntegrationTest() { // Arrange - var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; string clientId1 = "client1"; string clientId2 = "client2"; @@ -59,11 +77,11 @@ public async Task GetInventoryMember_IntegrationTest() ] }; - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); - await cacheRepository.Save(cacheKey, inventoryData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await _cacheRepository.Save(cacheKey, inventoryData); // Act - var result = await repository.GetInventoryMember(sessionId, clientId2); + var result = await _repository.GetInventoryMember(sessionId, clientId2); // Assert result.Should().NotBeNull(); @@ -74,8 +92,6 @@ public async Task GetInventoryMember_IntegrationTest() public async Task GetInventoryMember_WhenMemberNotFound_ReturnsNull() { // Arrange - var (repository, cacheRepository, redisInfrastructureService) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; var inventoryData = new InventoryData { @@ -83,11 +99,11 @@ public async Task GetInventoryMember_WhenMemberNotFound_ReturnsNull() InventoryMembers = [new() { ClientInstanceId = "client1" }] }; - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); - await cacheRepository.Save(cacheKey, inventoryData); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + await _cacheRepository.Save(cacheKey, inventoryData); // Act - var result = await repository.GetInventoryMember(sessionId, "nonExistentClient"); + var result = await _repository.GetInventoryMember(sessionId, "nonExistentClient"); // Assert result.Should().BeNull(); @@ -97,12 +113,10 @@ public async Task GetInventoryMember_WhenMemberNotFound_ReturnsNull() public async Task GetInventoryMember_WhenInventoryNotFound_ReturnsNull() { // Arrange - var (repository, _, _) = SetupRepositoryAndDependencies(); - string nonExistentSessionId = "nonExistentSession_" + DateTime.Now.Ticks; // Act - var result = await repository.GetInventoryMember(nonExistentSessionId, "anyClient"); + var result = await _repository.GetInventoryMember(nonExistentSessionId, "anyClient"); // Assert result.Should().BeNull(); @@ -112,8 +126,6 @@ public async Task GetInventoryMember_WhenInventoryNotFound_ReturnsNull() public async Task Save_IntegrationTest() { // Arrange - var (repository, _, redisInfrastructureService) = SetupRepositoryAndDependencies(); - string sessionId = "testSession_" + DateTime.Now.Ticks; var inventoryData = new InventoryData { @@ -121,11 +133,11 @@ public async Task Save_IntegrationTest() InventoryMembers = [new() { ClientInstanceId = "client1" }] }; - var cacheKey = redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType.Inventory, sessionId); // Act - await repository.Save(cacheKey, inventoryData); - var result = await repository.Get(sessionId); + await _repository.Save(cacheKey, inventoryData); + var result = await _repository.Get(sessionId); // Assert result.Should().NotBeNull(); @@ -133,21 +145,4 @@ public async Task Save_IntegrationTest() result.InventoryMembers.Should().HaveCount(1); result.InventoryMembers[0].ClientInstanceId.Should().Be("client1"); } - - private (InventoryRepository, CacheRepository, RedisInfrastructureService) SetupRepositoryAndDependencies() - { - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - - var redisInfrastructureService = new RedisInfrastructureService( - Options.Create(redisSettings), - cacheKeyFactory, - loggerFactoryMock); - - var cacheRepository = new CacheRepository(redisInfrastructureService); - var repository = new InventoryRepository(redisInfrastructureService, cacheRepository); - - return (repository, cacheRepository, redisInfrastructureService); - } } From 93d4852146c0c84d2db022a092a3678df7ab01fa Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Thu, 10 Apr 2025 21:41:06 +0200 Subject: [PATCH 16/22] test: add integration tests - TrackingActionRepositoryTests --- .../TrackingActionRepositoryTests.cs | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs new file mode 100644 index 000000000..1c6b90d04 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs @@ -0,0 +1,331 @@ +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Interfaces.Factories; +using ByteSync.ServerCommon.Interfaces.Repositories; +using ByteSync.ServerCommon.Interfaces.Services; +using ByteSync.ServerCommon.Repositories; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using RedLockNet; +using StackExchange.Redis; + +namespace ByteSync.ServerCommon.Tests.Repositories; + +[TestFixture] +public class TrackingActionRepositoryTests +{ + private TrackingActionRepository _repository; + private IRedisInfrastructureService _redisInfrastructureService; + private ISynchronizationRepository _synchronizationRepository; + private ITrackingActionEntityFactory _trackingActionEntityFactory; + private ICacheRepository _cacheRepository; + private ILogger _logger; + private IRedLock _lockMock; + private ITransaction _transactionMock; + + [SetUp] + public void SetUp() + { + _redisInfrastructureService = A.Fake(); + _synchronizationRepository = A.Fake(); + _trackingActionEntityFactory = A.Fake(); + _cacheRepository = A.Fake>(); + _logger = A.Fake>(); + _lockMock = A.Fake(); + _transactionMock = A.Fake(); + + _repository = new TrackingActionRepository( + _redisInfrastructureService, + _synchronizationRepository, + _trackingActionEntityFactory, + _cacheRepository, + _logger); + + // Configuration des mocks communs + A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A._)) + .Returns(_lockMock); + A.CallTo(() => _redisInfrastructureService.OpenTransaction()) + .Returns(_transactionMock); + } + + [Test] + public void EntityType_ShouldReturnTrackingAction() + { + // Assert + _repository.EntityType.Should().Be(EntityType.TrackingAction); + } + + [Test] + public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() + { + // Arrange + var sessionId = "session123"; + var actionsGroupId = "group456"; + var cacheKey = new CacheKey + { + EntityType = EntityType.TrackingAction, + EntityId = $"{sessionId}_{actionsGroupId}", + Value = $"test:TrackingAction:{sessionId}_{actionsGroupId}" + }; + var existingEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; + + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) + .Returns(cacheKey); + A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) + .Returns(existingEntity); + + // Act + var result = await _repository.GetOrBuild(sessionId, actionsGroupId); + + // Assert + result.Should().Be(existingEntity); + A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(cacheKey)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _trackingActionEntityFactory.Create(A._, A._)).MustNotHaveHappened(); + } + + [Test] + public async Task GetOrBuild_WhenEntityDoesNotExist_ShouldCreateAndSaveNewEntity() + { + // Arrange + var sessionId = "session123"; + var actionsGroupId = "group456"; + var cacheKey = new CacheKey + { + EntityType = EntityType.TrackingAction, + EntityId = $"{sessionId}_{actionsGroupId}", + Value = $"test:TrackingAction:{sessionId}_{actionsGroupId}" + }; + var newEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; + + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) + .Returns(cacheKey); + A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) + .Returns(default(TrackingActionEntity)); + A.CallTo(() => _trackingActionEntityFactory.Create(sessionId, actionsGroupId)) + .Returns(newEntity); + + // Act + var result = await _repository.GetOrBuild(sessionId, actionsGroupId); + + // Assert + result.Should().Be(newEntity); + A.CallTo(() => _trackingActionEntityFactory.Create(sessionId, actionsGroupId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _cacheRepository.Save(cacheKey, newEntity, null, null)).MustHaveHappenedOnceExactly(); + } + + [Test] + public async Task AddOrUpdate_WhenAllUpdatesSucceed_ShouldUpdateAllEntitiesAndSynchronization() + { + // Arrange + var sessionId = "session123"; + var actionsGroupIds = new List { "group1", "group2" }; + var syncEntity = new SynchronizationEntity { SessionId = sessionId }; + var trackingEntity1 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[0] }; + var trackingEntity2 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[1] }; + var syncCacheKey = new CacheKey + { + EntityType = EntityType.Synchronization, + EntityId = sessionId, + Value = $"test:Synchronization:{sessionId}" + }; + var cacheKey1 = new CacheKey + { + EntityType = EntityType.TrackingAction, + EntityId = $"{sessionId}_{actionsGroupIds[0]}", + Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[0]}" + }; + var cacheKey2 = new CacheKey + { + EntityType = EntityType.TrackingAction, + EntityId = $"{sessionId}_{actionsGroupIds[1]}", + Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[1]}" + }; + + + bool updateHandlerResult = true; + Func updateHandler = (_, _) => updateHandlerResult; + + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId)) + .Returns(syncCacheKey); + A.CallTo(() => _synchronizationRepository.Get(sessionId)) + .Returns(syncEntity); + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[0]}")) + .Returns(cacheKey1); + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[1]}")) + .Returns(cacheKey2); + A.CallTo(() => _cacheRepository.Get(cacheKey1, A._)) + .Returns(trackingEntity1); + A.CallTo(() => _cacheRepository.Get(cacheKey2, A._)) + .Returns(trackingEntity2); + + // Act + var result = await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.TrackingActionEntities.Should().Contain(trackingEntity1); + result.TrackingActionEntities.Should().Contain(trackingEntity2); + result.SynchronizationEntity.Should().Be(syncEntity); + + A.CallTo(() => _cacheRepository.Save(cacheKey1, trackingEntity1, _transactionMock, null)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _cacheRepository.Save(cacheKey2, trackingEntity2, _transactionMock, null)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _synchronizationRepository.Save(syncCacheKey, syncEntity, _transactionMock, null)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _transactionMock.ExecuteAsync(A._)).MustHaveHappenedOnceExactly(); + } + + + [Test] + public async Task AddOrUpdate_WhenUpdateFails_ShouldNotExecuteTransaction() + { + // Arrange + var sessionId = "session123"; + var actionsGroupIds = new List { "group1", "group2" }; + var syncEntity = new SynchronizationEntity { SessionId = sessionId }; + var trackingEntity1 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[0] }; + var syncCacheKey = new CacheKey + { + EntityType = EntityType.Synchronization, + EntityId = sessionId, + Value = $"test:Synchronization:{sessionId}" + }; + var cacheKey1 = new CacheKey + { + EntityType = EntityType.TrackingAction, + EntityId = $"{sessionId}_{actionsGroupIds[0]}", + Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[0]}" + }; + + bool updateHandlerResult = false; + Func updateHandler = (_, _) => updateHandlerResult; + + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId)) + .Returns(syncCacheKey); + A.CallTo(() => _synchronizationRepository.Get(sessionId)) + .Returns(syncEntity); + A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[0]}")) + .Returns(cacheKey1); + A.CallTo(() => _cacheRepository.Get(cacheKey1, A._)) + .Returns(trackingEntity1); + + // Act + var result = await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); + + // Assert + result.IsSuccess.Should().BeFalse(); + result.TrackingActionEntities.Should().BeEmpty(); + result.SynchronizationEntity.Should().Be(syncEntity); + + A.CallTo(() => _transactionMock.ExecuteAsync(A._)).MustNotHaveHappened(); + } + + + [Test] + public async Task AddOrUpdate_ShouldReleaseAllLocksEvenOnFailure() + { + // Arrange + var sessionId = "session123"; + var actionsGroupIds = new List { "group1", "group2" }; + var syncEntity = new SynchronizationEntity { SessionId = sessionId }; + var lockMock1 = A.Fake(); + var lockMock2 = A.Fake(); + + A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == sessionId))) + .Returns(_lockMock); + A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == $"{sessionId}_{actionsGroupIds[0]}"))) + .Returns(lockMock1); + A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == $"{sessionId}_{actionsGroupIds[1]}"))) + .Returns(lockMock2); + A.CallTo(() => _synchronizationRepository.Get(sessionId)) + .Returns(syncEntity); + A.CallTo(() => _cacheRepository.Get(A._)) + .Returns(new TrackingActionEntity()); + + // Simuler un échec de l'update + Func updateHandler = (entity, sync) => false; + + // Act + await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); + + // Assert + A.CallTo(() => lockMock1.DisposeAsync()).MustHaveHappenedOnceExactly(); + A.CallTo(() => lockMock2.DisposeAsync()).MustHaveHappenedOnceExactly(); + } + + [Test] + public async Task AddOrUpdate_WithIntegrationTest() + { + // Configuration de l'intégration + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var logger = A.Fake>(); + A.CallTo(() => loggerFactoryMock.CreateLogger(A._)).Returns(logger); + + var redisService = new RedisInfrastructureService( + Options.Create(redisSettings), + cacheKeyFactory, + loggerFactoryMock); + + var cacheRepo = new CacheRepository(redisService); + var syncRepo = A.Fake(); + var factory = A.Fake(); + + var repository = new TrackingActionRepository( + redisService, + syncRepo, + factory, + cacheRepo, + logger); + + // Données de test + var sessionId = "testSession_" + DateTime.Now.Ticks; + var actionsGroupId = "testActionsGroup_" + DateTime.Now.Ticks; + var entity = new TrackingActionEntity + { + ActionsGroupId = actionsGroupId, + SourceClientInstanceId = "source123", + IsSourceSuccess = true, + Size = 1024 + }; + entity.TargetClientInstanceIds.Add("target1"); + entity.TargetClientInstanceIds.Add("target2"); + + var syncEntity = new SynchronizationEntity + { + SessionId = sessionId, + LastSyncTime = DateTime.UtcNow + }; + + // Configure les mocks + A.CallTo(() => syncRepo.Get(sessionId)).Returns(syncEntity); + A.CallTo(() => factory.Create(sessionId, actionsGroupId)).Returns(entity); + + // Act - Récupère d'abord l'entité + var retrievedEntity = await repository.GetOrBuild(sessionId, actionsGroupId); + + // Assert + retrievedEntity.Should().NotBeNull(); + retrievedEntity.ActionsGroupId.Should().Be(actionsGroupId); + + // Vérifie qu'on peut ajouter un succès sur une cible + retrievedEntity.AddSuccessOnTarget("target1"); + retrievedEntity.SuccessTargetClientInstanceIds.Should().Contain("target1"); + + // Vérifie qu'on peut ajouter une erreur sur une cible + retrievedEntity.AddErrorOnTarget("target2"); + retrievedEntity.ErrorTargetClientInstanceIds.Should().Contain("target2"); + + // Test la propriété IsFinished + retrievedEntity.IsFinished.Should().BeTrue(); + retrievedEntity.IsSuccess.Should().BeFalse(); // Car il y a une erreur sur target2 + retrievedEntity.IsError.Should().BeTrue(); + retrievedEntity.IsErrorOnTarget.Should().BeTrue(); + } + + [TearDown] + public void Teardown() + { + _lockMock.Dispose(); + } +} From 6bc81c7709d7a6009e8fb136e1f55acee73413ea Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Fri, 11 Apr 2025 06:10:33 +0200 Subject: [PATCH 17/22] test: improve tests --- .../TrackingActionRepositoryTests.cs | 203 ++++++------------ 1 file changed, 68 insertions(+), 135 deletions(-) diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs index 1c6b90d04..c4f7fc792 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs @@ -1,12 +1,19 @@ -using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.Common.Business.Actions; +using ByteSync.ServerCommon.Business.Auth; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; using ByteSync.ServerCommon.Interfaces.Factories; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; +using ByteSync.ServerCommon.Misc; using ByteSync.ServerCommon.Repositories; +using ByteSync.ServerCommon.Services; +using ByteSync.ServerCommon.Tests.Helpers; using FakeItEasy; using FluentAssertions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using RedLockNet; using StackExchange.Redis; @@ -21,32 +28,52 @@ public class TrackingActionRepositoryTests private ITrackingActionEntityFactory _trackingActionEntityFactory; private ICacheRepository _cacheRepository; private ILogger _logger; - private IRedLock _lockMock; + // private IRedLock _lockMock; private ITransaction _transactionMock; + private ActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; [SetUp] public void SetUp() { - _redisInfrastructureService = A.Fake(); - _synchronizationRepository = A.Fake(); - _trackingActionEntityFactory = A.Fake(); - _cacheRepository = A.Fake>(); - _logger = A.Fake>(); - _lockMock = A.Fake(); - _transactionMock = A.Fake(); - - _repository = new TrackingActionRepository( - _redisInfrastructureService, - _synchronizationRepository, - _trackingActionEntityFactory, - _cacheRepository, - _logger); - - // Configuration des mocks communs - A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A._)) - .Returns(_lockMock); - A.CallTo(() => _redisInfrastructureService.OpenTransaction()) - .Returns(_transactionMock); + var redisSettings = TestSettingsInitializer.GetRedisSettings(); + var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); + var loggerFactoryMock = A.Fake(); + var loggerMock = A.Fake>(); + var cosmosDbSettings = TestSettingsInitializer.GetCosmosDbSettings(); + ByteSyncDbContext byteSyncDbContext = new ByteSyncDbContext(Options.Create(cosmosDbSettings)); + byteSyncDbContext.InitializeCosmosDb().Wait(); + _actionsGroupDefinitionsRepository = new ActionsGroupDefinitionsRepository(byteSyncDbContext); + var trackingActionEntityFactory = new TrackingActionEntityFactory(_actionsGroupDefinitionsRepository); + var synchronizationRepository = new SynchronizationRepository( + new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock), + new CacheRepository(new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock)), + _actionsGroupDefinitionsRepository); + _redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); + _cacheRepository = new CacheRepository(_redisInfrastructureService); + _repository = new TrackingActionRepository(_redisInfrastructureService, synchronizationRepository, trackingActionEntityFactory, _cacheRepository, + loggerMock); + + + // _redisInfrastructureService = A.Fake(); + // _synchronizationRepository = A.Fake(); + // _trackingActionEntityFactory = A.Fake(); + // _cacheRepository = A.Fake>(); + // _logger = A.Fake>(); + // _lockMock = A.Fake(); + // _transactionMock = A.Fake(); + // + // _repository = new TrackingActionRepository( + // _redisInfrastructureService, + // _synchronizationRepository, + // _trackingActionEntityFactory, + // _cacheRepository, + // _logger); + // + // // Configuration des mocks communs + // A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A._)) + // .Returns(_lockMock); + // A.CallTo(() => _redisInfrastructureService.OpenTransaction()) + // .Returns(_transactionMock); } [Test] @@ -60,8 +87,8 @@ public void EntityType_ShouldReturnTrackingAction() public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() { // Arrange - var sessionId = "session123"; - var actionsGroupId = "group456"; + var sessionId = "session123" + DateTime.Now.Ticks; + var actionsGroupId = "group456" + DateTime.Now.Ticks; var cacheKey = new CacheKey { EntityType = EntityType.TrackingAction, @@ -70,18 +97,27 @@ public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() }; var existingEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) - .Returns(cacheKey); - A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) - .Returns(existingEntity); + // A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) + // .Returns(cacheKey); + // A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) + // .Returns(existingEntity); + List actionsGroupDefinitions = new List + { + new ActionsGroupDefinition + { + ActionsGroupId = actionsGroupId, + } + }; + await _actionsGroupDefinitionsRepository.AddOrUpdateActionsGroupDefinitions(sessionId, actionsGroupDefinitions); + // Act var result = await _repository.GetOrBuild(sessionId, actionsGroupId); // Assert - result.Should().Be(existingEntity); - A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(cacheKey)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _trackingActionEntityFactory.Create(A._, A._)).MustNotHaveHappened(); + result.Should().BeEquivalentTo(existingEntity); + // A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(cacheKey)).MustHaveHappenedOnceExactly(); + // A.CallTo(() => _trackingActionEntityFactory.Create(A._, A._)).MustNotHaveHappened(); } [Test] @@ -220,112 +256,9 @@ public async Task AddOrUpdate_WhenUpdateFails_ShouldNotExecuteTransaction() } - [Test] - public async Task AddOrUpdate_ShouldReleaseAllLocksEvenOnFailure() - { - // Arrange - var sessionId = "session123"; - var actionsGroupIds = new List { "group1", "group2" }; - var syncEntity = new SynchronizationEntity { SessionId = sessionId }; - var lockMock1 = A.Fake(); - var lockMock2 = A.Fake(); - - A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == sessionId))) - .Returns(_lockMock); - A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == $"{sessionId}_{actionsGroupIds[0]}"))) - .Returns(lockMock1); - A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A.That.Matches(c => c.EntityId == $"{sessionId}_{actionsGroupIds[1]}"))) - .Returns(lockMock2); - A.CallTo(() => _synchronizationRepository.Get(sessionId)) - .Returns(syncEntity); - A.CallTo(() => _cacheRepository.Get(A._)) - .Returns(new TrackingActionEntity()); - - // Simuler un échec de l'update - Func updateHandler = (entity, sync) => false; - - // Act - await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); - - // Assert - A.CallTo(() => lockMock1.DisposeAsync()).MustHaveHappenedOnceExactly(); - A.CallTo(() => lockMock2.DisposeAsync()).MustHaveHappenedOnceExactly(); - } - - [Test] - public async Task AddOrUpdate_WithIntegrationTest() - { - // Configuration de l'intégration - var redisSettings = TestSettingsInitializer.GetRedisSettings(); - var cacheKeyFactory = new CacheKeyFactory(Options.Create(redisSettings)); - var loggerFactoryMock = A.Fake(); - var logger = A.Fake>(); - A.CallTo(() => loggerFactoryMock.CreateLogger(A._)).Returns(logger); - - var redisService = new RedisInfrastructureService( - Options.Create(redisSettings), - cacheKeyFactory, - loggerFactoryMock); - - var cacheRepo = new CacheRepository(redisService); - var syncRepo = A.Fake(); - var factory = A.Fake(); - - var repository = new TrackingActionRepository( - redisService, - syncRepo, - factory, - cacheRepo, - logger); - - // Données de test - var sessionId = "testSession_" + DateTime.Now.Ticks; - var actionsGroupId = "testActionsGroup_" + DateTime.Now.Ticks; - var entity = new TrackingActionEntity - { - ActionsGroupId = actionsGroupId, - SourceClientInstanceId = "source123", - IsSourceSuccess = true, - Size = 1024 - }; - entity.TargetClientInstanceIds.Add("target1"); - entity.TargetClientInstanceIds.Add("target2"); - - var syncEntity = new SynchronizationEntity - { - SessionId = sessionId, - LastSyncTime = DateTime.UtcNow - }; - - // Configure les mocks - A.CallTo(() => syncRepo.Get(sessionId)).Returns(syncEntity); - A.CallTo(() => factory.Create(sessionId, actionsGroupId)).Returns(entity); - - // Act - Récupère d'abord l'entité - var retrievedEntity = await repository.GetOrBuild(sessionId, actionsGroupId); - - // Assert - retrievedEntity.Should().NotBeNull(); - retrievedEntity.ActionsGroupId.Should().Be(actionsGroupId); - - // Vérifie qu'on peut ajouter un succès sur une cible - retrievedEntity.AddSuccessOnTarget("target1"); - retrievedEntity.SuccessTargetClientInstanceIds.Should().Contain("target1"); - - // Vérifie qu'on peut ajouter une erreur sur une cible - retrievedEntity.AddErrorOnTarget("target2"); - retrievedEntity.ErrorTargetClientInstanceIds.Should().Contain("target2"); - - // Test la propriété IsFinished - retrievedEntity.IsFinished.Should().BeTrue(); - retrievedEntity.IsSuccess.Should().BeFalse(); // Car il y a une erreur sur target2 - retrievedEntity.IsError.Should().BeTrue(); - retrievedEntity.IsErrorOnTarget.Should().BeTrue(); - } - [TearDown] public void Teardown() { - _lockMock.Dispose(); + // _lockMock.Dispose(); } } From 91aeaeff08fab4007898a69eb149def94de960d5 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Fri, 11 Apr 2025 06:28:54 +0200 Subject: [PATCH 18/22] test: improve unit tests --- .../Repositories/TrackingActionRepository.cs | 20 +- .../TrackingActionRepositoryTests.cs | 221 +++--------------- 2 files changed, 50 insertions(+), 191 deletions(-) diff --git a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs index 79ef59728..8d604c81d 100644 --- a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs @@ -4,6 +4,7 @@ using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; using Microsoft.Extensions.Logging; +using RedLockNet; namespace ByteSync.ServerCommon.Repositories; @@ -32,10 +33,10 @@ public async Task GetOrBuild(string sessionId, string acti await using var actionsGroupIdLock = await _redisInfrastructureService.AcquireLockAsync(cacheKey); - return await DoGetOrBuild(sessionId, actionsGroupId, cacheKey); + return await DoGetOrBuild(sessionId, actionsGroupId, cacheKey, actionsGroupIdLock); } - private async Task DoGetOrBuild(string sessionId, string actionsGroupId, CacheKey cacheKey) + private async Task DoGetOrBuild(string sessionId, string actionsGroupId, CacheKey cacheKey, IRedLock actionsGroupIdLock) { var trackingActionEntity = await Get($"{sessionId}_{actionsGroupId}"); @@ -43,7 +44,7 @@ private async Task DoGetOrBuild(string sessionId, string a { trackingActionEntity = await _trackingActionEntityFactory.Create(sessionId, actionsGroupId); - await Save(cacheKey, trackingActionEntity); + await Save(cacheKey, trackingActionEntity, null, actionsGroupIdLock); } return trackingActionEntity; @@ -55,7 +56,12 @@ public async Task AddOrUpdate(string sessionId, List(); @@ -73,12 +79,12 @@ public async Task AddOrUpdate(string sessionId, List AddOrUpdate(string sessionId, List _cacheRepository; - private ILogger _logger; - // private IRedLock _lockMock; - private ITransaction _transactionMock; private ActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; [SetUp] @@ -43,72 +36,33 @@ public void SetUp() ByteSyncDbContext byteSyncDbContext = new ByteSyncDbContext(Options.Create(cosmosDbSettings)); byteSyncDbContext.InitializeCosmosDb().Wait(); _actionsGroupDefinitionsRepository = new ActionsGroupDefinitionsRepository(byteSyncDbContext); - var trackingActionEntityFactory = new TrackingActionEntityFactory(_actionsGroupDefinitionsRepository); - var synchronizationRepository = new SynchronizationRepository( + _trackingActionEntityFactory = new TrackingActionEntityFactory(_actionsGroupDefinitionsRepository); + _synchronizationRepository = new SynchronizationRepository( new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock), new CacheRepository(new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock)), _actionsGroupDefinitionsRepository); _redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); _cacheRepository = new CacheRepository(_redisInfrastructureService); - _repository = new TrackingActionRepository(_redisInfrastructureService, synchronizationRepository, trackingActionEntityFactory, _cacheRepository, + _repository = new TrackingActionRepository(_redisInfrastructureService, _synchronizationRepository, _trackingActionEntityFactory, _cacheRepository, loggerMock); - - - // _redisInfrastructureService = A.Fake(); - // _synchronizationRepository = A.Fake(); - // _trackingActionEntityFactory = A.Fake(); - // _cacheRepository = A.Fake>(); - // _logger = A.Fake>(); - // _lockMock = A.Fake(); - // _transactionMock = A.Fake(); - // - // _repository = new TrackingActionRepository( - // _redisInfrastructureService, - // _synchronizationRepository, - // _trackingActionEntityFactory, - // _cacheRepository, - // _logger); - // - // // Configuration des mocks communs - // A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(A._)) - // .Returns(_lockMock); - // A.CallTo(() => _redisInfrastructureService.OpenTransaction()) - // .Returns(_transactionMock); - } - - [Test] - public void EntityType_ShouldReturnTrackingAction() - { - // Assert - _repository.EntityType.Should().Be(EntityType.TrackingAction); } [Test] public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() { // Arrange - var sessionId = "session123" + DateTime.Now.Ticks; - var actionsGroupId = "group456" + DateTime.Now.Ticks; - var cacheKey = new CacheKey - { - EntityType = EntityType.TrackingAction, - EntityId = $"{sessionId}_{actionsGroupId}", - Value = $"test:TrackingAction:{sessionId}_{actionsGroupId}" - }; + var nowTicks = DateTime.Now.Ticks; + var sessionId = "session_" + nowTicks; + var actionsGroupId = "group_" + nowTicks; var existingEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; - // A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) - // .Returns(cacheKey); - // A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) - // .Returns(existingEntity); - - List actionsGroupDefinitions = new List - { - new ActionsGroupDefinition + List actionsGroupDefinitions = + [ + new() { ActionsGroupId = actionsGroupId, } - }; + ]; await _actionsGroupDefinitionsRepository.AddOrUpdateActionsGroupDefinitions(sessionId, actionsGroupDefinitions); // Act @@ -116,149 +70,48 @@ public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() // Assert result.Should().BeEquivalentTo(existingEntity); - // A.CallTo(() => _redisInfrastructureService.AcquireLockAsync(cacheKey)).MustHaveHappenedOnceExactly(); - // A.CallTo(() => _trackingActionEntityFactory.Create(A._, A._)).MustNotHaveHappened(); } - - [Test] - public async Task GetOrBuild_WhenEntityDoesNotExist_ShouldCreateAndSaveNewEntity() - { - // Arrange - var sessionId = "session123"; - var actionsGroupId = "group456"; - var cacheKey = new CacheKey - { - EntityType = EntityType.TrackingAction, - EntityId = $"{sessionId}_{actionsGroupId}", - Value = $"test:TrackingAction:{sessionId}_{actionsGroupId}" - }; - var newEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; - - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupId}")) - .Returns(cacheKey); - A.CallTo(() => _cacheRepository.Get(cacheKey, A._)) - .Returns(default(TrackingActionEntity)); - A.CallTo(() => _trackingActionEntityFactory.Create(sessionId, actionsGroupId)) - .Returns(newEntity); - - // Act - var result = await _repository.GetOrBuild(sessionId, actionsGroupId); - // Assert - result.Should().Be(newEntity); - A.CallTo(() => _trackingActionEntityFactory.Create(sessionId, actionsGroupId)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _cacheRepository.Save(cacheKey, newEntity, null, null)).MustHaveHappenedOnceExactly(); - } - [Test] - public async Task AddOrUpdate_WhenAllUpdatesSucceed_ShouldUpdateAllEntitiesAndSynchronization() + public async Task AddOrUpdate_WhenAllUpdatesSucceed_ShouldReturnUpdatedEntities() { // Arrange - var sessionId = "session123"; + var sessionId = "session_" + DateTime.Now.Ticks; var actionsGroupIds = new List { "group1", "group2" }; - var syncEntity = new SynchronizationEntity { SessionId = sessionId }; - var trackingEntity1 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[0] }; - var trackingEntity2 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[1] }; - var syncCacheKey = new CacheKey - { - EntityType = EntityType.Synchronization, - EntityId = sessionId, - Value = $"test:Synchronization:{sessionId}" - }; - var cacheKey1 = new CacheKey - { - EntityType = EntityType.TrackingAction, - EntityId = $"{sessionId}_{actionsGroupIds[0]}", - Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[0]}" - }; - var cacheKey2 = new CacheKey - { - EntityType = EntityType.TrackingAction, - EntityId = $"{sessionId}_{actionsGroupIds[1]}", - Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[1]}" - }; + var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; + List actionsGroupDefinitions = + [ + new() + { + ActionsGroupId = "group1", + }, - bool updateHandlerResult = true; - Func updateHandler = (_, _) => updateHandlerResult; - - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId)) - .Returns(syncCacheKey); - A.CallTo(() => _synchronizationRepository.Get(sessionId)) - .Returns(syncEntity); - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[0]}")) - .Returns(cacheKey1); - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[1]}")) - .Returns(cacheKey2); - A.CallTo(() => _cacheRepository.Get(cacheKey1, A._)) - .Returns(trackingEntity1); - A.CallTo(() => _cacheRepository.Get(cacheKey2, A._)) - .Returns(trackingEntity2); - - // Act - var result = await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); - - // Assert - result.IsSuccess.Should().BeTrue(); - result.TrackingActionEntities.Should().Contain(trackingEntity1); - result.TrackingActionEntities.Should().Contain(trackingEntity2); - result.SynchronizationEntity.Should().Be(syncEntity); - - A.CallTo(() => _cacheRepository.Save(cacheKey1, trackingEntity1, _transactionMock, null)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _cacheRepository.Save(cacheKey2, trackingEntity2, _transactionMock, null)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _synchronizationRepository.Save(syncCacheKey, syncEntity, _transactionMock, null)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _transactionMock.ExecuteAsync(A._)).MustHaveHappenedOnceExactly(); - } + new() + { + ActionsGroupId = "group2", + } + ]; + + await _synchronizationRepository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); - - [Test] - public async Task AddOrUpdate_WhenUpdateFails_ShouldNotExecuteTransaction() - { - // Arrange - var sessionId = "session123"; - var actionsGroupIds = new List { "group1", "group2" }; - var syncEntity = new SynchronizationEntity { SessionId = sessionId }; - var trackingEntity1 = new TrackingActionEntity { ActionsGroupId = actionsGroupIds[0] }; - var syncCacheKey = new CacheKey + Func updateHandler = (_, _) => { - EntityType = EntityType.Synchronization, - EntityId = sessionId, - Value = $"test:Synchronization:{sessionId}" + return true; }; - var cacheKey1 = new CacheKey - { - EntityType = EntityType.TrackingAction, - EntityId = $"{sessionId}_{actionsGroupIds[0]}", - Value = $"test:TrackingAction:{sessionId}_{actionsGroupIds[0]}" - }; - - bool updateHandlerResult = false; - Func updateHandler = (_, _) => updateHandlerResult; - - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.Synchronization, sessionId)) - .Returns(syncCacheKey); - A.CallTo(() => _synchronizationRepository.Get(sessionId)) - .Returns(syncEntity); - A.CallTo(() => _redisInfrastructureService.ComputeCacheKey(EntityType.TrackingAction, $"{sessionId}_{actionsGroupIds[0]}")) - .Returns(cacheKey1); - A.CallTo(() => _cacheRepository.Get(cacheKey1, A._)) - .Returns(trackingEntity1); // Act var result = await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); // Assert - result.IsSuccess.Should().BeFalse(); - result.TrackingActionEntities.Should().BeEmpty(); - result.SynchronizationEntity.Should().Be(syncEntity); - - A.CallTo(() => _transactionMock.ExecuteAsync(A._)).MustNotHaveHappened(); - } - - - [TearDown] - public void Teardown() - { - // _lockMock.Dispose(); + result.IsSuccess.Should().BeTrue(); + result.TrackingActionEntities.Should().HaveCount(actionsGroupIds.Count); + result.TrackingActionEntities.Should().AllSatisfy(entity => + { + entity.Should().NotBeNull(); + entity.ActionsGroupId.Should().NotBeNullOrEmpty(); + actionsGroupDefinitions.Any(a => a.ActionsGroupId == entity.ActionsGroupId).Should().BeTrue(); + }); + result.SynchronizationEntity.Should().BeEquivalentTo(synchronizationEntity); } } From 327fa3bcfec4e61e16be2a93d38825707d9ebfd2 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Fri, 11 Apr 2025 07:27:44 +0200 Subject: [PATCH 19/22] fix: various fixes --- .../Helpers/Loaders/DependencyInjectionLoader.cs | 11 +++++++++++ .../Interfaces/Repositories/ICacheRepository.cs | 2 +- .../Repositories/CacheRepository.cs | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/ByteSync.Functions/Helpers/Loaders/DependencyInjectionLoader.cs b/src/ByteSync.Functions/Helpers/Loaders/DependencyInjectionLoader.cs index 3a0a195b0..e27aac301 100644 --- a/src/ByteSync.Functions/Helpers/Loaders/DependencyInjectionLoader.cs +++ b/src/ByteSync.Functions/Helpers/Loaders/DependencyInjectionLoader.cs @@ -25,6 +25,17 @@ private static void RegisterServerCommonAssembly(ContainerBuilder builder) .Where(t => t.Name.EndsWith("Repository")) .InstancePerLifetimeScope() .AsImplementedInterfaces(); + + var genericRepositoryTypes = executingAssembly.GetTypes() + .Where(t => t.Name.Contains("Repository`") && t.IsGenericTypeDefinition && !t.IsInterface); + + foreach (var genericType in genericRepositoryTypes) + { + builder.RegisterGeneric(genericType) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + } builder.RegisterAssemblyTypes(executingAssembly) .Where(t => t.Name.EndsWith("Service")) diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs index 991c173fa..63599b263 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs @@ -6,7 +6,7 @@ namespace ByteSync.ServerCommon.Interfaces.Repositories; public interface ICacheRepository where T : class { - Task Get(CacheKey cacheKey, ITransaction? transaction = null); + Task Get(CacheKey cacheKey); Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null); diff --git a/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs b/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs index ed5f6ace7..0332a949d 100644 --- a/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CacheRepository.cs @@ -18,9 +18,9 @@ public CacheRepository(IRedisInfrastructureService redisInfrastructureService) _redisInfrastructureService = redisInfrastructureService; } - public async Task Get(CacheKey cacheKey, ITransaction? transaction = null) + public async Task Get(CacheKey cacheKey) { - IDatabaseAsync database = _redisInfrastructureService.GetDatabase(transaction); + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(); string? serializedElement = await database.StringGetAsync(cacheKey.Value); if (serializedElement.IsNullOrEmpty()) @@ -59,7 +59,7 @@ public async Task> Update(CacheKey cacheKey, Func try { - var cachedElement = await Get(cacheKey, transaction); + var cachedElement = await Get(cacheKey); if (cachedElement == null) { @@ -93,7 +93,7 @@ public async Task> AddOrUpdate(CacheKey cacheKey, Func Date: Fri, 11 Apr 2025 08:22:06 +0200 Subject: [PATCH 20/22] feat: remove useless acquireLock feat: improve SessionMemberData.AuthCheckClientInstanceIds management --- .../CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs | 4 +--- .../Commands/Trusts/SendDigitalSignaturesCommandHandler.cs | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs index d5221c411..eea740e6b 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs @@ -43,8 +43,6 @@ public async Task Handle(FinalizeJoinCloudSessionRequ var transaction = _redisInfrastructureService.OpenTransaction(); - await using var sessionRedisLock = await _redisInfrastructureService.AcquireLockAsync(EntityType.Session, parameters.SessionId); - var updateResult = await _cloudSessionsRepository.Update(parameters.SessionId, innerCloudSessionData => { if (innerCloudSessionData.IsSessionRemoved) @@ -99,7 +97,7 @@ public async Task Handle(FinalizeJoinCloudSessionRequ { return false; } - }, transaction, sessionRedisLock); + }, transaction); if (updateResult.IsWaitingForTransaction) { diff --git a/src/ByteSync.ServerCommon/Commands/Trusts/SendDigitalSignaturesCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Trusts/SendDigitalSignaturesCommandHandler.cs index dea8dcf81..36863f8e8 100644 --- a/src/ByteSync.ServerCommon/Commands/Trusts/SendDigitalSignaturesCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/Trusts/SendDigitalSignaturesCommandHandler.cs @@ -71,7 +71,11 @@ await _cloudSessionsRepository.Update(cloudSession.SessionId, cloudSessionData = { foreach (var digitalSignatureCheckInfo in parameters.DigitalSignatureCheckInfos) { - member.AuthCheckClientInstanceIds.Add(digitalSignatureCheckInfo.Recipient); + var recipient = digitalSignatureCheckInfo.Recipient; + if (!member.AuthCheckClientInstanceIds.Contains(recipient)) + { + member.AuthCheckClientInstanceIds.Add(recipient); + } } return true; From f34c72802824207a141b245cc61305f7843a4055 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Fri, 11 Apr 2025 17:02:50 +0200 Subject: [PATCH 21/22] refactor: refactor IRepository & TrackingActionRepository --- .../Interfaces/Repositories/IRepository.cs | 2 -- .../Repositories/TrackingActionRepository.cs | 7 +++++-- .../Repositories/TrackingActionRepositoryTests.cs | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index de9a97774..53a7785b8 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -21,8 +21,6 @@ public interface IRepository Task> UpdateIfExists(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null); Task Delete(string key); diff --git a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs index 8d604c81d..e1059ecba 100644 --- a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs @@ -13,15 +13,18 @@ public class TrackingActionRepository : BaseRepository, IT private readonly IRedisInfrastructureService _redisInfrastructureService; private readonly ISynchronizationRepository _synchronizationRepository; private readonly ITrackingActionEntityFactory _trackingActionEntityFactory; + private readonly ICacheRepository _synchronizationCacheRepository; private readonly ILogger _logger; public TrackingActionRepository(IRedisInfrastructureService redisInfrastructureService, ISynchronizationRepository synchronizationRepository, ITrackingActionEntityFactory trackingActionEntityFactory, ICacheRepository cacheRepository, - ILogger logger) : base(redisInfrastructureService, cacheRepository) + ICacheRepository synchronizationCacheRepository, ILogger logger) + : base(redisInfrastructureService, cacheRepository) { _redisInfrastructureService = redisInfrastructureService; _synchronizationRepository = synchronizationRepository; _trackingActionEntityFactory = trackingActionEntityFactory; + _synchronizationCacheRepository = synchronizationCacheRepository; _logger = logger; } @@ -98,7 +101,7 @@ public async Task AddOrUpdate(string sessionId, List _cacheRepository; + private ICacheRepository _synchronizationCacheRepository; private ActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; [SetUp] @@ -43,8 +44,9 @@ public void SetUp() _actionsGroupDefinitionsRepository); _redisInfrastructureService = new RedisInfrastructureService(Options.Create(redisSettings), cacheKeyFactory, loggerFactoryMock); _cacheRepository = new CacheRepository(_redisInfrastructureService); + _synchronizationCacheRepository = new CacheRepository(_redisInfrastructureService); _repository = new TrackingActionRepository(_redisInfrastructureService, _synchronizationRepository, _trackingActionEntityFactory, _cacheRepository, - loggerMock); + _synchronizationCacheRepository, loggerMock); } [Test] From 0d5d37fd5ed01a4f609ee28e28a7588498c6a3bf Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Fri, 11 Apr 2025 17:08:02 +0200 Subject: [PATCH 22/22] refactor: improve IRepository --- .../Interfaces/Repositories/IRepository.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs index 53a7785b8..fba0ba530 100644 --- a/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs +++ b/src/ByteSync.ServerCommon/Interfaces/Repositories/IRepository.cs @@ -11,8 +11,6 @@ public interface IRepository Task Get(string key); - Task Get(CacheKey cacheKey); - Task> AddOrUpdate(string key, Func handler); Task> AddOrUpdate(string key, Func handler, ITransaction? transaction);