Skip to content

Commit 36a9473

Browse files
authored
fix: update the location of moved or replaced containers (#861)
This PR implements the `UpdateLocation` method for storage containers to properly update their location references when files are moved or replaced. This fixes an issue where containers retained stale location information after file system operations. ### Key changes: - Adds `UpdateLocation` method to the `IStorageContainer` interface and all implementing classes - Updates file move and replace operations to call `UpdateLocation` when containers change location - Adds test coverage for multiple replace operations to verify the fix works correctly
1 parent d055a3a commit 36a9473

File tree

7 files changed

+69
-4
lines changed

7 files changed

+69
-4
lines changed

Source/Testably.Abstractions.Testing/Storage/IStorageContainer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
9393
int? hResult = null,
9494
IStorageLocation? onBehalfOfLocation = null);
9595

96+
/// <summary>
97+
/// Updates the location of the container.
98+
/// </summary>
99+
IStorageContainer UpdateLocation(IStorageLocation newLocation);
100+
96101
/// <summary>
97102
/// Writes the <paramref name="bytes" /> to the <see cref="IFileInfo" />.
98103
/// </summary>

Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal sealed class InMemoryContainer : IStorageContainer
1717
private readonly FileSystemExtensibility _extensibility = new();
1818
private readonly MockFileSystem _fileSystem;
1919
private bool _isEncrypted;
20-
private readonly IStorageLocation _location;
20+
private IStorageLocation _location;
2121

2222
#if FEATURE_FILESYSTEM_UNIXFILEMODE
2323
private UnixFileMode _unixFileMode = UnixFileMode.OtherRead |
@@ -223,6 +223,13 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
223223
hResult ?? -2147024864);
224224
}
225225

226+
/// <inheritdoc cref="IStorageContainer.UpdateLocation(IStorageLocation)" />
227+
public IStorageContainer UpdateLocation(IStorageLocation newLocation)
228+
{
229+
_location = newLocation;
230+
return this;
231+
}
232+
226233
/// <inheritdoc cref="IStorageContainer.WriteBytes(byte[])" />
227234
public void WriteBytes(byte[] bytes)
228235
{

Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ public IStorageContainer GetOrCreateContainer(
549549
existingDestinationContainer.GetBytes().Length;
550550
destination.Drive?.ChangeUsedBytes(-1 * destinationBytesLength);
551551
if (backup != null &&
552-
_containers.TryAdd(backup, existingDestinationContainer))
552+
_containers.TryAdd(backup, existingDestinationContainer.UpdateLocation(backup)))
553553
{
554554
if (_fileSystem.Execute.IsWindows &&
555555
sourceContainer.Type == FileSystemTypes.File)
@@ -586,7 +586,7 @@ public IStorageContainer GetOrCreateContainer(
586586
DateTimeKind.Utc);
587587
}
588588

589-
_containers.TryAdd(destination, existingSourceContainer);
589+
_containers.TryAdd(destination, existingSourceContainer.UpdateLocation(destination));
590590
return destination;
591591
}
592592
}
@@ -1040,7 +1040,7 @@ private bool IncludeItemInEnumeration(
10401040
existingContainer.ClearBytes();
10411041
}
10421042

1043-
if (_containers.TryAdd(destination, sourceContainer))
1043+
if (_containers.TryAdd(destination, sourceContainer.UpdateLocation(destination)))
10441044
{
10451045
int bytesLength = sourceContainer.GetBytes().Length;
10461046
source.Drive?.ChangeUsedBytes(-1 * bytesLength);

Source/Testably.Abstractions.Testing/Storage/NullContainer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
105105
IStorageLocation? onBehalfOfLocation = null)
106106
=> new NullStorageAccessHandle(access, share, deleteAccess);
107107

108+
/// <inheritdoc cref="IStorageContainer.UpdateLocation(IStorageLocation)" />
109+
public IStorageContainer UpdateLocation(IStorageLocation newLocation)
110+
{
111+
// Do nothing in NullContainer
112+
return this;
113+
}
114+
108115
/// <inheritdoc cref="IStorageContainer.WriteBytes(byte[])" />
109116
public void WriteBytes(byte[] bytes)
110117
{

Tests/Testably.Abstractions.Testing.Tests/TestHelpers/LockableContainer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
111111
return new AccessHandle(access, share, deleteAccess);
112112
}
113113

114+
/// <inheritdoc cref="IStorageContainer.UpdateLocation(IStorageLocation)" />
115+
public IStorageContainer UpdateLocation(IStorageLocation newLocation)
116+
=> this;
117+
114118
/// <inheritdoc cref="IStorageContainer.WriteBytes(byte[])" />
115119
public void WriteBytes(byte[] bytes)
116120
{

Tests/Testably.Abstractions.Tests/FileSystem/File/ReplaceTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using aweXpect.Testably;
12
using System.IO;
23

34
namespace Testably.Abstractions.Tests.FileSystem.File;
@@ -188,6 +189,26 @@ public async Task Replace_ShouldReplaceFile(
188189
await That(FileSystem.File.ReadAllText(backupName)).IsEquivalentTo(destinationContents);
189190
}
190191

192+
[Theory]
193+
[AutoData]
194+
public async Task Replace_Twice_ShouldReplaceFile(
195+
string sourceName,
196+
string destinationName)
197+
{
198+
FileSystem.Initialize()
199+
.WithFile(sourceName).Which(f => f.HasStringContent("abc"))
200+
.WithFile(destinationName).Which(f => f.HasStringContent("xyz"));
201+
var file1 = FileSystem.FileInfo.New(sourceName);
202+
var file2 = FileSystem.FileInfo.New(destinationName);
203+
FileSystem.File.Replace(file2.FullName, file1.FullName, null);
204+
205+
var file3 = FileSystem.FileInfo.New(destinationName);
206+
FileSystem.File.WriteAllText(destinationName, "def");
207+
FileSystem.File.Replace(file3.FullName, file1.FullName, null);
208+
209+
await That(file1).HasContent("def");
210+
}
211+
191212
[Theory]
192213
[AutoData]
193214
public async Task Replace_SourceIsDirectory_ShouldThrowUnauthorizedAccessException(

Tests/Testably.Abstractions.Tests/FileSystem/FileInfo/ReplaceTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using aweXpect.Testably;
12
using System.IO;
23

34
namespace Testably.Abstractions.Tests.FileSystem.FileInfo;
@@ -398,6 +399,26 @@ void Act()
398399
}
399400
}
400401

402+
[Theory]
403+
[AutoData]
404+
public async Task Replace_Twice_ShouldReplaceFile(
405+
string sourceName,
406+
string destinationName)
407+
{
408+
FileSystem.Initialize()
409+
.WithFile(sourceName).Which(f => f.HasStringContent("abc"))
410+
.WithFile(destinationName).Which(f => f.HasStringContent("xyz"));
411+
IFileInfo file1 = FileSystem.FileInfo.New(sourceName);
412+
IFileInfo file2 = FileSystem.FileInfo.New(destinationName);
413+
file2.Replace(file1.FullName, null);
414+
415+
IFileInfo file3 = FileSystem.FileInfo.New(destinationName);
416+
FileSystem.File.WriteAllText(destinationName, "def");
417+
file3.Replace(file1.FullName, null);
418+
419+
await That(file1).HasContent("def");
420+
}
421+
401422
[Theory]
402423
[AutoData]
403424
public async Task Replace_WhenFileIsReadOnly_ShouldThrowUnauthorizedAccessException_OnWindows(

0 commit comments

Comments
 (0)