From 980d08d3bc588eefcf52a7bd7cffa3f236f8f48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Fri, 12 Sep 2025 08:24:08 +0200 Subject: [PATCH] fix: update the location of moved or replaced containers --- .../Storage/IStorageContainer.cs | 5 +++++ .../Storage/InMemoryContainer.cs | 9 +++++++- .../Storage/InMemoryStorage.cs | 6 +++--- .../Storage/NullContainer.cs | 7 +++++++ .../TestHelpers/LockableContainer.cs | 4 ++++ .../FileSystem/File/ReplaceTests.cs | 21 +++++++++++++++++++ .../FileSystem/FileInfo/ReplaceTests.cs | 21 +++++++++++++++++++ 7 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Storage/IStorageContainer.cs b/Source/Testably.Abstractions.Testing/Storage/IStorageContainer.cs index 4c32146a6..ccab079e2 100644 --- a/Source/Testably.Abstractions.Testing/Storage/IStorageContainer.cs +++ b/Source/Testably.Abstractions.Testing/Storage/IStorageContainer.cs @@ -93,6 +93,11 @@ IStorageAccessHandle RequestAccess(FileAccess access, FileShare share, int? hResult = null, IStorageLocation? onBehalfOfLocation = null); + /// + /// Updates the location of the container. + /// + IStorageContainer UpdateLocation(IStorageLocation newLocation); + /// /// Writes the to the . /// diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs index 734efc668..248b5c65d 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs @@ -17,7 +17,7 @@ internal sealed class InMemoryContainer : IStorageContainer private readonly FileSystemExtensibility _extensibility = new(); private readonly MockFileSystem _fileSystem; private bool _isEncrypted; - private readonly IStorageLocation _location; + private IStorageLocation _location; #if FEATURE_FILESYSTEM_UNIXFILEMODE private UnixFileMode _unixFileMode = UnixFileMode.OtherRead | @@ -223,6 +223,13 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share, hResult ?? -2147024864); } + /// + public IStorageContainer UpdateLocation(IStorageLocation newLocation) + { + _location = newLocation; + return this; + } + /// public void WriteBytes(byte[] bytes) { diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index d2e4aa601..e89a421a8 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -550,7 +550,7 @@ public IStorageContainer GetOrCreateContainer( existingDestinationContainer.GetBytes().Length; destination.Drive?.ChangeUsedBytes(-1 * destinationBytesLength); if (backup != null && - _containers.TryAdd(backup, existingDestinationContainer)) + _containers.TryAdd(backup, existingDestinationContainer.UpdateLocation(backup))) { if (_fileSystem.Execute.IsWindows && sourceContainer.Type == FileSystemTypes.File) @@ -587,7 +587,7 @@ public IStorageContainer GetOrCreateContainer( DateTimeKind.Utc); } - _containers.TryAdd(destination, existingSourceContainer); + _containers.TryAdd(destination, existingSourceContainer.UpdateLocation(destination)); return destination; } } @@ -1041,7 +1041,7 @@ private bool IncludeItemInEnumeration( existingContainer.ClearBytes(); } - if (_containers.TryAdd(destination, sourceContainer)) + if (_containers.TryAdd(destination, sourceContainer.UpdateLocation(destination))) { int bytesLength = sourceContainer.GetBytes().Length; source.Drive?.ChangeUsedBytes(-1 * bytesLength); diff --git a/Source/Testably.Abstractions.Testing/Storage/NullContainer.cs b/Source/Testably.Abstractions.Testing/Storage/NullContainer.cs index 38b0a76e9..bd126dbe7 100644 --- a/Source/Testably.Abstractions.Testing/Storage/NullContainer.cs +++ b/Source/Testably.Abstractions.Testing/Storage/NullContainer.cs @@ -105,6 +105,13 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share, IStorageLocation? onBehalfOfLocation = null) => new NullStorageAccessHandle(access, share, deleteAccess); + /// + public IStorageContainer UpdateLocation(IStorageLocation newLocation) + { + // Do nothing in NullContainer + return this; + } + /// public void WriteBytes(byte[] bytes) { diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/LockableContainer.cs b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/LockableContainer.cs index d4cd61892..9ed53f5ac 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/LockableContainer.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/LockableContainer.cs @@ -111,6 +111,10 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share, return new AccessHandle(access, share, deleteAccess); } + /// + public IStorageContainer UpdateLocation(IStorageLocation newLocation) + => this; + /// public void WriteBytes(byte[] bytes) { diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/File/ReplaceTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/File/ReplaceTests.cs index 317128407..771232f34 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/File/ReplaceTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/File/ReplaceTests.cs @@ -1,3 +1,4 @@ +using aweXpect.Testably; using System.IO; namespace Testably.Abstractions.Tests.FileSystem.File; @@ -188,6 +189,26 @@ public async Task Replace_ShouldReplaceFile( await That(FileSystem.File.ReadAllText(backupName)).IsEquivalentTo(destinationContents); } + [Theory] + [AutoData] + public async Task Replace_Twice_ShouldReplaceFile( + string sourceName, + string destinationName) + { + FileSystem.Initialize() + .WithFile(sourceName).Which(f => f.HasStringContent("abc")) + .WithFile(destinationName).Which(f => f.HasStringContent("xyz")); + var file1 = FileSystem.FileInfo.New(sourceName); + var file2 = FileSystem.FileInfo.New(destinationName); + FileSystem.File.Replace(file2.FullName, file1.FullName, null); + + var file3 = FileSystem.FileInfo.New(destinationName); + FileSystem.File.WriteAllText(destinationName, "def"); + FileSystem.File.Replace(file3.FullName, file1.FullName, null); + + await That(file1).HasContent("def"); + } + [Theory] [AutoData] public async Task Replace_SourceIsDirectory_ShouldThrowUnauthorizedAccessException( diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/FileInfo/ReplaceTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/FileInfo/ReplaceTests.cs index d91b770e9..cccc2fc5b 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/FileInfo/ReplaceTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/FileInfo/ReplaceTests.cs @@ -1,3 +1,4 @@ +using aweXpect.Testably; using System.IO; namespace Testably.Abstractions.Tests.FileSystem.FileInfo; @@ -398,6 +399,26 @@ void Act() } } + [Theory] + [AutoData] + public async Task Replace_Twice_ShouldReplaceFile( + string sourceName, + string destinationName) + { + FileSystem.Initialize() + .WithFile(sourceName).Which(f => f.HasStringContent("abc")) + .WithFile(destinationName).Which(f => f.HasStringContent("xyz")); + IFileInfo file1 = FileSystem.FileInfo.New(sourceName); + IFileInfo file2 = FileSystem.FileInfo.New(destinationName); + file2.Replace(file1.FullName, null); + + IFileInfo file3 = FileSystem.FileInfo.New(destinationName); + FileSystem.File.WriteAllText(destinationName, "def"); + file3.Replace(file1.FullName, null); + + await That(file1).HasContent("def"); + } + [Theory] [AutoData] public async Task Replace_WhenFileIsReadOnly_ShouldThrowUnauthorizedAccessException_OnWindows(