Skip to content

How to detect the context in DefaultAccessControlStrategy() #1158

Open

Description

@altmann

My usecase is ...

  • 2 files
    • myFile1.txt (list, read content)
    • myFile2.txt (list)
  • I can list both files _fileSystem.Directory.GetFiles(path) (no exception expected)
  • I can read the data only of myFile1.txt _fileSystem.File.ReadAllBytesAsync(path)

My setup:

MockFileSystem.WithAccessControlStrategy(new DefaultAccessControlStrategy((path, ext) =>
{
    var isDirectory = FileSystem.MockFileSystem.Directory.Exists(path);
    if (isDirectory)
    {
        return true;
    }

    if (path.Contains("myFile2.txt"))
    {
        return false;
    }

    return true;
}));

If I call _fileSystem.Directory.GetFiles(directoryOfFiles) then I got 3 calls into this hook:

  • first the directory path => true
  • then path to myFile1.txt => true
  • then path to myFile2.txt => false

In summary I got an Exception if I call _fileSystem.Directory.GetFiles(directoryOfFiles). How can I solve my problem? How can I detect which method is called _fileSystem.Directory.GetFiles(path) or _fileSystem.File.ReadAllBytesAsync(path)

Thank you in advance!

Activity

  1. tevinch commented on Oct 28, 2026

    @tevinch

    DefaultAccessControlStrategy gets the full path and extension data, but no method/operation context. A path denied by that callback can also fail GetFiles, so the callback cannot express “list this file, but deny ReadAllBytesAsync”.

    For that specific application test, you can compose the IFileSystem interface: keep MockFileSystem as the target and intercept only IFile.ReadAllBytesAsync for the second path. Your service still receives IFileSystem; no changes to its calls are needed.

    Runnable example and the small helper use Testably 7.1.1 and Castle.Core 5.2.1. The executable lists both files, reads the first, observes denial for the second, then lists and reads again. It also checks cancellation against the underlying mock, concurrent reads, relative paths, a matching filename in another directory, and unchanged content. These were scripted .NET 8 checks on macOS, with a separate Windows-simulated fixture reproducing the ACL behavior.

    This is a test-only policy for that method, not full ACL emulation. Other read methods retain their underlying behavior. Start with a fresh mock using this setup instead of the blanket path-denial callback; wrapping an already restrictive ACL does not remove its restriction.

  2. vbreuss commented on Oct 28, 2026

    @vbreuss
    Member

    Thank you for the detailed report, @altmann!

    You've found a bug in the MockFileSystem. Enumerating a directory called the access control strategy for every item it found. So denying access to a single file made Directory.GetFiles throw, even though only reading the file should fail. The real file system doesn't do that: listing a directory only needs access to the directory itself, and the files in it aren't opened.

    I fixed it so the mock behaves like the real file system. During enumeration, only the directories that are actually opened are checked: the directory you list and, when searching recursively, the subdirectories it recurses into. With the fix, your setup works as you expected: both files are listed, and File.ReadAllBytesAsync for myFile2.txt throws an UnauthorizedAccessException. You can also drop the directory check from your callback:

    MockFileSystem.WithAccessControlStrategy(new DefaultAccessControlStrategy(
        (path, _) => !path.Contains("myFile2.txt")));

    Until the fix is released, you can work around it with a strategy that only starts denying access right before the read:

    bool denyMyFile2 = false;
    MockFileSystem.WithAccessControlStrategy(new DefaultAccessControlStrategy(
        (path, _) => !(denyMyFile2 && path.Contains("myFile2.txt"))));
    
    string[] files = _fileSystem.Directory.GetFiles(directoryOfFiles); // lists both files
    denyMyFile2 = true;
    // _fileSystem.File.ReadAllBytesAsync(...) for myFile2.txt now throws

    This only works if your test can call the listing and the read separately.

    I am also thinking about giving more information in the interface, but that is a separate feature and needs more thought.

  3. vbreuss commented on Oct 29, 2026

    @vbreuss
    Member

    @altmann The immediate fix for you problem is released in v7.1.2.

  4. github-actions commented on Oct 30, 2026

    @github-actions

    This is addressed in release v7.2.0.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions