From a651b56addb8e88ff6cab26892c1abbd21428d0e Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 13 Apr 2025 20:15:04 +0200 Subject: [PATCH 1/6] feat: improve DataPartIndexer to better handle multi-parts inventories --- .../Actions/Local/SynchronizationRule.cs | 6 +- .../Services/Sessions/DataPartIndexer.cs | 7 +- .../Services/Sessions/DataPartIndexerTests.cs | 110 ++++++++++++++++++ 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 tests/ByteSync.Client.Tests/Services/Sessions/DataPartIndexerTests.cs diff --git a/src/ByteSync.Client/Business/Actions/Local/SynchronizationRule.cs b/src/ByteSync.Client/Business/Actions/Local/SynchronizationRule.cs index 20d056ace..08e01ac37 100644 --- a/src/ByteSync.Client/Business/Actions/Local/SynchronizationRule.cs +++ b/src/ByteSync.Client/Business/Actions/Local/SynchronizationRule.cs @@ -34,11 +34,11 @@ public List GetActions() return new List(Actions); } - internal List Conditions { get; set; } + public List Conditions { get; set; } - internal List Actions { get; set; } + public List Actions { get; set; } - internal void AddAction(AtomicAction atomicAction) + public void AddAction(AtomicAction atomicAction) { atomicAction.SynchronizationRule = this; diff --git a/src/ByteSync.Client/Services/Sessions/DataPartIndexer.cs b/src/ByteSync.Client/Services/Sessions/DataPartIndexer.cs index 811fe7957..af51c42b4 100644 --- a/src/ByteSync.Client/Services/Sessions/DataPartIndexer.cs +++ b/src/ByteSync.Client/Services/Sessions/DataPartIndexer.cs @@ -26,18 +26,19 @@ public void BuildMap(List inventories) DataPartsByNames.Clear(); + bool isInventoryWithMultipleParts = inventories.Any(i => i.InventoryParts.Count > 1); + var cptInventory = 0; foreach (var inventory in Inventories) { var inventoryLetter = ((char)('A' + cptInventory)).ToString(); - if (inventory.InventoryParts.Count == 1) + if (!isInventoryWithMultipleParts) { var dataPart = new DataPart(inventoryLetter, inventory); DataPartsByNames.Add(dataPart.Name, dataPart); } - - if (inventory.InventoryParts.Count > 1) + else { var cptPart = 1; foreach (var inventoryPart in inventory.InventoryParts) diff --git a/tests/ByteSync.Client.Tests/Services/Sessions/DataPartIndexerTests.cs b/tests/ByteSync.Client.Tests/Services/Sessions/DataPartIndexerTests.cs new file mode 100644 index 000000000..577a0f5df --- /dev/null +++ b/tests/ByteSync.Client.Tests/Services/Sessions/DataPartIndexerTests.cs @@ -0,0 +1,110 @@ +using ByteSync.Business.Actions.Local; +using ByteSync.Business.Comparisons; +using ByteSync.Common.Business.Inventories; +using ByteSync.Models.Inventories; +using ByteSync.Services.Sessions; +using FluentAssertions; +using NUnit.Framework; + +namespace ByteSync.Tests.Services.Sessions; + +[TestFixture] +public class DataPartIndexerTests +{ + private DataPartIndexer _dataPartIndexer; + + [SetUp] + public void SetUp() + { + _dataPartIndexer = new DataPartIndexer(); + } + + [Test] + public void BuildMap_ShouldMapSingleInventoryCorrectly() + { + // Arrange + var inventory = new Inventory + { + InventoryParts = [new InventoryPart()] + }; + + // Act + _dataPartIndexer.BuildMap([inventory]); + + // Assert + var dataParts = _dataPartIndexer.GetAllDataParts(); + dataParts.Should().HaveCount(1); + dataParts[0].Name.Should().Be("A"); + dataParts[0].Inventory.Should().Be(inventory); + } + + [Test] + public void BuildMap_ShouldMapMultipleInventoryPartsCorrectly() + { + // Arrange + var inventory = new Inventory + { + InventoryParts = [new InventoryPart(), new InventoryPart()] + }; + + // Act + _dataPartIndexer.BuildMap([inventory]); + + // Assert + var dataParts = _dataPartIndexer.GetAllDataParts(); + dataParts.Should().HaveCount(2); + dataParts[0].Name.Should().Be("A1"); + dataParts[1].Name.Should().Be("A2"); + } + + [Test] + public void GetDataPart_ShouldReturnCorrectDataPart_WhenNameExists() + { + // Arrange + var inventory = new Inventory + { + InventoryParts = [new InventoryPart()] + }; + _dataPartIndexer.BuildMap([inventory]); + + // Act + var dataPart = _dataPartIndexer.GetDataPart("A"); + + // Assert + dataPart.Should().NotBeNull(); + dataPart.Name.Should().Be("A"); + } + + [Test] + public void GetDataPart_ShouldReturnNull_WhenNameDoesNotExist() + { + // Act + var dataPart = _dataPartIndexer.GetDataPart("NonExistent"); + + // Assert + dataPart.Should().BeNull(); + } + + [Test] + public void Remap_ShouldUpdateActionsAndConditionsCorrectly() + { + // Arrange + var inventory = new Inventory + { + InventoryParts = [new InventoryPart()] + }; + _dataPartIndexer.BuildMap([inventory]); + + var sourcePart = _dataPartIndexer.GetDataPart("A"); + var synchronizationRule = new SynchronizationRule(FileSystemTypes.File, ConditionModes.All); + synchronizationRule.AddAction(new AtomicAction { Source = sourcePart, Destination = sourcePart }); + synchronizationRule.Conditions.Add(new AtomicCondition { Source = sourcePart!, Destination = sourcePart }); + + // Act + _dataPartIndexer.Remap(new List { synchronizationRule }); + + // Assert + synchronizationRule.Actions[0].Source.Should().Be(sourcePart); + synchronizationRule.Conditions[0].Source.Should().Be(sourcePart); + } +} From de7dbe7b2720aef00946ae1d9435fc06d721e890 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 13 Apr 2025 20:17:23 +0200 Subject: [PATCH 2/6] test: improve TestDataEncrypter --- .../Services/Encryptions/TestDataEncrypter.cs | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/ByteSync.Client.Tests/Services/Encryptions/TestDataEncrypter.cs b/tests/ByteSync.Client.Tests/Services/Encryptions/TestDataEncrypter.cs index 98e99058a..c8813bac7 100644 --- a/tests/ByteSync.Client.Tests/Services/Encryptions/TestDataEncrypter.cs +++ b/tests/ByteSync.Client.Tests/Services/Encryptions/TestDataEncrypter.cs @@ -7,7 +7,7 @@ using ByteSync.TestsCommon; using Moq; using NUnit.Framework; -using NUnit.Framework.Legacy; +using FluentAssertions; namespace ByteSync.Tests.Services.Encryptions; @@ -35,53 +35,53 @@ public void TestSessionSettings() _mockCloudSessionConnectionDataHolder.Setup(x => x.GetAesEncryptionKey()).Returns(Aes.Create().Key); - // DataEncrypter dataEncrypter = new DataEncrypter(generator.CloudSessionConnectionDataHolder.Object); sessionSettings = SessionSettings.BuildDefault(); - // Encryptage 1 + // Encryption 1 encryptedSessionSettings1 = _dataEncrypter.EncryptSessionSettings(sessionSettings); - // Encryptage 2 + // Encryption 2 encryptedSessionSettings2 = _dataEncrypter.EncryptSessionSettings(sessionSettings); - ClassicAssert.IsTrue(encryptedSessionSettings1.Data.Length > 0); - ClassicAssert.IsTrue(encryptedSessionSettings1.IV.Length > 0); + encryptedSessionSettings1.Data.Length.Should().BeGreaterThan(0); + encryptedSessionSettings1.IV.Length.Should().BeGreaterThan(0); - ClassicAssert.IsTrue(encryptedSessionSettings2.Data.Length > 0); - ClassicAssert.IsTrue(encryptedSessionSettings2.IV.Length > 0); + encryptedSessionSettings2.Data.Length.Should().BeGreaterThan(0); + encryptedSessionSettings2.IV.Length.Should().BeGreaterThan(0); - ClassicAssert.IsTrue(encryptedSessionSettings1.Data.SequenceEqual(encryptedSessionSettings1.Data)); - ClassicAssert.IsTrue(encryptedSessionSettings1.IV.SequenceEqual(encryptedSessionSettings1.IV)); + encryptedSessionSettings1.Data.Should().Equal(encryptedSessionSettings1.Data); + encryptedSessionSettings1.IV.Should().Equal(encryptedSessionSettings1.IV); - ClassicAssert.IsFalse(encryptedSessionSettings1.Data.SequenceEqual(encryptedSessionSettings2.Data)); - ClassicAssert.IsFalse(encryptedSessionSettings1.IV.SequenceEqual(encryptedSessionSettings2.IV)); + encryptedSessionSettings1.Data.Should().NotEqual(encryptedSessionSettings2.Data); + encryptedSessionSettings1.IV.Should().NotEqual(encryptedSessionSettings2.IV); - // Décryptage 1 + // Decryption 1 sessionSettings1 = _dataEncrypter.DecryptSessionSettings(encryptedSessionSettings1); - // Décryptage 2 + // Decryption 2 sessionSettings2 = _dataEncrypter.DecryptSessionSettings(encryptedSessionSettings2); - // On teste certaines propriétés - ClassicAssert.AreEqual(sessionSettings.Extensions, sessionSettings1.Extensions); - ClassicAssert.AreEqual(sessionSettings.AllowedExtensions, sessionSettings1.AllowedExtensions); - ClassicAssert.IsTrue(sessionSettings.AllowedExtensions.HaveSameContent(sessionSettings1.AllowedExtensions)); - ClassicAssert.AreEqual(sessionSettings.AnalysisMode, sessionSettings1.AnalysisMode); - ClassicAssert.AreEqual(sessionSettings.DataType, sessionSettings1.DataType); - ClassicAssert.AreEqual(sessionSettings.LinkingCase, sessionSettings1.LinkingCase); - ClassicAssert.AreEqual(sessionSettings.LinkingKey, sessionSettings1.LinkingKey); - ClassicAssert.AreEqual(sessionSettings.ForbiddenExtensions, sessionSettings1.ForbiddenExtensions); - ClassicAssert.IsTrue(sessionSettings.ForbiddenExtensions.HaveSameContent(sessionSettings1.ForbiddenExtensions)); + // Test some properties + sessionSettings.Extensions.Should().BeEquivalentTo(sessionSettings1.Extensions); + sessionSettings.AllowedExtensions.Should().BeEquivalentTo(sessionSettings1.AllowedExtensions); + sessionSettings.AllowedExtensions.HaveSameContent(sessionSettings1.AllowedExtensions).Should().BeTrue(); + sessionSettings.AnalysisMode.Should().Be(sessionSettings1.AnalysisMode); + sessionSettings.DataType.Should().Be(sessionSettings1.DataType); + sessionSettings.LinkingCase.Should().Be(sessionSettings1.LinkingCase); + sessionSettings.LinkingKey.Should().Be(sessionSettings1.LinkingKey); + sessionSettings.ForbiddenExtensions.Should().BeEquivalentTo(sessionSettings1.ForbiddenExtensions); + sessionSettings.ForbiddenExtensions.HaveSameContent(sessionSettings1.ForbiddenExtensions).Should().BeTrue(); - // On teste certaines propriétés - ClassicAssert.AreEqual(sessionSettings.Extensions, sessionSettings2.Extensions); - ClassicAssert.AreEqual(sessionSettings.AllowedExtensions, sessionSettings2.AllowedExtensions); - ClassicAssert.IsTrue(sessionSettings.AllowedExtensions.HaveSameContent(sessionSettings2.AllowedExtensions)); - ClassicAssert.AreEqual(sessionSettings.AnalysisMode, sessionSettings2.AnalysisMode); - ClassicAssert.AreEqual(sessionSettings.DataType, sessionSettings2.DataType); - ClassicAssert.AreEqual(sessionSettings.LinkingCase, sessionSettings2.LinkingCase); - ClassicAssert.AreEqual(sessionSettings.LinkingKey, sessionSettings2.LinkingKey); - ClassicAssert.AreEqual(sessionSettings.ForbiddenExtensions, sessionSettings2.ForbiddenExtensions); - ClassicAssert.IsTrue(sessionSettings.ForbiddenExtensions.HaveSameContent(sessionSettings2.ForbiddenExtensions)); + // Test some properties + sessionSettings.Extensions.Should().BeEquivalentTo(sessionSettings2.Extensions); + sessionSettings.AllowedExtensions.Should().BeEquivalentTo(sessionSettings2.AllowedExtensions); + sessionSettings.AllowedExtensions.HaveSameContent(sessionSettings2.AllowedExtensions).Should().BeTrue(); + sessionSettings.AnalysisMode.Should().Be(sessionSettings2.AnalysisMode); + sessionSettings.DataType.Should().Be(sessionSettings2.DataType); + sessionSettings.LinkingCase.Should().Be(sessionSettings2.LinkingCase); + sessionSettings.LinkingKey.Should().Be(sessionSettings2.LinkingKey); + sessionSettings.ForbiddenExtensions.Should().BeEquivalentTo(sessionSettings2.ForbiddenExtensions); + sessionSettings.ForbiddenExtensions.HaveSameContent(sessionSettings2.ForbiddenExtensions).Should().BeTrue(); } -} \ No newline at end of file +} + From 3712d8cb1de7b038d9fbd76edf7153d625991c54 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 13 Apr 2025 22:22:00 +0200 Subject: [PATCH 3/6] refactor: refactor UpdateExistingFilesBackuper test: add integration tests --- .../Updates/UpdateExistingFilesBackuper.cs | 112 +++++++---- .../TestUpdateExistingFilesBackuper.cs | 190 ++++++++++++++++++ 2 files changed, 260 insertions(+), 42 deletions(-) create mode 100644 tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs diff --git a/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs b/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs index 42bd36977..00ab25ffc 100644 --- a/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs +++ b/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs @@ -21,76 +21,104 @@ public UpdateExistingFilesBackuper(IUpdateRepository updateRepository, ILogger> BackedUpFileSystemInfos { get; } - public async Task BackupExistingFilesAsync(CancellationToken cancellationToken) + public Task BackupExistingFilesAsync(CancellationToken cancellationToken) { - await Task.Run(() => BackupExistingFiles(cancellationToken)); + return Task.Run(() => + { + try + { + var applicationBaseDirectoryInfo = new DirectoryInfo(_updateRepository.UpdateData.ApplicationBaseDirectory); + var filesToBackup = GetFilesToBackup(applicationBaseDirectoryInfo, cancellationToken); + + foreach (var fileSystemInfo in filesToBackup) + { + if (cancellationToken.IsCancellationRequested) + { + _logger.LogWarning("UpdateExistingFilesBackuper.BackupExistingFiles: Cancellation requested"); + return; + } + + BackupFileSystemInfo(fileSystemInfo); + } + } + catch (OperationCanceledException) + { + _logger.LogWarning("UpdateExistingFilesBackuper.BackupExistingFiles: Operation was canceled"); + // Terminer proprement sans propager l'exception + } + catch (Exception ex) + { + _logger.LogError(ex, "UpdateExistingFilesBackuper.BackupExistingFiles: An error occurred"); + throw; + } + }, cancellationToken); } - private void BackupExistingFiles(CancellationToken cancellationToken) + private IEnumerable GetFilesToBackup(DirectoryInfo baseDirectory, CancellationToken cancellationToken) { - DirectoryInfo applicationBaseDirectoryInfo = new DirectoryInfo(_updateRepository.UpdateData.ApplicationBaseDirectory); + var result = new List(); - foreach (var fileSystemInfo in applicationBaseDirectoryInfo.GetFileSystemInfos()) + foreach (var fileSystemInfo in baseDirectory.GetFileSystemInfos()) { if (cancellationToken.IsCancellationRequested) - { - _logger.LogWarning("UpdateExistingFilesBackuper.BackupExistingFiles: Cancellation requested"); + break; - return; - } - if (fileSystemInfo is DirectoryInfo) { if (!fileSystemInfo.Name.Equals("Contents", StringComparison.InvariantCultureIgnoreCase) && !fileSystemInfo.Name.Equals("ByteSync.app", StringComparison.InvariantCultureIgnoreCase)) { - _logger.LogInformation("UpdateExistingFilesBackuper.BackupExistingFiles: ignored directory {directory}", fileSystemInfo.FullName); - + _logger.LogInformation("UpdateExistingFilesBackuper.GetFilesToBackup: ignored directory {directory}", fileSystemInfo.FullName); continue; } } if (fileSystemInfo is FileInfo fi) { - // Si l'une des conditions est réunies - // - Le Nom ne contient pas ByteSync - // - Son extension est dans .log, .dat, .xml, .json ou .zip - // - Il commence par unins et finit par .exe - // => On l'ignore + // Skip files if: + // - Name doesn't contain ByteSync + // - Extension is .log, .dat, .xml, .json or .zip + // - Starts with "unins" and ends with .exe if (!fileSystemInfo.Name.Contains("ByteSync", StringComparison.InvariantCultureIgnoreCase) || fi.Extension.ToLower().In(".log", ".dat", ".xml", ".json", ".zip") || (fi.Name.StartsWith("unins", StringComparison.InvariantCultureIgnoreCase) && fi.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase))) { - _logger.LogInformation("UpdateExistingFilesBackuper.BackupExistingFiles: ignored file {file}", fileSystemInfo.FullName); - + _logger.LogInformation("UpdateExistingFilesBackuper.GetFilesToBackup: ignored file {file}", fileSystemInfo.FullName); continue; } } - string previousFullName = fileSystemInfo.FullName; - - int cpt = 0; - var backupDestination = $"{fileSystemInfo.FullName}.{UpdateConstants.BAK_EXTENSION}{cpt}"; - - while (File.Exists(backupDestination) || Directory.Exists(backupDestination)) - { - cpt += 1; - backupDestination = $"{fileSystemInfo.FullName}.{UpdateConstants.BAK_EXTENSION}{cpt}"; - } - - _logger.LogInformation("UpdateExistingFilesBackuper: Renaming {Source} to {Destination}", previousFullName, backupDestination); - - if (fileSystemInfo is FileInfo fileInfo) - { - fileInfo.MoveTo(backupDestination); - } - else if (fileSystemInfo is DirectoryInfo directoryInfo) - { - directoryInfo.MoveTo(backupDestination); - } + result.Add(fileSystemInfo); + } + + return result; + } + + private void BackupFileSystemInfo(FileSystemInfo fileSystemInfo) + { + string previousFullName = fileSystemInfo.FullName; + + int cpt = 0; + var backupDestination = $"{fileSystemInfo.FullName}.{UpdateConstants.BAK_EXTENSION}{cpt}"; + + while (File.Exists(backupDestination) || Directory.Exists(backupDestination)) + { + cpt += 1; + backupDestination = $"{fileSystemInfo.FullName}.{UpdateConstants.BAK_EXTENSION}{cpt}"; + } + + _logger.LogInformation("UpdateExistingFilesBackuper: Renaming {Source} to {Destination}", previousFullName, backupDestination); - BackedUpFileSystemInfos.Add(new Tuple(previousFullName, backupDestination)); + if (fileSystemInfo is FileInfo fileInfo) + { + fileInfo.MoveTo(backupDestination); } + else if (fileSystemInfo is DirectoryInfo directoryInfo) + { + directoryInfo.MoveTo(backupDestination); + } + + BackedUpFileSystemInfos.Add(new Tuple(previousFullName, backupDestination)); } -} \ No newline at end of file +} diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs b/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs new file mode 100644 index 000000000..1fef5365b --- /dev/null +++ b/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs @@ -0,0 +1,190 @@ +using Autofac; +using ByteSync.Business.Updates; +using ByteSync.Interfaces.Repositories.Updates; +using ByteSync.Services.Updates; +using ByteSync.TestsCommon; +using FluentAssertions; +using Moq; + +namespace ByteSync.Client.IntegrationTests.Services.Updates; + +public class TestUpdateExistingFilesBackuper : IntegrationTest +{ + private UpdateExistingFilesBackuper _backuper; + + [SetUp] + public void SetUp() + { + RegisterType(); + BuildMoqContainer(); + + _testDirectoryService.CreateTestDirectory(); + + var mockUpdateRepository = Container.Resolve>(); + mockUpdateRepository.Setup(r => r.UpdateData).Returns(new UpdateData + { + ApplicationBaseDirectory = _testDirectoryService.TestDirectory.FullName + }); + + _backuper = Container.Resolve(); + } + + [Test] + public async Task BackupExistingFilesAsync_ShouldBackupFilesWithByteSyncInName() + { + // Arrange + _testDirectoryService.CreateSubTestFile("ByteSync.exe", "exeContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.dll", "dllContent"); + _testDirectoryService.CreateSubTestFile("regular.txt", "textContent"); + + // Act + await _backuper.BackupExistingFilesAsync(CancellationToken.None); + + // Assert + _backuper.BackedUpFileSystemInfos.Count.Should().Be(2); + + // Vérifier que les fichiers originaux n'existent plus + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.dll")).Should().BeFalse(); + + // Vérifier que les fichiers de sauvegarde existent + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.dll.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + + // Vérifier que le fichier régulier n'a pas été affecté + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "regular.txt")).Should().BeTrue(); + } + + [Test] + public async Task BackupExistingFilesAsync_ShouldIgnoreSpecificFileTypes() + { + // Arrange + _testDirectoryService.CreateSubTestFile("ByteSync.log", "logContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.dat", "datContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.xml", "xmlContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.json", "jsonContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.zip", "zipContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.exe", "exeContent"); + + // Act + await _backuper.BackupExistingFilesAsync(CancellationToken.None); + + // Assert + _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); + + // Vérifier que seul ByteSync.exe a été renommé + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + + // Vérifier que les autres fichiers sont intacts + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.log")).Should().BeTrue(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.dat")).Should().BeTrue(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.xml")).Should().BeTrue(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.json")).Should().BeTrue(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.zip")).Should().BeTrue(); + } + + [Test] + public async Task BackupExistingFilesAsync_ShouldIgnoreUninstallerFiles() + { + // Arrange + _testDirectoryService.CreateSubTestFile("unins000.exe", "uninstallerContent"); + _testDirectoryService.CreateSubTestFile("ByteSync.exe", "exeContent"); + + // Act + await _backuper.BackupExistingFilesAsync(CancellationToken.None); + + // Assert + _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); + + // Vérifier que seul ByteSync.exe a été renommé + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + + // Vérifier que le fichier unins000.exe est intact + File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "unins000.exe")).Should().BeTrue(); + } + + [Test] + public async Task BackupExistingFilesAsync_ShouldHandleDirectories() + { + // Arrange + var contentsDir = _testDirectoryService.TestDirectory.CreateSubdirectory("Contents"); + var bytesyncAppDir = _testDirectoryService.TestDirectory.CreateSubdirectory("ByteSync.app"); + var ignoredDir = _testDirectoryService.TestDirectory.CreateSubdirectory("IgnoredDirectory"); + + await File.WriteAllTextAsync(Path.Combine(contentsDir.FullName, "test.txt"), "contentsFile"); + await File.WriteAllTextAsync(Path.Combine(bytesyncAppDir.FullName, "app.txt"), "appFile"); + await File.WriteAllTextAsync(Path.Combine(ignoredDir.FullName, "ignored.txt"), "ignoredFile"); + + // Act + await _backuper.BackupExistingFilesAsync(CancellationToken.None); + + // Assert + _backuper.BackedUpFileSystemInfos.Count.Should().Be(2); // Contents et ByteSync.app + + // Vérifier que les répertoires Contents et ByteSync.app ont été renommés + Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "Contents")).Should().BeFalse(); + Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"Contents.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + + Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.app")).Should().BeFalse(); + Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.app.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + + // Vérifier que IgnoredDirectory est intact + Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "IgnoredDirectory")).Should().BeTrue(); + } + + // [Test] + // public async Task BackupExistingFilesAsync_ShouldIncrementBackupNumbers() + // { + // // Arrange + // _testDirectoryService.CreateSubTestFile("ByteSync.exe", "exeContent"); + // + // // Créer un fichier de sauvegarde existant + // _testDirectoryService.CreateSubTestFile($"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0", "oldBackup"); + // + // // Act + // await _backuper.BackupExistingFilesAsync(CancellationToken.None); + // + // // Assert + // _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); + // + // // Vérifier que le fichier original n'existe plus + // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); + // + // // Vérifier que la sauvegarde a été numérotée 1 (car 0 existe déjà) + // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}1")).Should().BeTrue(); + // + // // Vérifier que la première sauvegarde existe toujours + // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); + // } + + [Test] + public async Task BackupExistingFilesAsync_ShouldRespectCancellationToken() + { + // Arrange + for (int i = 0; i < 50; i++) + { + _testDirectoryService.CreateSubTestFile($"ByteSync.{i}.exe", $"content{i}"); + } + + // Act + var cancellationTokenSource = new CancellationTokenSource(); + var task = _backuper.BackupExistingFilesAsync(cancellationTokenSource.Token); + + // Attendre un court délai pour que la tâche démarre + await Task.Delay(50); + + // Annuler l'opération + cancellationTokenSource.Cancel(); + + // Attendre la fin de la tâche (ne devrait plus lever d'exception) + await task; + + // Assert + // On ne peut pas garantir exactement combien de fichiers ont été sauvegardés + // avant l'annulation, mais il ne devrait pas y en avoir 50 + _backuper.BackedUpFileSystemInfos.Count.Should().BeLessThan(50); + } +} + From b94e8533fb44faa94928cf75ce1dc3d2f67a067b Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 13 Apr 2025 22:38:01 +0200 Subject: [PATCH 4/6] feat: improve UpdateExistingFilesBackuper test: improve tests --- .../Updates/UpdateExistingFilesBackuper.cs | 42 ++++++----- .../TestUpdateExistingFilesBackuper.cs | 74 +++---------------- 2 files changed, 34 insertions(+), 82 deletions(-) diff --git a/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs b/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs index 00ab25ffc..46914d8ac 100644 --- a/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs +++ b/src/ByteSync.Client/Services/Updates/UpdateExistingFilesBackuper.cs @@ -44,7 +44,6 @@ public Task BackupExistingFilesAsync(CancellationToken cancellationToken) catch (OperationCanceledException) { _logger.LogWarning("UpdateExistingFilesBackuper.BackupExistingFiles: Operation was canceled"); - // Terminer proprement sans propager l'exception } catch (Exception ex) { @@ -63,33 +62,39 @@ private IEnumerable GetFilesToBackup(DirectoryInfo baseDirectory if (cancellationToken.IsCancellationRequested) break; - if (fileSystemInfo is DirectoryInfo) + if (fileSystemInfo is DirectoryInfo directoryInfo) { - if (!fileSystemInfo.Name.Equals("Contents", StringComparison.InvariantCultureIgnoreCase) && - !fileSystemInfo.Name.Equals("ByteSync.app", StringComparison.InvariantCultureIgnoreCase)) + // Only include files specifically named “Contents” or “ByteSync.app” + if (directoryInfo.Name.Equals("Contents", StringComparison.InvariantCultureIgnoreCase) || + directoryInfo.Name.Equals("ByteSync.app", StringComparison.InvariantCultureIgnoreCase)) + { + result.Add(fileSystemInfo); + } + else { _logger.LogInformation("UpdateExistingFilesBackuper.GetFilesToBackup: ignored directory {directory}", fileSystemInfo.FullName); - continue; } } - - if (fileSystemInfo is FileInfo fi) + else if (fileSystemInfo is FileInfo fileInfo) { - // Skip files if: - // - Name doesn't contain ByteSync - // - Extension is .log, .dat, .xml, .json or .zip - // - Starts with "unins" and ends with .exe - if (!fileSystemInfo.Name.Contains("ByteSync", StringComparison.InvariantCultureIgnoreCase) || - fi.Extension.ToLower().In(".log", ".dat", ".xml", ".json", ".zip") || - (fi.Name.StartsWith("unins", StringComparison.InvariantCultureIgnoreCase) - && fi.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase))) + // Only include files that: + // - Contain “ByteSync” in their name + // - Do not have a .log, .dat, .xml, .json or .zip extension + // - Do not start with “unins” if the extension is .exe + bool containsByteSyncName = fileInfo.Name.Contains("ByteSync", StringComparison.InvariantCultureIgnoreCase); + bool hasAllowedExtension = !fileInfo.Extension.ToLower().In(".log", ".dat", ".xml", ".json", ".zip"); + bool isUninstaller = fileInfo.Name.StartsWith("unins", StringComparison.InvariantCultureIgnoreCase) + && fileInfo.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase); + + if (containsByteSyncName && hasAllowedExtension && !isUninstaller) + { + result.Add(fileSystemInfo); + } + else { _logger.LogInformation("UpdateExistingFilesBackuper.GetFilesToBackup: ignored file {file}", fileSystemInfo.FullName); - continue; } } - - result.Add(fileSystemInfo); } return result; @@ -122,3 +127,4 @@ private void BackupFileSystemInfo(FileSystemInfo fileSystemInfo) BackedUpFileSystemInfos.Add(new Tuple(previousFullName, backupDestination)); } } + diff --git a/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs b/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs index 1fef5365b..2a9c51d08 100644 --- a/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs +++ b/tests/ByteSync.Client.IntegrationTests/Services/Updates/TestUpdateExistingFilesBackuper.cs @@ -43,15 +43,15 @@ public async Task BackupExistingFilesAsync_ShouldBackupFilesWithByteSyncInName() // Assert _backuper.BackedUpFileSystemInfos.Count.Should().Be(2); - // Vérifier que les fichiers originaux n'existent plus + // Verify that the original files no longer exist File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.dll")).Should().BeFalse(); - // Vérifier que les fichiers de sauvegarde existent + // Verify that the backup files exist File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.dll.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); - // Vérifier que le fichier régulier n'a pas été affecté + // Verify that the regular file was not affected File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "regular.txt")).Should().BeTrue(); } @@ -72,11 +72,11 @@ public async Task BackupExistingFilesAsync_ShouldIgnoreSpecificFileTypes() // Assert _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); - // Vérifier que seul ByteSync.exe a été renommé + // Verify that only ByteSync.exe was renamed File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); - // Vérifier que les autres fichiers sont intacts + // Verify that the other files remain intact File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.log")).Should().BeTrue(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.dat")).Should().BeTrue(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.xml")).Should().BeTrue(); @@ -97,11 +97,11 @@ public async Task BackupExistingFilesAsync_ShouldIgnoreUninstallerFiles() // Assert _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); - // Vérifier que seul ByteSync.exe a été renommé + // Verify that only ByteSync.exe was renamed File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); - // Vérifier que le fichier unins000.exe est intact + // Verify that the unins000.exe file remains intact File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "unins000.exe")).Should().BeTrue(); } @@ -121,70 +121,16 @@ public async Task BackupExistingFilesAsync_ShouldHandleDirectories() await _backuper.BackupExistingFilesAsync(CancellationToken.None); // Assert - _backuper.BackedUpFileSystemInfos.Count.Should().Be(2); // Contents et ByteSync.app + _backuper.BackedUpFileSystemInfos.Count.Should().Be(2); // Contents and ByteSync.app - // Vérifier que les répertoires Contents et ByteSync.app ont été renommés + // Verify that the Contents and ByteSync.app directories were renamed Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "Contents")).Should().BeFalse(); Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"Contents.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.app")).Should().BeFalse(); Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.app.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); - // Vérifier que IgnoredDirectory est intact + // Verify that IgnoredDirectory remains intact Directory.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "IgnoredDirectory")).Should().BeTrue(); } - - // [Test] - // public async Task BackupExistingFilesAsync_ShouldIncrementBackupNumbers() - // { - // // Arrange - // _testDirectoryService.CreateSubTestFile("ByteSync.exe", "exeContent"); - // - // // Créer un fichier de sauvegarde existant - // _testDirectoryService.CreateSubTestFile($"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0", "oldBackup"); - // - // // Act - // await _backuper.BackupExistingFilesAsync(CancellationToken.None); - // - // // Assert - // _backuper.BackedUpFileSystemInfos.Count.Should().Be(1); - // - // // Vérifier que le fichier original n'existe plus - // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, "ByteSync.exe")).Should().BeFalse(); - // - // // Vérifier que la sauvegarde a été numérotée 1 (car 0 existe déjà) - // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}1")).Should().BeTrue(); - // - // // Vérifier que la première sauvegarde existe toujours - // File.Exists(Path.Combine(_testDirectoryService.TestDirectory.FullName, $"ByteSync.exe.{UpdateConstants.BAK_EXTENSION}0")).Should().BeTrue(); - // } - - [Test] - public async Task BackupExistingFilesAsync_ShouldRespectCancellationToken() - { - // Arrange - for (int i = 0; i < 50; i++) - { - _testDirectoryService.CreateSubTestFile($"ByteSync.{i}.exe", $"content{i}"); - } - - // Act - var cancellationTokenSource = new CancellationTokenSource(); - var task = _backuper.BackupExistingFilesAsync(cancellationTokenSource.Token); - - // Attendre un court délai pour que la tâche démarre - await Task.Delay(50); - - // Annuler l'opération - cancellationTokenSource.Cancel(); - - // Attendre la fin de la tâche (ne devrait plus lever d'exception) - await task; - - // Assert - // On ne peut pas garantir exactement combien de fichiers ont été sauvegardés - // avant l'annulation, mais il ne devrait pas y en avoir 50 - _backuper.BackedUpFileSystemInfos.Count.Should().BeLessThan(50); - } } - From db4ecb3fb9ca4e268af92e54b967ab20eba6281b Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 14 Apr 2025 06:38:14 +0200 Subject: [PATCH 5/6] feat: improve token expired management --- .../Helpers/Middlewares/JwtMiddleware.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs index 11645b3ba..a024e4953 100644 --- a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs +++ b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs @@ -47,16 +47,21 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next if (token != null) { var tokenHandler = new JwtSecurityTokenHandler(); - + try { var claims = ValidateToken(tokenHandler, token); var client = await GetClient(claims); context.Items.Add(AuthConstants.FUNCTION_CONTEXT_CLIENT, client!); - + await BeginScopeAndGoNext(context, next, client); } + catch (SecurityTokenExpiredException ex) + { + _logger.LogWarning(ex, "Token expired"); + await HandleTokenError(context, "Invalid token"); + } catch (Exception ex) { _logger.LogError(ex, "Error validating token"); From 9fba09faef7cc3624b1a3cbe578bb00b877828f0 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Mon, 14 Apr 2025 08:06:48 +0200 Subject: [PATCH 6/6] feat: improve jwt token extraction --- .../Helpers/Middlewares/JwtMiddleware.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs index a024e4953..b66d62eea 100644 --- a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs +++ b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs @@ -102,13 +102,33 @@ private async Task BeginScopeAndGoNext(FunctionContext context, FunctionExecutio } } - private static async Task ExtractToken(FunctionContext context) + private async Task ExtractToken(FunctionContext context) { var requestData = await context.GetHttpRequestDataAsync(); - var authorizationHeader = requestData?.Headers.FirstOrDefault(p => p.Key.Equals("Authorization")); - var token = authorizationHeader?.Value.LastOrDefault(); + if (requestData == null) + { + return null; + } + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (!requestData.Headers.TryGetValues("Authorization", out var authorizationValues) || authorizationValues == null) + { + return null; + } + + var token = authorizationValues.LastOrDefault(); + if (string.IsNullOrWhiteSpace(token)) + { + return null; + } - return token; + const string bearerPrefix = "Bearer "; + if (token.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase)) + { + token = token.Substring(bearerPrefix.Length); + } + + return string.IsNullOrWhiteSpace(token) ? null : token; } private TokenValidationParameters BuildTokenValidationParameters()