refactor: replace repo-relative string helpers with RelativePath#91
refactor: replace repo-relative string helpers with RelativePath#91woutervanranst wants to merge 47 commits into
Conversation
📝 WalkthroughWalkthroughThis PR migrates tests, fixtures, helpers and fakes from string paths to typed filesystem abstractions (RelativePath, LocalDirectory, RelativeFileSystem), rekeys fake blob stores to RelativePath, removes RepositoryPathStrings, and expands internals visibility for test assemblies. ChangesTyped filesystem migration (tests, fixtures, fakes, helpers)
Sequence Diagram(s)(no diagrams generated) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs (1)
108-109: ⚡ Quick winUse
RelativePath.Root / prefixinstead ofRelativePath.Parse(prefix.ToString()).
prefixis already a typedPathSegmentfromShard.PrefixOf(). Composing it directly via the/operator avoids the stringify/parse round-trip:Suggested fix
-var l2Path = RepositoryPaths.GetChunkIndexCacheRoot(Account, containerName).Resolve(RelativePath.Parse(prefix.ToString())); +var l2Path = RepositoryPaths.GetChunkIndexCacheRoot(Account, containerName).Resolve(RelativePath.Root / prefix);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs` around lines 108 - 109, The code builds l2Path by composing RepositoryPaths.GetChunkIndexCacheRoot(Account, containerName) with RelativePath.Parse(prefix.ToString()), but prefix is already a PathSegment from Shard.PrefixOf(contentHash); replace the parse round-trip with direct composition using RelativePath.Root / prefix so that l2Path is created by resolving RelativePath.Root / prefix instead of parsing prefix.ToString().src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs (1)
49-49: ⚡ Quick winExpose
FileTreeCacheDirectoryasLocalDirectoryinstead ofstring.This currently forces a
LocalDirectory -> string -> LocalDirectoryround-trip (see downstreamLocalDirectory.Parse(env.FileTreeCacheDirectory)), which weakens the type-safety this PR is introducing.Suggested diff
diff --git a/src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs b/src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs @@ - public string FileTreeCacheDirectory => _fileTreeCacheDirectory.ToString(); + public LocalDirectory FileTreeCacheDirectory => _fileTreeCacheDirectory;diff --git a/src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs b/src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs @@ - await using var stagingSession = await FileTreeStagingSession.OpenAsync(LocalDirectory.Parse(env.FileTreeCacheDirectory)); + await using var stagingSession = await FileTreeStagingSession.OpenAsync(env.FileTreeCacheDirectory);As per coding guidelines, "Avoid stringify/parse round-trips. Preserve strong types until a real foreign boundary such as console output, configuration, serialization, or external SDK calls."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs` at line 49, Change the FileTreeCacheDirectory property to return a LocalDirectory instead of string to avoid stringify/parse round-trips: replace the getter signature for FileTreeCacheDirectory to return the existing _fileTreeCacheDirectory (as a LocalDirectory) and update callers that currently do LocalDirectory.Parse(env.FileTreeCacheDirectory) to use the property directly; ensure any tests or consumers expecting a string are adjusted to call .ToString() only when crossing a foreign boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs`:
- Line 350: The test currently calls Directory.Delete(cacheDir.ToString(),
recursive: true) which can throw if the directory is already removed; replace
that raw delete with the existing helper DeleteDirectoryIfExists(cacheDir) to
safely guard against missing directories and match the file's cleanup
pattern—locate the call in FileTreeBuilderTests (the line invoking
Directory.Delete) and swap it for the DeleteDirectoryIfExists(cacheDir) helper
method.
---
Nitpick comments:
In `@src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs`:
- Line 49: Change the FileTreeCacheDirectory property to return a LocalDirectory
instead of string to avoid stringify/parse round-trips: replace the getter
signature for FileTreeCacheDirectory to return the existing
_fileTreeCacheDirectory (as a LocalDirectory) and update callers that currently
do LocalDirectory.Parse(env.FileTreeCacheDirectory) to use the property
directly; ensure any tests or consumers expecting a string are adjusted to call
.ToString() only when crossing a foreign boundary.
In `@src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs`:
- Around line 108-109: The code builds l2Path by composing
RepositoryPaths.GetChunkIndexCacheRoot(Account, containerName) with
RelativePath.Parse(prefix.ToString()), but prefix is already a PathSegment from
Shard.PrefixOf(contentHash); replace the parse round-trip with direct
composition using RelativePath.Root / prefix so that l2Path is created by
resolving RelativePath.Root / prefix instead of parsing prefix.ToString().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 88ef845a-f3bc-4729-95fa-12b7b54234ab
📒 Files selected for processing (32)
src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cssrc/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cssrc/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cssrc/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cssrc/Arius.Core.Tests/Shared/FileTree/Fakes/UnsortedSnapshotBlobContainerService.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeServiceTests.cssrc/Arius.Core.Tests/Shared/RepositoryPathsTests.cssrc/Arius.Core.Tests/Shared/Snapshot/SnapshotSerializerTests.cssrc/Arius.Core/Shared/Snapshot/SnapshotService.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cssrc/Arius.E2E.Tests/E2ETests.cssrc/Arius.E2E.Tests/Fixtures/E2EFixture.cssrc/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cssrc/Arius.E2E.Tests/Workflows/Steps/Helpers.cssrc/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cssrc/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cssrc/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/ListQueryIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/PipelineFixture.cssrc/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cssrc/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cssrc/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cssrc/Arius.Integration.Tests/Pipeline/RoundtripTests.cssrc/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cssrc/Arius.Tests.Shared/AssemblyMarker.cssrc/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cssrc/Arius.Tests.Shared/RepositoryPathStrings.cssrc/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs
💤 Files with no reviewable changes (3)
- src/Arius.Core/Shared/Snapshot/SnapshotService.cs
- src/Arius.Tests.Shared/RepositoryPathStrings.cs
- src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cs
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #91 +/- ##
==========================================
- Coverage 78.77% 78.71% -0.07%
==========================================
Files 78 78
Lines 5277 5289 +12
Branches 722 728 +6
==========================================
+ Hits 4157 4163 +6
Misses 928 928
- Partials 192 198 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Arius.Core.Tests/Shared/RepositoryTestFixtureTests.cs`:
- Around line 21-37: The test double-disposes the fixture:
RepositoryTestFixture.CreateWithEncryptionAsync is awaited into "fixture" with
"await using", so remove the explicit await fixture.DisposeAsync() call (the
DisposeAsync invocation on the fixture) to avoid running cleanup twice; rely on
the language-scoped disposal provided by "await using" and keep the remaining
assertions after the scope as-is.
In `@src/Arius.E2E.Tests/Fixtures/E2EFixtureTests.cs`:
- Around line 9-41: The test class E2EFixtureTests and its test method
DisposeAsync_PreservesLocalCache_WhenRequested (which uses
E2EFixture.ResetLocalCacheAsync, E2EFixture.CreateAsync,
PreserveLocalCacheAsync, DisposeAsync) should be moved out of
src/Arius.E2E.Tests into an appropriate unit-test project (e.g.,
Arius.Tests.Shared.Tests or a dedicated fixtures test project); create a new
test file in that project, update the namespace and test project references to
include any required test helpers or FakeInMemoryBlobContainerService, and
ensure the target test project's csproj references the production assembly
containing E2EFixture and RepositoryPaths so the test compiles and runs.
In `@src/Arius.Tests.Shared/AssemblyMarker.cs`:
- Line 9: Change the visibility of the marker type AssemblyMarker from public to
internal: locate the declaration "AssemblyMarker" (the sealed marker class) and
replace its public modifier with internal so tests access it via
InternalsVisibleTo; ensure no external non-test consumers require it before
changing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cff7dc05-374e-455b-b4f8-085af535675a
📒 Files selected for processing (26)
src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cssrc/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cssrc/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cssrc/Arius.Core.Tests/Shared/RepositoryTestFixtureTests.cssrc/Arius.Core/AssemblyMarker.cssrc/Arius.E2E.Tests/E2ETests.cssrc/Arius.E2E.Tests/Fixtures/E2EFixture.cssrc/Arius.E2E.Tests/Fixtures/E2EFixtureTests.cssrc/Arius.E2E.Tests/Workflows/RepresentativeWorkflowRunner.cssrc/Arius.E2E.Tests/Workflows/RepresentativeWorkflowState.cssrc/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cssrc/Arius.E2E.Tests/Workflows/Steps/Helpers.cssrc/Arius.E2E.Tests/Workflows/Steps/MaterializeVersionStep.cssrc/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cssrc/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cssrc/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/ListQueryIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/PipelineFixture.cssrc/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cssrc/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cssrc/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cssrc/Arius.Integration.Tests/Pipeline/RoundtripTests.cssrc/Arius.Tests.Shared/AssemblyMarker.cssrc/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs
✅ Files skipped from review due to trivial changes (1)
- src/Arius.Core/AssemblyMarker.cs
🚧 Files skipped from review as they are similar to previous changes (11)
- src/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cs
- src/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cs
- src/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cs
- src/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cs
- src/Arius.E2E.Tests/E2ETests.cs
- src/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cs
- src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
- src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs
- src/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cs
- src/Arius.Integration.Tests/Pipeline/ListQueryIntegrationTests.cs
- src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs
| public class E2EFixtureTests | ||
| { | ||
| [Test] | ||
| public async Task DisposeAsync_PreservesLocalCache_WhenRequested() | ||
| { | ||
| var accountName = $"acct-e2e-preserve-{Guid.NewGuid():N}"; | ||
| var containerName = $"ctr-e2e-preserve-{Guid.NewGuid():N}"; | ||
| var cacheRoot = RepositoryPaths.GetRepositoryRoot(accountName, containerName).ToString(); | ||
|
|
||
| await E2EFixture.ResetLocalCacheAsync(accountName, containerName); | ||
|
|
||
| try | ||
| { | ||
| var fixture = await E2EFixture.CreateAsync( | ||
| new FakeInMemoryBlobContainerService(), | ||
| accountName, | ||
| containerName, | ||
| BlobTier.Hot); | ||
|
|
||
| Directory.CreateDirectory(cacheRoot); | ||
| await File.WriteAllTextAsync(Path.Combine(cacheRoot, "lease-marker.txt"), "preserve"); | ||
|
|
||
| await fixture.PreserveLocalCacheAsync(); | ||
| await fixture.DisposeAsync(); | ||
|
|
||
| Directory.Exists(cacheRoot).ShouldBeTrue(); | ||
| File.Exists(Path.Combine(cacheRoot, "lease-marker.txt")).ShouldBeTrue(); | ||
| } | ||
| finally | ||
| { | ||
| await E2EFixture.ResetLocalCacheAsync(accountName, containerName); | ||
| } | ||
| } |
There was a problem hiding this comment.
Relocate fixture self-test out of src/Arius.E2E.Tests/.
This test verifies E2EFixture infrastructure behavior (cache preservation during dispose), not actual end-to-end Arius workflow coverage. As per coding guidelines: "src/Arius.E2E.Tests/ is reserved for actual end-to-end Arius behavior coverage. Do not add self-tests for E2E datasets, fixtures, scenario catalogs, or scenario runners there."
Move this test to an appropriate unit-test project for the E2E test infrastructure (e.g., Arius.Tests.Shared.Tests or a dedicated fixture-testing project).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Arius.E2E.Tests/Fixtures/E2EFixtureTests.cs` around lines 9 - 41, The
test class E2EFixtureTests and its test method
DisposeAsync_PreservesLocalCache_WhenRequested (which uses
E2EFixture.ResetLocalCacheAsync, E2EFixture.CreateAsync,
PreserveLocalCacheAsync, DisposeAsync) should be moved out of
src/Arius.E2E.Tests into an appropriate unit-test project (e.g.,
Arius.Tests.Shared.Tests or a dedicated fixtures test project); create a new
test file in that project, update the namespace and test project references to
include any required test helpers or FakeInMemoryBlobContainerService, and
ensure the target test project's csproj references the production assembly
containing E2EFixture and RepositoryPaths so the test compiles and runs.
|
|
||
| namespace Arius.Tests.Shared; | ||
|
|
||
| public sealed class AssemblyMarker { } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use internal visibility for the marker type.
AssemblyMarker should be internal here; test assemblies already have access via InternalsVisibleTo.
Suggested change
-public sealed class AssemblyMarker { }
+internal sealed class AssemblyMarker { }As per coding guidelines, "Make non-test classes internal. Only make them public when they must be consumed by another non-test assembly; for test access, prefer InternalsVisibleTo."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public sealed class AssemblyMarker { } | |
| internal sealed class AssemblyMarker { } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Arius.Tests.Shared/AssemblyMarker.cs` at line 9, Change the visibility of
the marker type AssemblyMarker from public to internal: locate the declaration
"AssemblyMarker" (the sealed marker class) and replace its public modifier with
internal so tests access it via InternalsVisibleTo; ensure no external non-test
consumers require it before changing.
Keep repository-relative existence checks on RelativeFileSystem so test code follows the same typed path boundaries as the rest of the codebase. Preserve LocalDirectory.Resolve for true host-path boundaries such as File.OpenRead and directory lifecycle work.
Keep in-memory blob test doubles on typed blob paths instead of string keys so blob lookups, listings, and collision state stay inside the existing RelativePath boundary.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs`:
- Around line 34-37: The current check-then-write on the ConcurrentDictionary
_blobs in UploadAsync and the OpenWriteAsync callback is non-atomic and can
cause TOCTOU races; change the logic so that when overwrite == false you use
_blobs.TryAdd(blobName, new StoredBlob(...)) and throw
BlobAlreadyExistsException if TryAdd returns false, while preserving direct
assignment (_blobs[blobName] = ...) when overwrite == true; apply the same
TryAdd+throw pattern inside the OpenWriteAsync callback where a new StoredBlob
is added so the add is atomic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: dfc51704-bbe0-4ef7-8d0f-76829fce4848
📒 Files selected for processing (2)
src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cssrc/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs
| if (!overwrite && _blobs.ContainsKey(blobName)) | ||
| throw new BlobAlreadyExistsException(blobName); | ||
|
|
||
| _blobs[blobKey] = new StoredBlob(ms.ToArray(), new Dictionary<string, string>(metadata), tier, contentType, false); | ||
| _blobs[blobName] = new StoredBlob(ms.ToArray(), new Dictionary<string, string>(metadata), tier, contentType, false); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/Arius.Tests.Shared/Storage && wc -l FakeInMemoryBlobContainerService.csRepository: woutervanranst/Arius7
Length of output: 105
🏁 Script executed:
cd src/Arius.Tests.Shared/Storage && cat -n FakeInMemoryBlobContainerService.cs | sed -n '1,70p'Repository: woutervanranst/Arius7
Length of output: 3924
Use atomic dictionary writes to prevent TOCTOU races
Lines 34-35 and 53-54 check-then-write on ConcurrentDictionary without atomicity. Concurrent callers can interleave between the check and write, causing the exception to be skipped and data to be silently overwritten. Since this test double models blob storage semantics for concurrent test scenarios, the race can mask actual concurrency bugs in production code.
Use ConcurrentDictionary.TryAdd() to make the check-and-set atomic:
- In
UploadAsyncwithoverwrite=false: useTryAdd()and throw if it returns false - In
UploadAsyncwithoverwrite=true: direct assignment (races are acceptable here) - In
OpenWriteAsynccallback: useTryAdd()and throw if it returns false
Also applies to: 53-60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs` around
lines 34 - 37, The current check-then-write on the ConcurrentDictionary _blobs
in UploadAsync and the OpenWriteAsync callback is non-atomic and can cause
TOCTOU races; change the logic so that when overwrite == false you use
_blobs.TryAdd(blobName, new StoredBlob(...)) and throw
BlobAlreadyExistsException if TryAdd returns false, while preserving direct
assignment (_blobs[blobName] = ...) when overwrite == true; apply the same
TryAdd+throw pattern inside the OpenWriteAsync callback where a new StoredBlob
is added so the add is atomic.
Keep synthetic dataset state keyed by RelativePath so workflow helpers and disk assertions can compare repository-relative files without stringify/parse round trips.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs (1)
17-17: ⚡ Quick winAvoid string-based path logic in typed-path assertions.
The pointer-file filter and ordering currently depend on
RelativePath.ToString(), which reintroduces string-path semantics in core test logic. Prefer aRelativePath-aware helper/comparer for suffix checks and ordering so the assertion path stays strongly typed end-to-end.As per coding guidelines, "Avoid stringify/parse round-trips. Preserve strong types until a real foreign boundary such as console output, configuration, serialization, or external SDK calls." and "Prefer RelativePath for repository-relative paths, subtree roots, and prefixes that may contain multiple segments."
Also applies to: 24-25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs` at line 17, The code is using relativePath.ToString().EndsWith(".pointer.arius", ...) which reintroduces string-path logic; replace this with a RelativePath-aware check and ordering using the RelativePath value itself. Create/inline a helper such as HasPointerFileSuffix(RelativePath relativePath) (or a small RelativePathSuffixComparer) and use that in the if condition instead of ToString().EndsWith, and update any sorting logic at the other occurrences (lines 24–25) to use the same RelativePath-aware comparator so suffix checks and ordering remain strongly typed and avoid string round-trips; reference the local variable relativePath and the includePointerFiles flag when applying the new helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs`:
- Line 17: The code is using relativePath.ToString().EndsWith(".pointer.arius",
...) which reintroduces string-path logic; replace this with a
RelativePath-aware check and ordering using the RelativePath value itself.
Create/inline a helper such as HasPointerFileSuffix(RelativePath relativePath)
(or a small RelativePathSuffixComparer) and use that in the if condition instead
of ToString().EndsWith, and update any sorting logic at the other occurrences
(lines 24–25) to use the same RelativePath-aware comparator so suffix checks and
ordering remain strongly typed and avoid string round-trips; reference the local
variable relativePath and the includePointerFiles flag when applying the new
helper.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6d87fe3b-93b9-4796-80e3-1c93119ce064
📒 Files selected for processing (4)
src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryState.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cssrc/Arius.E2E.Tests/Workflows/Steps/Helpers.cs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/Arius.E2E.Tests/Workflows/Steps/Helpers.cs
- src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cs
…ranst/Arius7 into remove-blobpathstrings
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs (1)
314-337:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid double-disposing
stagingSession1in the same test scope.
stagingSession1is declared withawait using, so the explicitawait stagingSession1.DisposeAsync()causes a second disposal at scope exit and can make this test brittle.Suggested patch
- await stagingSession1.DisposeAsync(); new RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs` around lines 314 - 337, stagingSession1 is already disposed by the "await using var stagingSession1 = await FileTreeStagingSession.OpenAsync(...)" pattern, so remove the explicit "await stagingSession1.DisposeAsync();" call to avoid double-dispose; keep the subsequent cleanup call to "new RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true);" but ensure it executes after the using scope if it relies on the session being disposed. References: stagingSession1, FileTreeStagingSession.OpenAsync, DisposeAsync, RelativeFileSystem.DeleteDirectory.
🧹 Nitpick comments (1)
src/Arius.E2E.Tests/Workflows/Steps/AssertRemoteStateStep.cs (1)
23-24: ⚡ Quick winAvoid order-dependent “latest snapshot” assertion
This assertion infers latest by taking the last listed blob, which can make the test flaky if enumeration order changes. Prefer asserting that the expected version is present (or compare against already resolved
latestSnapshotdata).Suggested change
- (await state.Fixture.Repository.Snapshot.ListBlobNamesAsync(cancellationToken))[^1].Name.ToString() - .ShouldBe(state.LatestSnapshotVersion, $"{Name}: latest resolved snapshot should match the most recent archive result."); + var snapshotNames = await state.Fixture.Repository.Snapshot.ListBlobNamesAsync(cancellationToken); + snapshotNames.Select(x => x.Name.ToString()) + .ShouldContain(state.LatestSnapshotVersion!, $"{Name}: latest resolved snapshot should match the most recent archive result.");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Arius.E2E.Tests/Workflows/Steps/AssertRemoteStateStep.cs` around lines 23 - 24, The test currently assumes the last returned blob is the latest by using (await state.Fixture.Repository.Snapshot.ListBlobNamesAsync(...))[^1].Name, which is order-dependent and flaky; change the assertion in AssertRemoteStateStep (the code that reads state.Fixture.Repository.Snapshot.ListBlobNamesAsync and compares to state.LatestSnapshotVersion) to instead verify that state.LatestSnapshotVersion is present in the returned list (e.g., search the blob names for an entry equal to state.LatestSnapshotVersion) or compare against the already-resolved latestSnapshot value rather than relying on list order; update the assertion message that uses Name to still reference Name for context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs`:
- Around line 41-45: The if-condition in ArchiveTierLifecycleStep currently
calls versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory)
before checking sourceVersion, which runs the filesystem check even when not
needed; change the condition order so the version check happens first (if
sourceVersion == SyntheticRepositoryVersion.V2 &&
!versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory)) so
RematerializeV1Async and
SyntheticRepositoryMaterializer.MaterializeV2FromExistingAsync are only invoked
when sourceVersion is V2 and the directory is missing.
In
`@src/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cs`:
- Line 85: The finally block calling new
RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true)
should be made best-effort: guard it against missing cache directories by either
checking existence before deleting or wrapping the DeleteDirectory call in a
try/catch that swallows DirectoryNotFoundException (and optionally IOException)
so tests don't fail when the cache was never created; update the three
occurrences that call DeleteDirectory(RelativePath.Root, recursive: true) to use
this guarded pattern referencing RelativeFileSystem.DeleteDirectory and
RelativePath.Root.
In `@src/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs`:
- Around line 256-262: In CreateTempRoots (RepositoryTestFixture) avoid
unconditional DeleteDirectory calls that throw when directories don't exist:
before calling tempRootFileSystem.DeleteDirectory for RelativePath.Root,
sourceDirectory, and restoreDirectory (and when using the ownsTempRoot flag),
check existence via the filesystem API (e.g., tempRootFileSystem.DirectoryExists
or equivalent) or wrap the delete in a try/catch that ignores not-found errors
so deletion is only attempted when the directory exists; update the block
referencing ownsTempRoot, tempRootFileSystem.DeleteDirectory, RelativePath.Root,
sourceDirectory, and restoreDirectory accordingly.
---
Outside diff comments:
In `@src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs`:
- Around line 314-337: stagingSession1 is already disposed by the "await using
var stagingSession1 = await FileTreeStagingSession.OpenAsync(...)" pattern, so
remove the explicit "await stagingSession1.DisposeAsync();" call to avoid
double-dispose; keep the subsequent cleanup call to "new
RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive:
true);" but ensure it executes after the using scope if it relies on the session
being disposed. References: stagingSession1, FileTreeStagingSession.OpenAsync,
DisposeAsync, RelativeFileSystem.DeleteDirectory.
---
Nitpick comments:
In `@src/Arius.E2E.Tests/Workflows/Steps/AssertRemoteStateStep.cs`:
- Around line 23-24: The test currently assumes the last returned blob is the
latest by using (await
state.Fixture.Repository.Snapshot.ListBlobNamesAsync(...))[^1].Name, which is
order-dependent and flaky; change the assertion in AssertRemoteStateStep (the
code that reads state.Fixture.Repository.Snapshot.ListBlobNamesAsync and
compares to state.LatestSnapshotVersion) to instead verify that
state.LatestSnapshotVersion is present in the returned list (e.g., search the
blob names for an entry equal to state.LatestSnapshotVersion) or compare against
the already-resolved latestSnapshot value rather than relying on list order;
update the assertion message that uses Name to still reference Name for context.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9771d8f1-ce8d-47a0-9fe4-5a9bdc2eef4b
📒 Files selected for processing (44)
src/Arius.Benchmarks/ArchiveStepBenchmarks.cssrc/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cssrc/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cssrc/Arius.Core.Tests/Features/ArchiveCommand/LocalFileEnumeratorTests.cssrc/Arius.Core.Tests/Features/ListQuery/ListQueryHandlerTests.cssrc/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cssrc/Arius.Core.Tests/Shared/FileSystem/LocalDirectoryTests.cssrc/Arius.Core.Tests/Shared/FileSystem/RelativeFileSystemTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreePathsTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeServiceTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeStagingSessionTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterPathValidationTests.cssrc/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterTests.cssrc/Arius.Core/Shared/FileSystem/RelativeFileSystem.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryState.cssrc/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cssrc/Arius.E2E.Tests/E2ETests.cssrc/Arius.E2E.Tests/Fixtures/E2EFixture.cssrc/Arius.E2E.Tests/RepresentativeArchiveRestoreTests.cssrc/Arius.E2E.Tests/Workflows/RepresentativeWorkflowRunner.cssrc/Arius.E2E.Tests/Workflows/Steps/ArchiveStep.cssrc/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cssrc/Arius.E2E.Tests/Workflows/Steps/AssertConflictBehaviorStep.cssrc/Arius.E2E.Tests/Workflows/Steps/AssertRemoteStateStep.cssrc/Arius.E2E.Tests/Workflows/Steps/Helpers.cssrc/Arius.E2E.Tests/Workflows/Steps/MaterializeVersionStep.cssrc/Arius.E2E.Tests/Workflows/Steps/RestoreStep.cssrc/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cssrc/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cssrc/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cssrc/Arius.Integration.Tests/Pipeline/PipelineFixture.cssrc/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cssrc/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cssrc/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cssrc/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cssrc/Arius.Integration.Tests/Pipeline/RoundtripTests.cssrc/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cssrc/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cssrc/Arius.Tests.Shared/IO/FileSystemHelper.cssrc/Arius.Tests.Shared/TestTempRoots.cs
| if (!versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory) && sourceVersion == SyntheticRepositoryVersion.V2) | ||
| { | ||
| var v1State = await MaterializeVersionStep.RematerializeV1Async(state, cancellationToken); | ||
| var versionRootPath = Path.Combine(state.VersionedSourceRoot, nameof(SyntheticRepositoryVersion.V2)); | ||
| sourceState = await SyntheticRepositoryMaterializer.MaterializeV2FromExistingAsync(state.Definition, state.Seed, v1State.RootPath, versionRootPath, state.Fixture.Encryption); | ||
| var versionRootPath = LocalDirectory.Parse(state.VersionedSourceDirectory.Resolve(RelativePath.Parse(nameof(SyntheticRepositoryVersion.V2)))); | ||
| sourceState = await SyntheticRepositoryMaterializer.MaterializeV2FromExistingAsync(state.Definition, state.Seed, v1State.RootDirectory, versionRootPath, state.Fixture.Encryption); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect RelativeFileSystem API shape for DirectoryExists overloads
fd -i RelativeFileSystem.cs
rg -nP --type=cs -C3 '\bclass\s+RelativeFileSystem\b|\bDirectoryExists\s*\('
# 2) Inspect this call site context
rg -nP --type=cs -C4 'versionedSourceFileSystem\.DirectoryExists\s*\('Repository: woutervanranst/Arius7
Length of output: 15297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs"
# Show the relevant region around DirectoryExists and GetContainedDirectoryPath
nl -ba "$file" | sed -n '1,140p'Repository: woutervanranst/Arius7
Length of output: 108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs"
# Print the whole file with line numbers using cat -n (nl isn't available)
cat -n "$file" | sed -n '1,200p'Repository: woutervanranst/Arius7
Length of output: 9827
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "GetContainedDirectoryPath\s*\(" -S src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs
# Print the method body area once we know the line number
line=$(rg -n "GetContainedDirectoryPath\s*\(" -S src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs | head -n1 | cut -d: -f1)
start=$((line-40))
end=$((line+80))
sed -n "${start},${end}p" src/Arius.Core/Shared/FileSystem/RelativeFileSystem.csRepository: woutervanranst/Arius7
Length of output: 5737
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '180,280p' src/Arius.Core/Shared/FileSystem/RelativeFileSystem.csRepository: woutervanranst/Arius7
Length of output: 1493
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '30,70p' src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.csRepository: woutervanranst/Arius7
Length of output: 2728
Fix the condition ordering in ArchiveTierLifecycleStep (line 41)
RelativeFileSystem.DirectoryExists(LocalDirectory) is a valid overload and enforces root containment via GetContainedDirectoryPath, so the LocalDirectory-vs-RelativePath concern is not applicable.
Reorder the operands so the filesystem check only runs when sourceVersion == SyntheticRepositoryVersion.V2, e.g.:
if (sourceVersion == SyntheticRepositoryVersion.V2 && !versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs` around lines
41 - 45, The if-condition in ArchiveTierLifecycleStep currently calls
versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory) before
checking sourceVersion, which runs the filesystem check even when not needed;
change the condition order so the version check happens first (if sourceVersion
== SyntheticRepositoryVersion.V2 &&
!versionedSourceFileSystem.DirectoryExists(sourceState.RootDirectory)) so
RematerializeV1Async and
SyntheticRepositoryMaterializer.MaterializeV2FromExistingAsync are only invoked
when sourceVersion is V2 and the directory is missing.
| var cacheDir = RepositoryPathStrings.GetFileTreeCacheDirectory(Account, container.Name); | ||
| if (Directory.Exists(cacheDir)) Directory.Delete(cacheDir, recursive: true); | ||
| var cacheDir = RepositoryPaths.GetFileTreeCacheRoot(Account, container.Name); | ||
| new RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true); |
There was a problem hiding this comment.
Guard cache cleanup against missing directories in finally.
DeleteDirectory(RelativePath.Root, recursive: true) can throw when the cache root was never created, which turns cleanup into test failures. Please make cleanup best-effort (ignore missing directory).
Suggested fix
- new RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true);
+ try
+ {
+ new RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // no-op: cache directory may not exist in some test paths
+ }Also applies to: 129-129, 166-166
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cs`
at line 85, The finally block calling new
RelativeFileSystem(cacheDir).DeleteDirectory(RelativePath.Root, recursive: true)
should be made best-effort: guard it against missing cache directories by either
checking existence before deleting or wrapping the DeleteDirectory call in a
try/catch that swallows DirectoryNotFoundException (and optionally IOException)
so tests don't fail when the cache was never created; update the three
occurrences that call DeleteDirectory(RelativePath.Root, recursive: true) to use
this guarded pattern referencing RelativeFileSystem.DeleteDirectory and
RelativePath.Root.
| if (ownsTempRoot) | ||
| tempRootFileSystem.DeleteDirectory(RelativePath.Root, recursive: true); | ||
| else | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(relativePath), "Path must stay within the fixture root."); | ||
| tempRootFileSystem.DeleteDirectory(sourceDirectory, recursive: true); | ||
| tempRootFileSystem.DeleteDirectory(restoreDirectory, recursive: true); | ||
| } |
There was a problem hiding this comment.
CreateTempRoots can fail when deleting non-existent directories.
The delete calls are unconditional and can throw before setup runs (especially for fresh temp roots). That makes fixture creation brittle across many tests.
Suggested fix
- if (ownsTempRoot)
- tempRootFileSystem.DeleteDirectory(RelativePath.Root, recursive: true);
+ if (ownsTempRoot)
+ {
+ try
+ {
+ tempRootFileSystem.DeleteDirectory(RelativePath.Root, recursive: true);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ }
+ }
else
{
- tempRootFileSystem.DeleteDirectory(sourceDirectory, recursive: true);
- tempRootFileSystem.DeleteDirectory(restoreDirectory, recursive: true);
+ try { tempRootFileSystem.DeleteDirectory(sourceDirectory, recursive: true); }
+ catch (DirectoryNotFoundException) { }
+ try { tempRootFileSystem.DeleteDirectory(restoreDirectory, recursive: true); }
+ catch (DirectoryNotFoundException) { }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (ownsTempRoot) | |
| tempRootFileSystem.DeleteDirectory(RelativePath.Root, recursive: true); | |
| else | |
| { | |
| throw new ArgumentOutOfRangeException(nameof(relativePath), "Path must stay within the fixture root."); | |
| tempRootFileSystem.DeleteDirectory(sourceDirectory, recursive: true); | |
| tempRootFileSystem.DeleteDirectory(restoreDirectory, recursive: true); | |
| } | |
| if (ownsTempRoot) | |
| { | |
| try | |
| { | |
| tempRootFileSystem.DeleteDirectory(RelativePath.Root, recursive: true); | |
| } | |
| catch (DirectoryNotFoundException) | |
| { | |
| } | |
| } | |
| else | |
| { | |
| try { tempRootFileSystem.DeleteDirectory(sourceDirectory, recursive: true); } | |
| catch (DirectoryNotFoundException) { } | |
| try { tempRootFileSystem.DeleteDirectory(restoreDirectory, recursive: true); } | |
| catch (DirectoryNotFoundException) { } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs` around lines 256 -
262, In CreateTempRoots (RepositoryTestFixture) avoid unconditional
DeleteDirectory calls that throw when directories don't exist: before calling
tempRootFileSystem.DeleteDirectory for RelativePath.Root, sourceDirectory, and
restoreDirectory (and when using the ownsTempRoot flag), check existence via the
filesystem API (e.g., tempRootFileSystem.DirectoryExists or equivalent) or wrap
the delete in a try/catch that ignores not-found errors so deletion is only
attempted when the directory exists; update the block referencing ownsTempRoot,
tempRootFileSystem.DeleteDirectory, RelativePath.Root, sourceDirectory, and
restoreDirectory accordingly.
Summary by CodeRabbit
Tests
Chores