From 313211b2d8b2c49f84a214cc7b793a2ccb5cfbc6 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 28 Mar 2026 22:49:29 +0500 Subject: [PATCH] Fix: TryUnwrapPrefix returns null for all non-root paths when prefix is "/" --- .../PrefixedFileSystem.cs | 8 ++- .../PrefixedFileSystemTests.cs | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Ramstack.FileSystem.Prefixed/PrefixedFileSystem.cs b/src/Ramstack.FileSystem.Prefixed/PrefixedFileSystem.cs index cd731e0..a9eb554 100644 --- a/src/Ramstack.FileSystem.Prefixed/PrefixedFileSystem.cs +++ b/src/Ramstack.FileSystem.Prefixed/PrefixedFileSystem.cs @@ -113,11 +113,15 @@ internal string WrapWithPrefix(string underlyingPath) { Debug.Assert(VirtualPath.IsNormalized(path)); + if (prefix == "/") + return path; + if (path == prefix) return "/"; - if (path.StartsWith(prefix, StringComparison.Ordinal) && path[prefix.Length] == '/') - return new string(path.AsSpan(prefix.Length)); + if ((uint)prefix.Length < (uint)path.Length) + if (path.StartsWith(prefix, StringComparison.Ordinal) && path[prefix.Length] == '/') + return new string(path.AsSpan(prefix.Length)); return null; } diff --git a/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs b/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs index d60adb0..c0264e0 100644 --- a/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs @@ -1,3 +1,5 @@ +using System.Reflection; + using Ramstack.FileSystem.Physical; using Ramstack.FileSystem.Specification.Tests; using Ramstack.FileSystem.Specification.Tests.Utilities; @@ -7,8 +9,59 @@ namespace Ramstack.FileSystem.Prefixed; [TestFixture] public class PrefixedFileSystemTests : VirtualFileSystemSpecificationTests { + private static readonly Func s_unwrapPrefix = + typeof(PrefixedFileSystem) + .GetMethod("TryUnwrapPrefix", BindingFlags.Static | BindingFlags.NonPublic)! + .CreateDelegate>(); + private readonly TempFileStorage _storage = new TempFileStorage(); + [TestCase("/", "/", ExpectedResult = "/")] + [TestCase("/", "/foo", ExpectedResult = "/foo")] + [TestCase("/", "/a/b/c", ExpectedResult = "/a/b/c")] + + [TestCase("/a/b", "/a/b", ExpectedResult = "/")] + + [TestCase("/a/b", "/a/b/c", ExpectedResult = "/c")] + [TestCase("/a/b", "/a/b/c/d", ExpectedResult = "/c/d")] + + [TestCase("/a/b", "/a/bc", ExpectedResult = null)] + + [TestCase("/a/b", "/a/c", ExpectedResult = null)] + [TestCase("/a/b", "/a", ExpectedResult = null)] + public string? UnwrapPrefix(string prefix, string path) => + s_unwrapPrefix(path, prefix); + + [Test] + public async Task File_RootPrefix_DelegatesToInnerProvider() + { + using var fs = new PrefixedFileSystem("/", + new PhysicalFileSystem(_storage.Root)); + + var file = fs.GetFile("/project/README.md"); + Assert.That(await file.ExistsAsync(), Is.True); + } + + [Test] + public async Task File_RootPrefix_MissingFile_ReturnsNotFound() + { + using var fs = new PrefixedFileSystem("/", + new PhysicalFileSystem(_storage.Root)); + + var file = fs.GetFile("/project/nonexistent.txt"); + Assert.That(await file.ExistsAsync(), Is.False); + } + + [Test] + public async Task Directory_RootPrefix_DelegatesToInnerProvider() + { + using var fs = new PrefixedFileSystem("/", + new PhysicalFileSystem(_storage.Root)); + + var file = fs.GetDirectory("/project"); + Assert.That(await file.ExistsAsync(), Is.True); + } + [Test] public async Task File_Create_InsideArtificialDirectory_ThrowsException() {