diff --git a/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs index 0ea1d98..f898883 100644 --- a/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/FileAssertions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.IO; +using System.Linq; using System.Text; namespace Testably.Abstractions.FluentAssertions; @@ -17,6 +18,50 @@ internal FileAssertions(IFileInfo? instance) { } + /// + /// Asserts that the current file does not have the given . + /// + public AndConstraint DoesNotHaveAttribute( + FileAttributes attribute, string because = "", params object[] becauseArgs) + { + Execute.Assertion + .WithDefaultIdentifier(Identifier) + .BecauseOf(because, becauseArgs) + .ForCondition(Subject != null) + .FailWith( + $"You can't assert that the file does not have attribute {attribute} if it is null") + .Then + .Given(() => Subject!) + .ForCondition(fileInfo => !fileInfo.Attributes.HasFlag(attribute)) + .FailWith( + $"Expected {{context}} {{0}} not to have attribute {attribute}{{reason}}, but it did.", + fileInfo => fileInfo.Name); + + return new AndConstraint(this); + } + + /// + /// Asserts that the current file has the given . + /// + public AndConstraint HasAttribute( + FileAttributes attribute, string because = "", params object[] becauseArgs) + { + Execute.Assertion + .WithDefaultIdentifier(Identifier) + .BecauseOf(because, becauseArgs) + .ForCondition(Subject != null) + .FailWith( + $"You can't assert that the file has attribute {attribute} if it is null") + .Then + .Given(() => Subject!) + .ForCondition(fileInfo => fileInfo.Attributes.HasFlag(attribute)) + .FailWith( + $"Expected {{context}} {{0}} to have attribute {attribute}{{reason}}, but it did not.", + fileInfo => fileInfo.Name); + + return new AndConstraint(this); + } + /// /// Asserts that the binary content of the current file is equivalent to the given . /// @@ -99,7 +144,7 @@ public AndConstraint IsNotReadOnly( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert that the file is not read-only if the FileInfo is null") + "You can't assert that the file is not read-only if it is null") .Then .Given(() => Subject!) .ForCondition(fileInfo => !fileInfo.IsReadOnly) @@ -121,7 +166,7 @@ public AndConstraint IsReadOnly( .BecauseOf(because, becauseArgs) .ForCondition(Subject != null) .FailWith( - "You can't assert that the file is read-only if the FileInfo is null") + "You can't assert that the file is read-only if it is null") .Then .Given(() => Subject!) .ForCondition(fileInfo => fileInfo.IsReadOnly) diff --git a/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs b/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs index 61dd121..f0ead05 100644 --- a/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs +++ b/Source/Testably.Abstractions.FluentAssertions/FileInfoAssertions.cs @@ -1,3 +1,4 @@ +using System.IO; using System.Text; namespace Testably.Abstractions.FluentAssertions; @@ -26,6 +27,16 @@ public AndConstraint BeReadOnly( return new AndConstraint(this); } + /// + /// Asserts that the current file has the given . + /// + public AndConstraint HaveAttribute( + FileAttributes attribute, string because = "", params object[] becauseArgs) + { + new FileAssertions(Subject).HasAttribute(attribute, because, becauseArgs); + return new AndConstraint(this); + } + /// /// Asserts that the binary content of the current file is equivalent to the given . /// @@ -66,4 +77,14 @@ public AndConstraint NotBeReadOnly( new FileAssertions(Subject).IsNotReadOnly(because, becauseArgs); return new AndConstraint(this); } + + /// + /// Asserts that the current file does not have the given . + /// + public AndConstraint NotHaveAttribute( + FileAttributes attribute, string because = "", params object[] becauseArgs) + { + new FileAssertions(Subject).DoesNotHaveAttribute(attribute, because, becauseArgs); + return new AndConstraint(this); + } } diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileAssertionsTests.cs index 52171f2..d505b37 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileAssertionsTests.cs @@ -1,6 +1,7 @@ using AutoFixture.Xunit2; using FluentAssertions; using System; +using System.IO; using System.Linq; using System.Text; using Testably.Abstractions.Testing; @@ -11,6 +12,79 @@ namespace Testably.Abstractions.FluentAssertions.Tests; public class FileAssertionsTests { + [Theory] + [AutoData] + public void DoesNotHaveAttribute_WithAttribute_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + Exception? exception = Record.Exception(() => + { + sut.DoesNotHaveAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected file \"{fileDescription.Name}\" not to have attribute {FileAttributes.ReadOnly} {because}, but it did."); + } + + [Theory] + [AutoData] + public void DoesNotHaveAttribute_WithoutAttribute_ShouldNotThrow( + FileDescription fileDescription) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + sut.DoesNotHaveAttribute(FileAttributes.ReadOnly); + } + + [Theory] + [AutoData] + public void HasAttribute_WithAttribute_ShouldNotThrow(FileDescription fileDescription) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + sut.HasAttribute(FileAttributes.ReadOnly); + } + + [Theory] + [AutoData] + public void HasAttribute_WithoutAttribute_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + FileAssertions? sut = fileSystem.Should().HaveFile(fileDescription.Name).Which; + + Exception? exception = Record.Exception(() => + { + sut.HasAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected file \"{fileDescription.Name}\" to have attribute {FileAttributes.ReadOnly} {because}, but it did not."); + } + [Theory] [AutoData] public void HasContent_Bytes_FullContent_ShouldNotThrow( diff --git a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs index a9ccf24..4989ca1 100644 --- a/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs +++ b/Tests/Testably.Abstractions.FluentAssertions.Tests/FileInfoAssertionsTests.cs @@ -1,6 +1,7 @@ using AutoFixture.Xunit2; using FluentAssertions; using System; +using System.IO; using System.IO.Abstractions; using System.Linq; using System.Text; @@ -64,6 +65,58 @@ public void BeReadOnly_WithWritableFile_ShouldThrow( $"Expected file \"{fileDescription.Name}\" to be read-only {because}, but it was not."); } + [Theory] + [AutoData] + public void HaveAttribute_Null_ShouldThrow(string because) + { + IFileInfo? sut = null; + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain("null"); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void HaveAttribute_WithAttribute_ShouldNotThrow(FileDescription fileDescription) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + IFileInfo sut = fileSystem.FileInfo.New(fileDescription.Name); + + sut.Should().HaveAttribute(FileAttributes.ReadOnly); + } + + [Theory] + [AutoData] + public void HaveAttribute_WithoutAttribute_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + IFileInfo sut = fileSystem.FileInfo.New(fileDescription.Name); + + Exception? exception = Record.Exception(() => + { + sut.Should().HaveAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected file \"{fileDescription.Name}\" to have attribute {FileAttributes.ReadOnly} {because}, but it did not."); + } + [Theory] [AutoData] public void HaveContent_Bytes_FullContent_ShouldNotThrow( @@ -320,4 +373,56 @@ public void NotBeReadOnly_WithWritableFile_ShouldNotThrow(FileDescription fileDe sut.Should().NotBeReadOnly(); } + + [Theory] + [AutoData] + public void NotHaveAttribute_Null_ShouldThrow(string because) + { + IFileInfo? sut = null; + + Exception? exception = Record.Exception(() => + { + sut.Should().NotHaveAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should().Contain("null"); + exception.Message.Should().NotContain(because); + } + + [Theory] + [AutoData] + public void NotHaveAttribute_WithAttribute_ShouldThrow( + FileDescription fileDescription, + string because) + { + fileDescription.IsReadOnly = true; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + IFileInfo sut = fileSystem.FileInfo.New(fileDescription.Name); + + Exception? exception = Record.Exception(() => + { + sut.Should().NotHaveAttribute(FileAttributes.ReadOnly, because); + }); + + exception.Should().NotBeNull(); + exception!.Message.Should() + .Be( + $"Expected file \"{fileDescription.Name}\" not to have attribute {FileAttributes.ReadOnly} {because}, but it did."); + } + + [Theory] + [AutoData] + public void NotHaveAttribute_WithoutAttribute_ShouldNotThrow(FileDescription fileDescription) + { + fileDescription.IsReadOnly = false; + MockFileSystem fileSystem = new(); + fileSystem.Initialize() + .With(fileDescription); + IFileInfo sut = fileSystem.FileInfo.New(fileDescription.Name); + + sut.Should().NotHaveAttribute(FileAttributes.ReadOnly); + } }