Skip to content

refactor: replace repo-relative string helpers with RelativePath#91

Open
woutervanranst wants to merge 47 commits into
masterfrom
remove-blobpathstrings
Open

refactor: replace repo-relative string helpers with RelativePath#91
woutervanranst wants to merge 47 commits into
masterfrom
remove-blobpathstrings

Conversation

@woutervanranst
Copy link
Copy Markdown
Owner

@woutervanranst woutervanranst commented May 12, 2026

Summary by CodeRabbit

  • Tests

    • Migrated test suites to typed path and filesystem APIs, added/updated many unit, integration and E2E tests for robust path handling, staging, and staging-writer behavior; improved cleanup and validation via filesystem abstractions.
  • Chores

    • Removed legacy string-based path helpers, updated fixture APIs and signatures, added test temp-root helpers, and expanded test-assembly visibility to support cross-suite testing.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This 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.

Changes

Typed filesystem migration (tests, fixtures, fakes, helpers)

Layer / File(s) Summary
Repository test fixture core & assembly visibility
src/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs, src/Arius.Tests.Shared/AssemblyMarker.cs, src/Arius.Core/AssemblyMarker.cs
Parse temp roots into LocalDirectory, expose RelativeFileSystem/LocalDirectory/RestoreDirectory properties, add resetLocalCacheOnDispose/ownsTempRoot flags and factory forwarding; add/expand InternalsVisibleTo entries.
Remove string-path helpers
src/Arius.Tests.Shared/RepositoryPathStrings.cs
Delete RepositoryPathStrings and replace call sites with RepositoryPaths typed helpers returning LocalDirectory/RelativePath.
Fixture public IO APIs & pipeline fixture
src/Arius.E2E.Tests/Fixtures/E2EFixture.cs, src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
Expose repository-backed LocalDirectory/RelativeFileSystem props; change/remove legacy string-based file I/O helpers and delegate to underlying repository fixture.
Archive test environment & archive tests
src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs, src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs
Cache roots stored as LocalDirectory, create cached RelativeFileSystem, ensure cache dirs exist before archive runs; WriteRandomFile accepts RelativePath; tests updated to use RelativePath.Parse(...).
Restore tests & snapshot parsing
src/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cs, src/Arius.Core/Shared/Snapshot/SnapshotService.cs, src/Arius.Core.Tests/Shared/Snapshot/SnapshotSerializerTests.cs
Introduce AssertRestoredFile helper using RelativePath and typed file systems; change SnapshotService.ParseTimestamp to accept RelativePath (removed string overload); update tests accordingly.
File-tree builder/service tests & integration
src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs, src/Arius.Core.Tests/Shared/FileTree/FileTreeServiceTests.cs, src/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cs
Switch cache/staging helpers to LocalDirectory, use RepositoryPaths helpers and RelativeFileSystem(...).DeleteDirectory(RelativePath.Root, recursive:true) for cleanup; replace some FileName helpers with PathSegment.Parse.
Fake blob container services
src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cs, src/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs, src/Arius.Core.Tests/Shared/FileTree/Fakes/UnsortedSnapshotBlobContainerService.cs
Change internal storage and public seeding/metadata/listing APIs to key by RelativePath (remove ToString-based keying and string overloads); adapt upload/open/download/list/delete flows to use RelativePath keys.
E2E workflows & runner/state
src/Arius.E2E.Tests/Workflows/RepresentativeWorkflowRunner.cs, src/Arius.E2E.Tests/Workflows/RepresentativeWorkflowState.cs, src/Arius.E2E.Tests/Workflows/Steps/*
Use LocalDirectory for workflow roots and Resolve(RelativePath.Parse(...)) for subpaths; update steps (notably ArchiveTierLifecycleStep) to accept/produce RelativePath targets and use RelativeFileSystem for verification.
Synthetic materializer & repository state
src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cs, src/Arius.E2E.Tests/Datasets/SyntheticRepositoryState.cs, src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs
Materializer, state, and assertions now use RelativePath keys and typed helpers for writes, hashing, mutations, and disk-tree assertions.
Integration & pipeline tests (writes/restore verifications)
multiple files under src/Arius.Integration.Tests/Pipeline/* and src/Arius.Integration.Tests/* (e.g., RoundtripTests.cs, RestoreDispositionTests.cs, GcmIntegrationTests.cs, CrashRecoveryTests.cs, ListQueryIntegrationTests.cs, ContainerCreationTests.cs, RecoveryScriptTests.cs, RehydrationStateTests.cs, RestoreCostModelTests.cs)
Systematically replace string-path test writes and restore checks with RelativePath.Parse(...) + LocalFileSystem.WriteAllBytesAsync(RelativePath, ...) and RestoreFileSystem.ReadAllBytes/FileExists(RelativePath); add using Arius.Core.Shared.FileSystem where needed and remove deprecated fixture helpers.
Helpers and step utilities
src/Arius.E2E.Tests/Workflows/Steps/Helpers.cs, src/Arius.E2E.Tests/Workflows/Steps/MaterializeVersionStep.cs
Update helper signatures to accept RelativePath, resolve conflict/restore paths via fixture-local RelativeFileSystem, and adapt duplicate-content hashing helpers to accept RelativePath.
RelativeFileSystem core additions
src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs
Add LocalDirectory overloads for Create/Delete/Exists; ensure parent dirs are created via CreateDirectory(path.Parent ?? RelativePath.Root); containment-check helper added and DeleteDirectory refactor to ignore DirectoryNotFoundException.
New test helpers
src/Arius.Tests.Shared/TestTempRoots.cs, src/Arius.Tests.Shared/IO/FileSystemHelper.cs
Add TestTempRoots helper and LocalDirectory overload for FileSystemHelper.CopyDirectory; copy now preserves timestamps and creates target dirs.
Staging writer/session & file-tree tests
src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingSessionTests.cs, src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterTests.cs, src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterPathValidationTests.cs, src/Arius.Core.Tests/Shared/FileTree/FileTreePathsTests.cs
Add new staging session and path-validation tests; refactor staging-writer tests to shared temp root, parse staged entries for structural assertions; add FileTreePaths tests for path parsing and directory-id hashing.

Sequence Diagram(s)

(no diagrams generated)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch remove-blobpathstrings

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs (1)

108-109: ⚡ Quick win

Use RelativePath.Root / prefix instead of RelativePath.Parse(prefix.ToString()).

prefix is already a typed PathSegment from Shard.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 win

Expose FileTreeCacheDirectory as LocalDirectory instead of string.

This currently forces a LocalDirectory -> string -> LocalDirectory round-trip (see downstream LocalDirectory.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

📥 Commits

Reviewing files that changed from the base of the PR and between 0447b29 and 79e8bc5.

📒 Files selected for processing (32)
  • src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs
  • src/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/Fakes/UnsortedSnapshotBlobContainerService.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeServiceTests.cs
  • src/Arius.Core.Tests/Shared/RepositoryPathsTests.cs
  • src/Arius.Core.Tests/Shared/Snapshot/SnapshotSerializerTests.cs
  • src/Arius.Core/Shared/Snapshot/SnapshotService.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cs
  • src/Arius.E2E.Tests/E2ETests.cs
  • src/Arius.E2E.Tests/Fixtures/E2EFixture.cs
  • src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/Helpers.cs
  • src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cs
  • src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
  • src/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/ListQueryIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
  • src/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cs
  • src/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cs
  • src/Arius.Integration.Tests/Pipeline/RoundtripTests.cs
  • src/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cs
  • src/Arius.Tests.Shared/AssemblyMarker.cs
  • src/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs
  • src/Arius.Tests.Shared/RepositoryPathStrings.cs
  • src/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

Comment thread src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

❌ Patch coverage is 70.00000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.71%. Comparing base (7206122) to head (835ace5).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
...Arius.Core/Shared/FileSystem/RelativeFileSystem.cs 70.00% 0 Missing and 6 partials ⚠️
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     
Flag Coverage Δ
linux 81.85% <70.00%> (-0.09%) ⬇️
windows 77.16% <70.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 79e8bc5 and eab5716.

📒 Files selected for processing (26)
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs
  • src/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cs
  • src/Arius.Core.Tests/Shared/RepositoryTestFixtureTests.cs
  • src/Arius.Core/AssemblyMarker.cs
  • src/Arius.E2E.Tests/E2ETests.cs
  • src/Arius.E2E.Tests/Fixtures/E2EFixture.cs
  • src/Arius.E2E.Tests/Fixtures/E2EFixtureTests.cs
  • src/Arius.E2E.Tests/Workflows/RepresentativeWorkflowRunner.cs
  • src/Arius.E2E.Tests/Workflows/RepresentativeWorkflowState.cs
  • src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/Helpers.cs
  • src/Arius.E2E.Tests/Workflows/Steps/MaterializeVersionStep.cs
  • src/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cs
  • src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
  • src/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/ListQueryIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
  • src/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cs
  • src/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cs
  • src/Arius.Integration.Tests/Pipeline/RoundtripTests.cs
  • src/Arius.Tests.Shared/AssemblyMarker.cs
  • src/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

Comment thread src/Arius.Core.Tests/Shared/RepositoryTestFixtureTests.cs Outdated
Comment on lines +9 to +41
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);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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 { }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 16396ed and d0ce214.

📒 Files selected for processing (2)
  • src/Arius.Core.Tests/Fakes/FakeSeededBlobContainerService.cs
  • src/Arius.Tests.Shared/Storage/FakeInMemoryBlobContainerService.cs

Comment on lines +34 to +37
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd src/Arius.Tests.Shared/Storage && wc -l FakeInMemoryBlobContainerService.cs

Repository: 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 UploadAsync with overwrite=false: use TryAdd() and throw if it returns false
  • In UploadAsync with overwrite=true: direct assignment (races are acceptable here)
  • In OpenWriteAsync callback: use TryAdd() 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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs (1)

17-17: ⚡ Quick win

Avoid 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 a RelativePath-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

📥 Commits

Reviewing files that changed from the base of the PR and between d0ce214 and 20263ae.

📒 Files selected for processing (4)
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryState.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs
  • src/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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Avoid double-disposing stagingSession1 in the same test scope.

stagingSession1 is declared with await using, so the explicit await 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 win

Avoid 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 latestSnapshot data).

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

📥 Commits

Reviewing files that changed from the base of the PR and between d0ce214 and 835ace5.

📒 Files selected for processing (44)
  • src/Arius.Benchmarks/ArchiveStepBenchmarks.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveRecoveryTests.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/ArchiveTestEnvironment.cs
  • src/Arius.Core.Tests/Features/ArchiveCommand/LocalFileEnumeratorTests.cs
  • src/Arius.Core.Tests/Features/ListQuery/ListQueryHandlerTests.cs
  • src/Arius.Core.Tests/Features/RestoreCommand/RestoreCommandHandlerTests.cs
  • src/Arius.Core.Tests/Shared/FileSystem/LocalDirectoryTests.cs
  • src/Arius.Core.Tests/Shared/FileSystem/RelativeFileSystemTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeBuilderTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreePathsTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeServiceTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingSessionTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterPathValidationTests.cs
  • src/Arius.Core.Tests/Shared/FileTree/FileTreeStagingWriterTests.cs
  • src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryMaterializer.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryState.cs
  • src/Arius.E2E.Tests/Datasets/SyntheticRepositoryStateAssertions.cs
  • src/Arius.E2E.Tests/E2ETests.cs
  • src/Arius.E2E.Tests/Fixtures/E2EFixture.cs
  • src/Arius.E2E.Tests/RepresentativeArchiveRestoreTests.cs
  • src/Arius.E2E.Tests/Workflows/RepresentativeWorkflowRunner.cs
  • src/Arius.E2E.Tests/Workflows/Steps/ArchiveStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/AssertConflictBehaviorStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/AssertRemoteStateStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/Helpers.cs
  • src/Arius.E2E.Tests/Workflows/Steps/MaterializeVersionStep.cs
  • src/Arius.E2E.Tests/Workflows/Steps/RestoreStep.cs
  • src/Arius.Integration.Tests/ChunkIndex/ChunkIndexServiceIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/ContainerCreationTests.cs
  • src/Arius.Integration.Tests/Pipeline/CrashRecoveryTests.cs
  • src/Arius.Integration.Tests/Pipeline/GcmIntegrationTests.cs
  • src/Arius.Integration.Tests/Pipeline/PipelineFixture.cs
  • src/Arius.Integration.Tests/Pipeline/RecoveryScriptTests.cs
  • src/Arius.Integration.Tests/Pipeline/RehydrationStateTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreCostModelTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestoreDispositionTests.cs
  • src/Arius.Integration.Tests/Pipeline/RestorePointerTimestampTests.cs
  • src/Arius.Integration.Tests/Pipeline/RoundtripTests.cs
  • src/Arius.Integration.Tests/Shared/FileTree/FileTreeBuilderIntegrationTests.cs
  • src/Arius.Tests.Shared/Fixtures/RepositoryTestFixture.cs
  • src/Arius.Tests.Shared/IO/FileSystemHelper.cs
  • src/Arius.Tests.Shared/TestTempRoots.cs

Comment on lines +41 to +45
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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.cs

Repository: woutervanranst/Arius7

Length of output: 5737


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '180,280p' src/Arius.Core/Shared/FileSystem/RelativeFileSystem.cs

Repository: woutervanranst/Arius7

Length of output: 1493


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '30,70p' src/Arius.E2E.Tests/Workflows/Steps/ArchiveTierLifecycleStep.cs

Repository: 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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Comment on lines +256 to 262
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);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant