diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
index ac3e41a36..e77e214da 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
@@ -203,11 +203,12 @@ public FileSecurity AccessControl
public FileShare AllowedFileShare { get; set; } = FileShare.ReadWrite | FileShare.Delete;
///
/// Checks whether the file is accessible for this type of FileAccess.
- /// MockfileData can be configured to have FileShare.None, which indicates it is locked by a 'different process'.
+ /// MockFileData can be configured to have FileShare.None, which indicates it is locked by a 'different process'.
///
/// If the file is 'locked by a different process', an IOException will be thrown.
+ /// If the file is read-only and is accessed for writing, an UnauthorizedAccessException will be thrown.
///
- /// The path is used in the IOException message to match the message in real life situations
+ /// The path is used in the exception message to match the message in real life situations
/// The access type to check
internal void CheckFileAccess(string path, FileAccess access)
{
@@ -215,6 +216,11 @@ internal void CheckFileAccess(string path, FileAccess access)
{
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
}
+
+ if (Attributes.HasFlag(FileAttributes.ReadOnly) && access.HasFlag(FileAccess.Write))
+ {
+ throw CommonExceptions.AccessDenied(path);
+ }
}
internal virtual MockFileData Clone()
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
index a31104282..fcfb0e4a1 100644
--- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs
@@ -104,6 +104,52 @@ public void MockFileStream_Constructor_ReadTypeNotWritable()
Assert.Throws(() => stream.WriteByte(1));
}
+ [Test]
+ [TestCase(FileAccess.Write)]
+ [TestCase(FileAccess.ReadWrite)]
+ public void MockFileStream_Constructor_WriteAccessOnReadOnlyFile_Throws_Exception(
+ FileAccess fileAccess)
+ {
+ // Arrange
+ var filePath = @"C:\test.txt";
+ var fileSystem = new MockFileSystem(new Dictionary
+ {
+ { filePath, new MockFileData("hi") { Attributes = FileAttributes.ReadOnly } }
+ });
+
+ // Act
+ Assert.Throws(() => new MockFileStream(fileSystem, filePath, FileMode.Open, fileAccess));
+ }
+
+ [Test]
+ public void MockFileStream_Constructor_ReadAccessOnReadOnlyFile_Does_Not_Throw_Exception()
+ {
+ // Arrange
+ var filePath = @"C:\test.txt";
+ var fileSystem = new MockFileSystem(new Dictionary
+ {
+ { filePath, new MockFileData("hi") { Attributes = FileAttributes.ReadOnly } }
+ });
+
+ // Act
+ Assert.DoesNotThrow(() => new MockFileStream(fileSystem, filePath, FileMode.Open, FileAccess.Read));
+ }
+
+
+ [Test]
+ public void MockFileStream_Constructor_WriteAccessOnNonReadOnlyFile_Does_Not_Throw_Exception()
+ {
+ // Arrange
+ var filePath = @"C:\test.txt";
+ var fileSystem = new MockFileSystem(new Dictionary
+ {
+ { filePath, new MockFileData("hi") { Attributes = FileAttributes.Normal } }
+ });
+
+ // Act
+ Assert.DoesNotThrow(() => new MockFileStream(fileSystem, filePath, FileMode.Open, FileAccess.Write));
+ }
+
[Test]
[TestCase(FileShare.None, FileAccess.Read)]
[TestCase(FileShare.None, FileAccess.ReadWrite)]