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 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/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/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 5e5ce934d..eea740e6b 100644 --- a/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs +++ b/src/ByteSync.ServerCommon/Commands/CloudSessions/FinalizeJoinCloudSessionCommandHandler.cs @@ -1,6 +1,7 @@ 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.Mappers; using ByteSync.ServerCommon.Interfaces.Repositories; @@ -17,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; } @@ -40,8 +41,8 @@ public async Task Handle(FinalizeJoinCloudSessionRequ FinalizeJoinSessionStatuses? finalizeJoinSessionStatus = null; SessionMemberData? joiner = null; - var transaction = _cacheService.OpenTransaction(); - + var transaction = _redisInfrastructureService.OpenTransaction(); + var updateResult = await _cloudSessionsRepository.Update(parameters.SessionId, innerCloudSessionData => { if (innerCloudSessionData.IsSessionRemoved) 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 bf71c1d88..630697b9c 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; @@ -18,7 +19,7 @@ public class StartInventoryCommandHandler : IRequestHandler _logger; public StartInventoryCommandHandler( @@ -26,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; } @@ -42,13 +43,11 @@ 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 _redisInfrastructureService.AcquireLockAsync(EntityType.Session, sessionId); - await using var inventoryRedisLock = await _cacheService.AcquireLockAsync(_inventoryRepository.ComputeCacheKey(_inventoryRepository.ElementName, - 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/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; 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/ICacheRepository.cs b/src/ByteSync.ServerCommon/Interfaces/Repositories/ICacheRepository.cs new file mode 100644 index 000000000..63599b263 --- /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); + + 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 729770bcb..fba0ba530 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,14 +7,10 @@ namespace ByteSync.ServerCommon.Interfaces.Repositories; public interface IRepository { - string ComputeCacheKey(params string[] keyParts); - - string ElementName { get; } + EntityType EntityType { get; } Task Get(string key); - Task Get(string key, ITransaction? transaction); - Task> AddOrUpdate(string key, Func handler); Task> AddOrUpdate(string key, Func handler, ITransaction? transaction); @@ -22,9 +19,7 @@ public interface IRepository Task> UpdateIfExists(string key, Func updateHandler, ITransaction? transaction = null, IRedLock? redisLock = null); - Task> Save(string key, T element, ITransaction? transaction = null); - - Task> SetElement(string cacheKey, T createdOrUpdatedElement, IDatabaseAsync database); + Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null); Task Delete(string key); diff --git a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs deleted file mode 100644 index da9002491..000000000 --- a/src/ByteSync.ServerCommon/Interfaces/Services/ICacheService.cs +++ /dev/null @@ -1,20 +0,0 @@ -using RedLockNet; -using RedLockNet.SERedis; -using StackExchange.Redis; - -namespace ByteSync.ServerCommon.Interfaces.Services; - -public interface ICacheService -{ - public RedLockFactory RedLockFactory { get; } - - public string Prefix { get; } - - ITransaction OpenTransaction(); - - IDatabaseAsync GetDatabase(); - - IDatabaseAsync GetDatabase(ITransaction? transaction); - - Task AcquireLockAsync(string key); -} \ No newline at end of file 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/IRedisInfrastructureService.cs b/src/ByteSync.ServerCommon/Interfaces/Services/IRedisInfrastructureService.cs new file mode 100644 index 000000000..7bfcf9921 --- /dev/null +++ b/src/ByteSync.ServerCommon/Interfaces/Services/IRedisInfrastructureService.cs @@ -0,0 +1,21 @@ +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; +using RedLockNet; +using StackExchange.Redis; + +namespace ByteSync.ServerCommon.Interfaces.Services; + +public interface IRedisInfrastructureService +{ + ITransaction OpenTransaction(); + + IDatabaseAsync GetDatabase(); + + IDatabaseAsync GetDatabase(ITransaction? transaction); + + Task AcquireLockAsync(EntityType entityType, string entityId); + + Task AcquireLockAsync(CacheKey cacheKey); + + 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 ffec17114..b7351e173 100644 --- a/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/BaseRepository.cs @@ -1,8 +1,5 @@ -using System.Text; -using ByteSync.Common.Controls.Json; -using ByteSync.Common.Helpers; -using ByteSync.ServerCommon.Business.Repositories; -using ByteSync.ServerCommon.Exceptions; +using ByteSync.ServerCommon.Business.Repositories; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; using RedLockNet; @@ -12,42 +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; } - private string Prefix => _cacheService.Prefix; + public abstract EntityType EntityType { get; } - public abstract string ElementName { 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) + 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 = ComputeCacheKey(ElementName, key); - - var cachedElement = await GetCachedElement(cacheKey); - return cachedElement; + return await _cacheRepository.Get(cacheKey); } public async Task> AddOrUpdate(string key, Func handler) @@ -57,134 +38,33 @@ public async Task> AddOrUpdate(string key, Func ha public async Task> AddOrUpdate(string key, Func handler, ITransaction? transaction) { - var cacheKey = ComputeCacheKey(ElementName, 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); - } + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + 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; + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + return await _cacheRepository.Update(cacheKey, updateHandler, false, transaction, redisLock); } - private async Task> DoUpdate(string key, Func updateHandler, bool throwIfNotExists, ITransaction? transaction, - IRedLock? redisLockParam) + public async Task> Save(string key, T element, ITransaction? transaction = null, IRedLock? redisLock = null) { - var cacheKey = ComputeCacheKey(ElementName, key); - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - - IRedLock? redisLock = redisLockParam; - bool shouldDispose = false; - - if (redisLock == null) - { - redisLock = await _cacheService.RedLockFactory.CreateLockAsync(cacheKey, TimeSpan.FromSeconds(30)); - shouldDispose = true; - } - - try - { - if (redisLock.IsAcquired) - { - 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); - } - } - else - { - throw new AcquireRedisLockException(key, redisLock); - } - } - finally - { - if (shouldDispose && redisLock != null) - { - await redisLock.DisposeAsync(); - } - } + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + return await _cacheRepository.Save(cacheKey, element, transaction, redisLock); } - public async Task> Save(string key, T element, ITransaction? transaction = null) + public async Task> Save(CacheKey cacheKey, T element, ITransaction? transaction = null, IRedLock? redisLock = null) { - var cacheKey = ComputeCacheKey(ElementName, key); - 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) - { - T? cachedElement = default(T); - - string? serializedElement = await _cacheService.GetDatabase().StringGetAsync(cacheKey); - if (serializedElement.IsNotEmpty()) - { - cachedElement = JsonHelper.Deserialize(serializedElement!); - } - - return cachedElement; - } - - public async Task> SetElement(string cacheKey, T createdOrUpdatedElement, IDatabaseAsync database) - { - string serializedElement = JsonHelper.Serialize(createdOrUpdatedElement); - - if (database is ITransaction) - { - _ = database.StringSetAsync(cacheKey, serializedElement, Expiry); - - return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.WaitingForTransaction); - } - else - { - await database.StringSetAsync(cacheKey, serializedElement, Expiry); - - return new UpdateEntityResult(createdOrUpdatedElement, UpdateEntityStatus.Saved); - } + return await _cacheRepository.Save(cacheKey, element, transaction, redisLock); } public async Task Delete(string key) @@ -194,9 +74,7 @@ public async Task Delete(string key) public async Task Delete(string key, ITransaction? transaction) { - var cacheKey = ComputeCacheKey(ElementName, key); - IDatabaseAsync database = _cacheService.GetDatabase(transaction); - - await database.KeyDeleteAsync(cacheKey); + var cacheKey = _cacheService.ComputeCacheKey(EntityType, key); + 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..0332a949d --- /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) + { + IDatabaseAsync database = _redisInfrastructureService.GetDatabase(); + 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); + + 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); + 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 60ff3e8ca..a72101c8c 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; @@ -6,11 +7,12 @@ namespace ByteSync.ServerCommon.Repositories; public class ClientSoftwareVersionSettingsRepository : BaseRepository, IClientSoftwareVersionSettingsRepository { - public ClientSoftwareVersionSettingsRepository(ICacheService cacheService) : base(cacheService) + public ClientSoftwareVersionSettingsRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } - 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..bc6ceeb6e 100644 --- a/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/ClientsRepository.cs @@ -1,9 +1,9 @@ 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; -using StackExchange.Redis; namespace ByteSync.ServerCommon.Repositories; @@ -11,12 +11,13 @@ 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; } - 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..0e3f400bf 100644 --- a/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/CloudSessionProfileRepository.cs @@ -6,9 +6,10 @@ namespace ByteSync.ServerCommon.Repositories; public class CloudSessionProfileRepository : BaseRepository, ICloudSessionProfileRepository { - public CloudSessionProfileRepository(ICacheService cacheService) : base(cacheService) + public CloudSessionProfileRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } - 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..00f0402ad 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; @@ -9,17 +11,20 @@ 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 string ComputeSessionCacheKey(CloudSessionData cloudSessionData) + private CacheKey ComputeSessionCacheKey(CloudSessionData cloudSessionData) { - return ComputeCacheKey("Session", cloudSessionData.SessionId); + return _redisInfrastructureService.ComputeCacheKey(EntityType, cloudSessionData.SessionId); } - public override string ElementName => "Session"; + public override EntityType EntityType => EntityType.Session; public Task GetSessionMember(string sessionId, Client client) { @@ -49,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); + string? serializedElement = await _redisInfrastructureService.GetDatabase().StringGetAsync(cacheKey.Value); if (serializedElement == null || serializedElement.IsEmpty()) { - await SetElement(cacheKey, cloudSessionData, transaction); + await Save(cacheKey, cloudSessionData, transaction, redisLock); isNewSessionOk = true; } } diff --git a/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs b/src/ByteSync.ServerCommon/Repositories/InventoryRepository.cs index 40d6237ac..bb3d47c78 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; @@ -6,11 +7,12 @@ namespace ByteSync.ServerCommon.Repositories; public class InventoryRepository : BaseRepository, IInventoryRepository { - public InventoryRepository(ICacheService cacheService) : base(cacheService) + public InventoryRepository(IRedisInfrastructureService redisInfrastructureService, + ICacheRepository cacheRepository) : base(redisInfrastructureService, cacheRepository) { } - 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..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 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); - 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; @@ -41,13 +45,13 @@ 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); } 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 042339417..cce115be2 100644 --- a/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SharedFilesRepository.cs @@ -1,6 +1,7 @@ using ByteSync.Common.Business.SharedFiles; +using ByteSync.ServerCommon.Business.Repositories; using ByteSync.ServerCommon.Business.Sessions; -using ByteSync.ServerCommon.Exceptions; +using ByteSync.ServerCommon.Entities; using ByteSync.ServerCommon.Interfaces.Repositories; using ByteSync.ServerCommon.Interfaces.Services; @@ -8,106 +9,101 @@ 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 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 _redisInfrastructureService.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 _redisInfrastructureService.ComputeCacheKey(EntityType.SessionSharedFiles, sessionId); } public async Task AddOrUpdate(SharedFileDefinition sharedFileDefinition, Func updateHandler) { - string sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); - string sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); - - var database = _cacheService.GetDatabase(); + var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); + var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinition); - await using var sessionSharedFilesLock = await _cacheService.AcquireLockAsync(sessionSharedFilesKey); - await using var sharedFileLock = await _cacheService.AcquireLockAsync(sharedFileCacheKey); + var transaction = _redisInfrastructureService.OpenTransaction(); + + 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, sharedFileDefinition.Id); + await Save(sharedFileCacheKey, element, transaction, sessionSharedFilesLock); + _ = transaction.SetAddAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + + await transaction.ExecuteAsync(); } public async Task Forget(SharedFileDefinition sharedFileDefinition) { - string sessionSharedFilesKey = ComputeSessionSharedFilesKey(sharedFileDefinition); - string sharedFileCacheKey = ComputeSharedFileCacheKey(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); - await database.SetRemoveAsync(sessionSharedFilesKey, sharedFileDefinition.Id); + _ = transaction.KeyDeleteAsync(sharedFileCacheKey.Value); + _ = transaction.SetRemoveAsync(sessionSharedFilesKey.Value, sharedFileDefinition.Id); + + await transaction.ExecuteAsync(); } public async Task> Clear(string sessionId) { List result = new List(); - string sessionSharedFilesKey = ComputeSessionSharedFilesKey(sessionId); - - var database = _cacheService.GetDatabase(); + var sessionSharedFilesKey = ComputeSessionSharedFilesKey(sessionId); - await using var sessionSharedFilesLock = await _cacheService.RedLockFactory.CreateLockAsync(sessionSharedFilesKey, TimeSpan.FromSeconds(30)); + await using var sessionSharedFilesLock = await _redisInfrastructureService.AcquireLockAsync(sessionSharedFilesKey); - if (sessionSharedFilesLock.IsAcquired) - { - var redisValues = await database.SetMembersAsync(sessionSharedFilesKey); - List sharedFileDefinitionIds = redisValues.Select(value => value.ToString()).ToList(); + var database = _redisInfrastructureService.GetDatabase(); + var redisValues = await database.SetMembersAsync(sessionSharedFilesKey.Value); + 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)); + var transaction = _redisInfrastructureService.OpenTransaction(); + foreach (var sharedFileDefinitionId in sharedFileDefinitionIds) + { + var sharedFileCacheKey = ComputeSharedFileCacheKey(sharedFileDefinitionId); + await using var sharedFileLock = await _redisInfrastructureService.AcquireLockAsync(sharedFileCacheKey); - if (sharedFileLock.IsAcquired) - { - var sharedFileData = await GetCachedElement(sharedFileCacheKey); + var sharedFileData = await Get(sharedFileCacheKey); - if (sharedFileData != null) - { - result.Add(sharedFileData); + if (sharedFileData != null) + { + result.Add(sharedFileData); - await database.KeyDeleteAsync(sharedFileCacheKey); - } - } - else - { - throw new AcquireRedisLockException(sharedFileCacheKey, sharedFileLock); - } + _ = transaction.KeyDeleteAsync(sharedFileCacheKey.Value); } - - await database.KeyDeleteAsync(sessionSharedFilesKey); - } - else - { - throw new AcquireRedisLockException(sessionSharedFilesKey, sessionSharedFilesLock); } + + _ = 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 58bc605eb..1126fe4cb 100644 --- a/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/SynchronizationRepository.cs @@ -9,12 +9,13 @@ 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; } - 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..e1059ecba 100644 --- a/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs +++ b/src/ByteSync.ServerCommon/Repositories/TrackingActionRepository.cs @@ -1,41 +1,45 @@ -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; using ByteSync.ServerCommon.Interfaces.Services; using Microsoft.Extensions.Logging; +using RedLockNet; 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 ICacheRepository _synchronizationCacheRepository; 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, + ICacheRepository synchronizationCacheRepository, ILogger logger) + : base(redisInfrastructureService, cacheRepository) { - _actionsGroupDefinitionsRepository = actionsGroupDefinitionsRepository; + _redisInfrastructureService = redisInfrastructureService; _synchronizationRepository = synchronizationRepository; _trackingActionEntityFactory = trackingActionEntityFactory; + _synchronizationCacheRepository = synchronizationCacheRepository; _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}"); - await using var actionsGroupIdLock = await _cacheService.AcquireLockAsync(cacheKey); + var cacheKey = _redisInfrastructureService.ComputeCacheKey(EntityType, $"{sessionId}_{actionsGroupId}"); + + 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, string cacheKey) + private async Task DoGetOrBuild(string sessionId, string actionsGroupId, CacheKey cacheKey, IRedLock actionsGroupIdLock) { var trackingActionEntity = await Get($"{sessionId}_{actionsGroupId}"); @@ -43,7 +47,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; @@ -52,11 +56,16 @@ 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); - 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)); + if (synchronizationEntity == null) + { + throw new InvalidOperationException($"SynchronizationEntity for session {sessionId} not found"); + } - var synchronizationEntity = (await _synchronizationRepository.Get(sessionId))!; - var transaction = _cacheService.OpenTransaction(); + var transaction = _redisInfrastructureService.OpenTransaction(); var locks = new List(); @@ -69,16 +78,16 @@ public async Task AddOrUpdate(string sessionId, List AddOrUpdate(string sessionId, List redisSettings, ILoggerFactory loggerFactory) + public RedisInfrastructureService(IOptions redisSettings, ICacheKeyFactory cacheKeyFactory, ILoggerFactory loggerFactory) { _redisSettings = redisSettings.Value; + _cacheKeyFactory = cacheKeyFactory; _connectionMultiplexer = ConnectionMultiplexer.Connect(_redisSettings.ConnectionString); @@ -30,16 +35,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; public ITransaction OpenTransaction() { @@ -58,9 +53,16 @@ public IDatabaseAsync GetDatabase(ITransaction? transaction) return database; } - public async Task AcquireLockAsync(string key) + public async Task AcquireLockAsync(EntityType entityType, string entityId) + { + var cacheKey = ComputeCacheKey(entityType, entityId); + + return await AcquireLockAsync(cacheKey); + } + + public async Task AcquireLockAsync(CacheKey cacheKey) { - var redisLock = await _redLockFactory.CreateLockAsync(key, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(1)); + var redisLock = await _redLockFactory.CreateLockAsync(cacheKey.Value, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(1)); if (redisLock.IsAcquired) { @@ -68,7 +70,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.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs index 0757d71f7..1b299ef30 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/BaseElementTypeModule.cs @@ -23,6 +23,17 @@ protected override void Load(ContainerBuilder builder) .AsImplementedInterfaces() .AsSelf() .InstancePerLifetimeScope(); + + 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 { diff --git a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs index af7381aa3..4dff184e7 100644 --- a/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs +++ b/tests/ByteSync.Functions.IntegrationTests/TestHelpers/Autofac/RepositoriesModule.cs @@ -4,7 +4,7 @@ public class RepositoriesModule : BaseElementTypeModule { public RepositoriesModule(bool useConcrete) : base(useConcrete) { - + } protected override string ElementsType => "Repository"; 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/Factories/CacheKeyFactoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Factories/CacheKeyFactoryTests.cs new file mode 100644 index 000000000..8525e8172 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Factories/CacheKeyFactoryTests.cs @@ -0,0 +1,83 @@ +using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Options; + +namespace ByteSync.ServerCommon.Tests.Factories; + +public class CacheKeyFactoryTests +{ + private CacheKeyFactory _cacheKeyFactory; + private IOptions _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"); + } +} 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/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(); + } +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs new file mode 100644 index 000000000..d71aaaab6 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/CloudSessionsRepositoryTests.cs @@ -0,0 +1,164 @@ +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; + +[TestFixture] +public class CloudSessionsRepositoryTests +{ + private CloudSessionsRepository _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 CloudSessionsRepository(_redisInfrastructureService, _cacheRepository); + } + + [Test] + public async Task GetSessionMember_WithValidData_ReturnsCorrectSessionMember() + { + // Arrange + 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 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 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 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); + } +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs new file mode 100644 index 000000000..e899ab7ac --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/InventoryRepositoryTests.cs @@ -0,0 +1,148 @@ +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; + +[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 + 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 + 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 + 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 + 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 + 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"); + } +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs index ad378e77f..4d6170021 100644 --- a/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SharedFilesRepositoryTests.cs @@ -1,55 +1,120 @@ using ByteSync.Common.Business.SharedFiles; using ByteSync.ServerCommon.Business.Sessions; -using ByteSync.ServerCommon.Business.Settings; +using ByteSync.ServerCommon.Entities; +using ByteSync.ServerCommon.Factories; using ByteSync.ServerCommon.Repositories; using ByteSync.ServerCommon.Services; using ByteSync.ServerCommon.Tests.Helpers; using FakeItEasy; -using Microsoft.Extensions.Configuration; +using FluentAssertions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; 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 void Test() + public async Task AddOrUpdate_IntegrationTest() { - Assert.Pass(); + // Arrange + 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 loggerFactoryMock = A.Fake(); - var cacheService = new CacheService(Options.Create(redisSettings), loggerFactoryMock); - var repository = new SharedFilesRepository(cacheService); + 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 sharedFileDefinition = new SharedFileDefinition { SessionId = "testSession_" + DateTime.Now.Ticks, 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.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 +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs new file mode 100644 index 000000000..d3904aba4 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/SynchronizationRepositoryTests.cs @@ -0,0 +1,97 @@ +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; + +[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 + 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 + 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(); + } +} diff --git a/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs new file mode 100644 index 000000000..7fdcbe266 --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Repositories/TrackingActionRepositoryTests.cs @@ -0,0 +1,119 @@ +using ByteSync.Common.Business.Actions; +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; + +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 ICacheRepository _synchronizationCacheRepository; + private ActionsGroupDefinitionsRepository _actionsGroupDefinitionsRepository; + + [SetUp] + public void SetUp() + { + 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); + _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); + _synchronizationCacheRepository = new CacheRepository(_redisInfrastructureService); + _repository = new TrackingActionRepository(_redisInfrastructureService, _synchronizationRepository, _trackingActionEntityFactory, _cacheRepository, + _synchronizationCacheRepository, loggerMock); + } + + [Test] + public async Task GetOrBuild_WhenEntityExists_ShouldReturnExistingEntity() + { + // Arrange + var nowTicks = DateTime.Now.Ticks; + var sessionId = "session_" + nowTicks; + var actionsGroupId = "group_" + nowTicks; + var existingEntity = new TrackingActionEntity { ActionsGroupId = actionsGroupId }; + + List actionsGroupDefinitions = + [ + new() + { + ActionsGroupId = actionsGroupId, + } + ]; + await _actionsGroupDefinitionsRepository.AddOrUpdateActionsGroupDefinitions(sessionId, actionsGroupDefinitions); + + // Act + var result = await _repository.GetOrBuild(sessionId, actionsGroupId); + + // Assert + result.Should().BeEquivalentTo(existingEntity); + } + + [Test] + public async Task AddOrUpdate_WhenAllUpdatesSucceed_ShouldReturnUpdatedEntities() + { + // Arrange + var sessionId = "session_" + DateTime.Now.Ticks; + var actionsGroupIds = new List { "group1", "group2" }; + var synchronizationEntity = new SynchronizationEntity { SessionId = sessionId }; + + List actionsGroupDefinitions = + [ + new() + { + ActionsGroupId = "group1", + }, + + new() + { + ActionsGroupId = "group2", + } + ]; + + await _synchronizationRepository.AddSynchronization(synchronizationEntity, actionsGroupDefinitions); + + Func updateHandler = (_, _) => + { + return true; + }; + + // Act + var result = await _repository.AddOrUpdate(sessionId, actionsGroupIds, updateHandler); + + // Assert + 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); + } +} 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;