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)]