Skip to content

Commit 73fbfc8

Browse files
committed
vfs
1 parent 0647163 commit 73fbfc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1074
-598
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Run `dotnet restore ManagedCode.Storage.slnx` before compiling. Use `dotnet buil
2323
Follow standard C# conventions: 4-space indentation, PascalCase types, camelCase locals, and suffix async APIs with `Async`. Nullability is enabled repository-wide, so annotate optional members and avoid the suppression operator unless justified. Match method names to existing patterns such as `DownloadFile_WhenFileExists_ReturnsSuccess`. Remove unused usings and let analyzers guide layout.
2424

2525
## Testing Guidelines
26-
Tests use xUnit and FluentAssertions; choose `[Fact]` for atomic cases and `[Theory]` for data-driven permutations. Place provider suites under `Tests/ManagedCode.Storage.Tests/Storages/` and reuse `.../Common/` helpers to spin up Testcontainers (Azurite, LocalStack, FakeGcsServer). Add fakes or harnesses mirroring `ManagedCode.Storage.TestFakes/` when introducing new providers. Always run `dotnet test` locally and exercise critical upload/download paths.
26+
Tests use xUnit and Shouldly; choose `[Fact]` for atomic cases and `[Theory]` for data-driven permutations. Place provider suites under `Tests/ManagedCode.Storage.Tests/Storages/` and reuse `.../Common/` helpers to spin up Testcontainers (Azurite, LocalStack, FakeGcsServer). Add fakes or harnesses mirroring `ManagedCode.Storage.TestFakes/` when introducing new providers. Always run `dotnet test` locally and exercise critical upload/download paths.
2727

2828
## Commit & Pull Request Guidelines
2929
Write commit subjects in the imperative mood (`add ftp retry policy`) and keep them provider-scoped. Group related edits in one commit and avoid WIP spam. Pull requests should summarize impact, list touched projects, reference issues, and note new configuration or secrets. Include the `dotnet` commands you ran and add logs when CI needs context.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The library supports two connection modes:
5757
- Extension methods for DI registration (e.g., `AddAzureStorage`, `AddAWSStorageAsDefault`)
5858

5959
## Testing
60-
- Uses xUnit with FluentAssertions
60+
- Uses xUnit with Shouldly
6161
- Testcontainers for integration testing (Azurite, LocalStack, FakeGcsServer)
6262
- Test projects follow pattern: `Tests/ManagedCode.Storage.Tests/`
6363
- Includes test fakes in `ManagedCode.Storage.TestFakes`

ManagedCode.Storage.VirtualFileSystem/Core/IVfsEntry.cs renamed to ManagedCode.Storage.VirtualFileSystem/Core/IVfsNode.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,51 @@
77
namespace ManagedCode.Storage.VirtualFileSystem.Core;
88

99
/// <summary>
10-
/// Base interface for virtual file system entries
10+
/// Base interface for virtual file system nodes
1111
/// </summary>
12-
public interface IVfsEntry
12+
public interface IVfsNode
1313
{
1414
/// <summary>
15-
/// Gets the path of this entry
15+
/// Gets the path of this node
1616
/// </summary>
1717
VfsPath Path { get; }
1818

1919
/// <summary>
20-
/// Gets the name of this entry
20+
/// Gets the name of this node
2121
/// </summary>
2222
string Name { get; }
2323

2424
/// <summary>
25-
/// Gets the type of this entry
25+
/// Gets the type of this node
2626
/// </summary>
2727
VfsEntryType Type { get; }
2828

2929
/// <summary>
30-
/// Gets when this entry was created
30+
/// Gets when this node was created
3131
/// </summary>
3232
DateTimeOffset CreatedOn { get; }
3333

3434
/// <summary>
35-
/// Gets when this entry was last modified
35+
/// Gets when this node was last modified
3636
/// </summary>
3737
DateTimeOffset LastModified { get; }
3838

3939
/// <summary>
40-
/// Checks if this entry exists
40+
/// Checks if this node exists
4141
/// </summary>
4242
/// <param name="cancellationToken">Cancellation token</param>
4343
/// <returns>True if the entry exists</returns>
4444
ValueTask<bool> ExistsAsync(CancellationToken cancellationToken = default);
4545

4646
/// <summary>
47-
/// Refreshes the entry information from storage
47+
/// Refreshes the node information from storage
4848
/// </summary>
4949
/// <param name="cancellationToken">Cancellation token</param>
5050
/// <returns>Task representing the async operation</returns>
5151
Task RefreshAsync(CancellationToken cancellationToken = default);
5252

5353
/// <summary>
54-
/// Gets the parent directory of this entry
54+
/// Gets the parent directory of this node
5555
/// </summary>
5656
/// <param name="cancellationToken">Cancellation token</param>
5757
/// <returns>The parent directory</returns>
@@ -134,4 +134,4 @@ public class DeleteDirectoryResult
134134
/// List of errors encountered during deletion
135135
/// </summary>
136136
public List<string> Errors { get; set; } = new();
137-
}
137+
}

ManagedCode.Storage.VirtualFileSystem/Core/IVirtualDirectory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace ManagedCode.Storage.VirtualFileSystem.Core;
99
/// <summary>
1010
/// Represents a directory in the virtual filesystem
1111
/// </summary>
12-
public interface IVirtualDirectory : IVfsEntry
12+
public interface IVirtualDirectory : IVfsNode
1313
{
1414
/// <summary>
1515
/// Lists files in this directory with pagination and pattern matching
@@ -47,7 +47,7 @@ IAsyncEnumerable<IVirtualDirectory> GetDirectoriesAsync(
4747
/// <param name="pageSize">Page size for pagination</param>
4848
/// <param name="cancellationToken">Cancellation token</param>
4949
/// <returns>Async enumerable of entries</returns>
50-
IAsyncEnumerable<IVfsEntry> GetEntriesAsync(
50+
IAsyncEnumerable<IVfsNode> GetEntriesAsync(
5151
SearchPattern? pattern = null,
5252
bool recursive = false,
5353
int pageSize = 100,

ManagedCode.Storage.VirtualFileSystem/Core/IVirtualFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace ManagedCode.Storage.VirtualFileSystem.Core;
1111
/// <summary>
1212
/// Represents a file in the virtual filesystem
1313
/// </summary>
14-
public interface IVirtualFile : IVfsEntry
14+
public interface IVirtualFile : IVfsNode
1515
{
1616
/// <summary>
1717
/// Gets the file size in bytes

ManagedCode.Storage.VirtualFileSystem/Core/IVirtualFileSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ Task CopyAsync(
121121
/// <param name="path">Entry path</param>
122122
/// <param name="cancellationToken">Cancellation token</param>
123123
/// <returns>Entry information or null if not found</returns>
124-
ValueTask<IVfsEntry?> GetEntryAsync(VfsPath path, CancellationToken cancellationToken = default);
124+
ValueTask<IVfsNode?> GetEntryAsync(VfsPath path, CancellationToken cancellationToken = default);
125125

126126
/// <summary>
127127
/// Lists directory contents with pagination
@@ -130,7 +130,7 @@ Task CopyAsync(
130130
/// <param name="options">Listing options</param>
131131
/// <param name="cancellationToken">Cancellation token</param>
132132
/// <returns>Async enumerable of entries</returns>
133-
IAsyncEnumerable<IVfsEntry> ListAsync(
133+
IAsyncEnumerable<IVfsNode> ListAsync(
134134
VfsPath path,
135135
ListOptions? options = null,
136136
CancellationToken cancellationToken = default);

ManagedCode.Storage.VirtualFileSystem/Implementations/VirtualDirectory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public async IAsyncEnumerable<IVirtualDirectory> GetDirectoriesAsync(
126126
}
127127

128128
/// <inheritdoc />
129-
public async IAsyncEnumerable<IVfsEntry> GetEntriesAsync(
129+
public async IAsyncEnumerable<IVfsNode> GetEntriesAsync(
130130
SearchPattern? pattern = null,
131131
bool recursive = false,
132132
int pageSize = 100,
@@ -140,7 +140,7 @@ public async IAsyncEnumerable<IVfsEntry> GetEntriesAsync(
140140
}
141141
}
142142

143-
private async IAsyncEnumerable<IVfsEntry> GetEntriesInternalAsync(
143+
private async IAsyncEnumerable<IVfsNode> GetEntriesInternalAsync(
144144
SearchPattern? pattern,
145145
bool recursive,
146146
int pageSize,

ManagedCode.Storage.VirtualFileSystem/Implementations/VirtualFileSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ private async Task CopyDirectoryAsync(
469469
}
470470

471471
/// <inheritdoc />
472-
public async ValueTask<IVfsEntry?> GetEntryAsync(VfsPath path, CancellationToken cancellationToken = default)
472+
public async ValueTask<IVfsNode?> GetEntryAsync(VfsPath path, CancellationToken cancellationToken = default)
473473
{
474474
ThrowIfDisposed();
475475

@@ -487,7 +487,7 @@ private async Task CopyDirectoryAsync(
487487
}
488488

489489
/// <inheritdoc />
490-
public async IAsyncEnumerable<IVfsEntry> ListAsync(
490+
public async IAsyncEnumerable<IVfsNode> ListAsync(
491491
VfsPath path,
492492
ListOptions? options = null,
493493
[EnumeratorCancellation] CancellationToken cancellationToken = default)

Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseDownloadControllerTests.cs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Net;
33
using System.Threading.Tasks;
4-
using FluentAssertions;
4+
using Shouldly;
55
using ManagedCode.Storage.Core.Helpers;
66
using ManagedCode.Storage.Core.Models;
77
using ManagedCode.Storage.Tests.Common;
@@ -35,20 +35,18 @@ public async Task DownloadFile_WhenFileExists_SaveToTempStorage_ReturnSuccess()
3535
var fileCRC = Crc32Helper.CalculateFileCrc(localFile.FilePath); // Calculate CRC from file path
3636
await using var uploadStream = localFile.FileStream; // Get stream once
3737
var uploadFileBlob = await storageClient.UploadFile(uploadStream, _uploadEndpoint, contentName);
38-
uploadFileBlob.IsSuccess.Should().BeTrue();
38+
uploadFileBlob.IsSuccess.ShouldBeTrue();
3939
var uploadedMetadata = uploadFileBlob.Value ?? throw new InvalidOperationException("Upload did not return metadata");
4040

4141
// Act
4242
var downloadedFileResult = await storageClient.DownloadFile(uploadedMetadata.FullName, _downloadEndpoint);
4343

4444
// Assert
4545
downloadedFileResult.IsSuccess
46-
.Should()
47-
.BeTrue();
46+
.ShouldBeTrue();
4847
var downloadedLocal = downloadedFileResult.Value ?? throw new InvalidOperationException("Download result does not contain a file");
4948
var downloadedFileCRC = Crc32Helper.CalculateFileCrc(downloadedLocal.FilePath);
50-
downloadedFileCRC.Should()
51-
.Be(fileCRC);
49+
downloadedFileCRC.ShouldBe(fileCRC);
5250
}
5351

5452
[Fact]
@@ -63,17 +61,17 @@ public async Task DownloadFileAsBytes_WhenFileExists_ReturnSuccess()
6361
var fileCRC = Crc32Helper.CalculateFileCrc(localFile.FilePath); // Calculate CRC from file path
6462
await using var uploadStream = localFile.FileStream; // Get stream once
6563
var uploadFileBlob = await storageClient.UploadFile(uploadStream, _uploadEndpoint, contentName);
66-
uploadFileBlob.IsSuccess.Should().BeTrue();
64+
uploadFileBlob.IsSuccess.ShouldBeTrue();
6765
var uploadedMetadata = uploadFileBlob.Value ?? throw new InvalidOperationException("Upload did not return metadata");
6866

6967
// Act
7068
var downloadedFileResult = await storageClient.DownloadFile(uploadedMetadata.FullName, _downloadBytesEndpoint);
7169

7270
// Assert
73-
downloadedFileResult.IsSuccess.Should().BeTrue();
71+
downloadedFileResult.IsSuccess.ShouldBeTrue();
7472
var downloadedLocal = downloadedFileResult.Value ?? throw new InvalidOperationException("Download result does not contain a file");
7573
var downloadedFileCRC = Crc32Helper.CalculateFileCrc(downloadedLocal.FilePath);
76-
downloadedFileCRC.Should().Be(fileCRC);
74+
downloadedFileCRC.ShouldBe(fileCRC);
7775
}
7876

7977
[Fact]
@@ -88,11 +86,9 @@ public async Task DownloadFile_WhenFileDoNotExist_ReturnFail()
8886

8987
// Assert
9088
downloadedFileResult.IsFailed
91-
.Should()
92-
.BeTrue();
89+
.ShouldBeTrue();
9390
downloadedFileResult.Problem
9491
?.StatusCode
95-
.Should()
96-
.Be((int)HttpStatusCode.InternalServerError);
92+
.ShouldBe((int)HttpStatusCode.InternalServerError);
9793
}
9894
}

Tests/ManagedCode.Storage.Tests/AspNetTests/Abstracts/BaseStreamControllerTests.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.IO;
33
using System.Net;
44
using System.Threading.Tasks;
5-
using FluentAssertions;
5+
using Shouldly;
66
using ManagedCode.Storage.Core.Helpers;
77
using ManagedCode.Storage.Core.Models;
88
using ManagedCode.Storage.Tests.Common;
@@ -34,25 +34,23 @@ public async Task StreamFile_WhenFileExists_SaveToTempStorage_ReturnSuccess()
3434
var fileCRC = Crc32Helper.CalculateFileCrc(localFile.FilePath); // Calculate CRC from file path
3535
await using var uploadStream = localFile.FileStream; // Get stream once
3636
var uploadFileBlob = await storageClient.UploadFile(uploadStream, _uploadEndpoint, contentName);
37-
uploadFileBlob.IsSuccess.Should().BeTrue();
37+
uploadFileBlob.IsSuccess.ShouldBeTrue();
3838
var uploadedMetadata = uploadFileBlob.Value ?? throw new InvalidOperationException("Upload did not return metadata");
3939

4040
// Act
4141
var streamFileResult = await storageClient.GetFileStream(uploadedMetadata.FullName, _streamEndpoint);
4242

4343
// Assert
4444
streamFileResult.IsSuccess
45-
.Should()
46-
.BeTrue();
45+
.ShouldBeTrue();
4746
var streamedValue = streamFileResult.Value ?? throw new InvalidOperationException("Stream result does not contain a stream");
4847

4948
await using var stream = streamedValue;
5049
await using var newLocalFile = await LocalFile.FromStreamAsync(stream, Path.GetTempPath(), Guid.NewGuid()
5150
.ToString("N") + extension);
5251

5352
var streamedFileCRC = Crc32Helper.CalculateFileCrc(newLocalFile.FilePath);
54-
streamedFileCRC.Should()
55-
.Be(fileCRC);
53+
streamedFileCRC.ShouldBe(fileCRC);
5654
}
5755

5856
[Fact]
@@ -67,11 +65,9 @@ public async Task StreamFile_WhenFileDoNotExist_ReturnFail()
6765

6866
// Assert
6967
streamFileResult.IsFailed
70-
.Should()
71-
.BeTrue();
68+
.ShouldBeTrue();
7269
streamFileResult.Problem
7370
?.StatusCode
74-
.Should()
75-
.Be((int)HttpStatusCode.InternalServerError);
71+
.ShouldBe((int)HttpStatusCode.InternalServerError);
7672
}
7773
}

0 commit comments

Comments
 (0)