Skip to content

Commit fb6937a

Browse files
authored
feat: add DoesNotHaveAttribute method for IFileInfo extensions (#149)
Adds a new negative assertion for `IFileInfo` attribute checks in **aweXpect.Testably**, along with corresponding unit tests and API surface snapshots to ensure the extension is publicly exposed across target frameworks. **Changes:** - Introduces `DoesNotHaveAttribute(this IThat<IFileInfo>, FileAttributes unexpected)` on `FileInfoExtensions`. - Adds unit tests covering success/failure cases (including non-existent file behavior). - Updates API approval snapshot files for netstandard2.0, net8.0, and net10.0.
1 parent 3b4a82d commit fb6937a

6 files changed

Lines changed: 132 additions & 1 deletion

File tree

Source/aweXpect.Testably/FileInfoExtensions.HasAttribute.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.IO;
23
using System.IO.Abstractions;
34
using System.Text;
@@ -15,10 +16,37 @@ public static partial class FileInfoExtensions
1516
/// </summary>
1617
public static AndOrResult<IFileInfo, IThat<IFileInfo>> HasAttribute(this IThat<IFileInfo> source,
1718
FileAttributes expected)
18-
=> new(
19+
{
20+
if (expected == default)
21+
{
22+
throw new ArgumentException(
23+
"The expected file attributes must include at least one flag.", nameof(expected));
24+
}
25+
26+
return new AndOrResult<IFileInfo, IThat<IFileInfo>>(
1927
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
2028
=> new HasAttributeConstraint(it, grammars, expected)),
2129
source);
30+
}
31+
32+
/// <summary>
33+
/// Verifies that the <see cref="IFileInfo" /> does not have the <paramref name="unexpected" />
34+
/// <see cref="FileAttributes" />.
35+
/// </summary>
36+
public static AndOrResult<IFileInfo, IThat<IFileInfo>> DoesNotHaveAttribute(this IThat<IFileInfo> source,
37+
FileAttributes unexpected)
38+
{
39+
if (unexpected == default)
40+
{
41+
throw new ArgumentException(
42+
"The unexpected file attributes must include at least one flag.", nameof(unexpected));
43+
}
44+
45+
return new AndOrResult<IFileInfo, IThat<IFileInfo>>(
46+
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
47+
=> new HasAttributeConstraint(it, grammars, unexpected).Invert()),
48+
source);
49+
}
2250

2351
private sealed class HasAttributeConstraint(string it, ExpectationGrammars grammars, FileAttributes expected)
2452
: ConstraintResult.WithValue<IFileInfo>(grammars),

Tests/aweXpect.Testably.Api.Tests/Expected/aweXpect.Testably_net10.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace aweXpect.Testably
4848
public static class FileInfoExtensions
4949
{
5050
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotExist(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
51+
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotHaveAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes unexpected) { }
5152
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> Exists(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
5253
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> HasAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes expected) { }
5354
public static aweXpect.Testably.Results.FileInfoContentResult HasContent(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }

Tests/aweXpect.Testably.Api.Tests/Expected/aweXpect.Testably_net8.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace aweXpect.Testably
4343
public static class FileInfoExtensions
4444
{
4545
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotExist(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
46+
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotHaveAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes unexpected) { }
4647
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> Exists(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
4748
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> HasAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes expected) { }
4849
public static aweXpect.Testably.Results.FileInfoContentResult HasContent(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }

Tests/aweXpect.Testably.Api.Tests/Expected/aweXpect.Testably_netstandard2.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace aweXpect.Testably
4343
public static class FileInfoExtensions
4444
{
4545
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotExist(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
46+
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> DoesNotHaveAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes unexpected) { }
4647
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> Exists(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
4748
public static aweXpect.Results.AndOrResult<System.IO.Abstractions.IFileInfo, aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo>> HasAttribute(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source, System.IO.FileAttributes expected) { }
4849
public static aweXpect.Testably.Results.FileInfoContentResult HasContent(this aweXpect.Core.IThat<System.IO.Abstractions.IFileInfo> source) { }
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.IO;
2+
using System.IO.Abstractions;
3+
using Testably.Abstractions.Testing;
4+
5+
namespace aweXpect.Testably.Tests;
6+
7+
public sealed partial class FileInfo
8+
{
9+
public sealed class DoesNotHaveAttribute
10+
{
11+
public sealed class Tests
12+
{
13+
[Fact]
14+
public async Task WhenAttributeIsAbsent_ShouldSucceed()
15+
{
16+
MockFileSystem fileSystem = new();
17+
string path = "foo.txt";
18+
fileSystem.File.WriteAllText(path, "");
19+
IFileInfo fileInfo = fileSystem.FileInfo.New(path);
20+
21+
async Task Act()
22+
{
23+
await That(fileInfo).DoesNotHaveAttribute(FileAttributes.ReadOnly);
24+
}
25+
26+
await That(Act).DoesNotThrow();
27+
}
28+
29+
[Fact]
30+
public async Task WhenAttributeIsPresent_ShouldFail()
31+
{
32+
MockFileSystem fileSystem = new();
33+
string path = "foo.txt";
34+
fileSystem.File.WriteAllText(path, "");
35+
fileSystem.File.SetAttributes(path, FileAttributes.ReadOnly);
36+
IFileInfo fileInfo = fileSystem.FileInfo.New(path);
37+
38+
async Task Act()
39+
{
40+
await That(fileInfo).DoesNotHaveAttribute(FileAttributes.ReadOnly);
41+
}
42+
43+
await That(Act).ThrowsException()
44+
.WithMessage("""
45+
Expected that fileInfo
46+
does not have attribute ReadOnly,
47+
but it did
48+
""");
49+
}
50+
51+
[Fact]
52+
public async Task WhenFileDoesNotExist_ShouldSucceed()
53+
{
54+
MockFileSystem fileSystem = new();
55+
IFileInfo fileInfo = fileSystem.FileInfo.New("foo.txt");
56+
57+
async Task Act()
58+
{
59+
await That(fileInfo).DoesNotHaveAttribute(FileAttributes.ReadOnly);
60+
}
61+
62+
await That(Act).DoesNotThrow();
63+
}
64+
65+
[Fact]
66+
public async Task WhenUnexpectedIsDefault_ShouldThrowArgumentException()
67+
{
68+
MockFileSystem fileSystem = new();
69+
string path = "foo.txt";
70+
fileSystem.File.WriteAllText(path, "");
71+
IFileInfo fileInfo = fileSystem.FileInfo.New(path);
72+
73+
async Task Act()
74+
{
75+
await That(fileInfo).DoesNotHaveAttribute(default);
76+
}
77+
78+
await That(Act).Throws<ArgumentException>()
79+
.WithParamName("unexpected");
80+
}
81+
}
82+
}
83+
}

Tests/aweXpect.Testably.Tests/FileInfo.HasAttribute.Tests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ async Task Act()
4747

4848
await That(Act).DoesNotThrow();
4949
}
50+
51+
[Fact]
52+
public async Task WhenExpectedIsDefault_ShouldThrowArgumentException()
53+
{
54+
MockFileSystem fileSystem = new();
55+
string path = "foo.txt";
56+
fileSystem.File.WriteAllText(path, "");
57+
IFileInfo fileInfo = fileSystem.FileInfo.New(path);
58+
59+
async Task Act()
60+
{
61+
await That(fileInfo).HasAttribute(default);
62+
}
63+
64+
await That(Act).Throws<ArgumentException>()
65+
.WithParamName("expected");
66+
}
5067
}
5168
}
5269
}

0 commit comments

Comments
 (0)