diff --git a/Directory.Packages.props b/Directory.Packages.props
index def38fa7..99f3a5b2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -46,6 +46,6 @@
-
+
diff --git a/src/RustPlusBot.Features.Alarms/Hosting/AlarmsHostedService.cs b/src/RustPlusBot.Features.Alarms/Hosting/AlarmsHostedService.cs
index 54d822d1..de148d91 100644
--- a/src/RustPlusBot.Features.Alarms/Hosting/AlarmsHostedService.cs
+++ b/src/RustPlusBot.Features.Alarms/Hosting/AlarmsHostedService.cs
@@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _observedLoop, _wipedLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs b/src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs
index 451c2311..5599a905 100644
--- a/src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs
+++ b/src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs
@@ -173,7 +173,9 @@ public async Task EnsureConnectionAsync(ulong guildId, Guid serverId, Cancellati
///
public async Task StopAsync(ulong guildId, Guid serverId)
{
- await _gate.WaitAsync().ConfigureAwait(false);
+ // CancellationToken.None, not _shutdown.Token: teardown must still acquire the gate after
+ // StopAllAsync has already cancelled _shutdown, otherwise the connection is never stopped.
+ await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
await StopConnectionAsync((guildId, serverId)).ConfigureAwait(false);
@@ -188,7 +190,8 @@ public async Task StopAsync(ulong guildId, Guid serverId)
public async Task StopAllAsync()
{
await _shutdown.CancelAsync().ConfigureAwait(false);
- await _gate.WaitAsync().ConfigureAwait(false);
+ // CancellationToken.None: _shutdown was just cancelled, so waiting on it would abandon shutdown.
+ await _gate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
foreach (var key in _connections.Keys.ToList())
diff --git a/src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs b/src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs
index 22aa08b8..f2a892e2 100644
--- a/src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs
+++ b/src/RustPlusBot.Features.Events/Hosting/EventsHostedService.cs
@@ -60,12 +60,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_relayLoop, _rigLoop, _tickLoop, _disconnectLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs b/src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs
index ac3fb976..0e6401c0 100644
--- a/src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs
+++ b/src/RustPlusBot.Features.Map/Hosting/InfoMapHostedService.cs
@@ -58,12 +58,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_statusLoop, _tickLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs b/src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs
index 9a113c71..4e7f556b 100644
--- a/src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs
+++ b/src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs
@@ -79,12 +79,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_markerLoop, _settingsLoop, _statusLoop, _tickLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Pairing/Supervisor/PairingSupervisor.cs b/src/RustPlusBot.Features.Pairing/Supervisor/PairingSupervisor.cs
index 5b393275..8641e938 100644
--- a/src/RustPlusBot.Features.Pairing/Supervisor/PairingSupervisor.cs
+++ b/src/RustPlusBot.Features.Pairing/Supervisor/PairingSupervisor.cs
@@ -249,10 +249,11 @@ private async Task MarkExpiredAsync((ulong Guild, ulong Owner) key, Guid registr
await using (expireScope.ConfigureAwait(false))
{
var store = expireScope.ServiceProvider.GetRequiredService();
- await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired).ConfigureAwait(false);
+ await store.SetStatusAsync(registrationId, FcmRegistrationStatus.Expired, _shutdown.Token)
+ .ConfigureAwait(false);
}
- await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner).ConfigureAwait(false);
+ await notifier.NotifyCredentialsExpiredAsync(key.Guild, key.Owner, _shutdown.Token).ConfigureAwait(false);
}
private sealed class Handle : IAsyncDisposable
diff --git a/src/RustPlusBot.Features.StorageMonitors/Hosting/StorageMonitorsHostedService.cs b/src/RustPlusBot.Features.StorageMonitors/Hosting/StorageMonitorsHostedService.cs
index 546349e8..5e6345fc 100644
--- a/src/RustPlusBot.Features.StorageMonitors/Hosting/StorageMonitorsHostedService.cs
+++ b/src/RustPlusBot.Features.StorageMonitors/Hosting/StorageMonitorsHostedService.cs
@@ -47,12 +47,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _triggeredLoop, _statusLoop, _reachabilityLoop, _wipedLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Switches/Hosting/SwitchesHostedService.cs b/src/RustPlusBot.Features.Switches/Hosting/SwitchesHostedService.cs
index fd0537c4..61aae4cc 100644
--- a/src/RustPlusBot.Features.Switches/Hosting/SwitchesHostedService.cs
+++ b/src/RustPlusBot.Features.Switches/Hosting/SwitchesHostedService.cs
@@ -49,12 +49,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_pairedLoop, _stateLoop, _statusLoop, _deviceLoop, _reachabilityLoop, _wipedLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs b/src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs
index c5d217b9..ca9bfada 100644
--- a/src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs
+++ b/src/RustPlusBot.Features.Wipes/Hosting/WipesHostedService.cs
@@ -39,12 +39,12 @@ public async Task StopAsync(CancellationToken cancellationToken)
foreach (var loop in new[]
{
_statusLoop, _wipedLoop
- }.Where(t => t is not null))
+ }.OfType())
{
try
{
#pragma warning disable VSTHRD003 // Our own loop tasks, joined on stop.
- await loop!.ConfigureAwait(false);
+ await loop.ConfigureAwait(false);
#pragma warning restore VSTHRD003
}
catch (OperationCanceledException)
diff --git a/src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs b/src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs
index 03ae039d..5ed88740 100644
--- a/src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs
+++ b/src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs
@@ -97,7 +97,7 @@ private Task OnReadyAsync()
// Healing sweeps every provisioned guild's channels over REST; doing it inline blocks the
// gateway task and stalls event dispatch, so offload it. Failures must be caught here —
// nothing awaits this.
- _ = Task.Run(HealProvisionedGuildsAsync);
+ _ = Task.Run(HealProvisionedGuildsAsync, _cts.Token);
return Task.CompletedTask;
}
@@ -110,12 +110,16 @@ private async Task HealProvisionedGuildsAsync()
{
var store = scope.ServiceProvider.GetRequiredService();
var reconciler = scope.ServiceProvider.GetRequiredService();
- foreach (var guildId in await store.GetProvisionedGuildIdsAsync().ConfigureAwait(false))
+ foreach (var guildId in await store.GetProvisionedGuildIdsAsync(_cts.Token).ConfigureAwait(false))
{
- await reconciler.HealGuildAsync(guildId).ConfigureAwait(false);
+ await reconciler.HealGuildAsync(guildId, _cts.Token).ConfigureAwait(false);
}
}
}
+ catch (OperationCanceledException)
+ {
+ // Shutting down.
+ }
catch (Exception ex) // Broad catch is intentional: a faulting startup heal must not crash the host.
{
logger.LogError(ex, "Startup self-heal failed.");
@@ -135,9 +139,13 @@ private async Task OnChannelDestroyedAsync(SocketChannel channel)
await using (scope.ConfigureAwait(false))
{
var reconciler = scope.ServiceProvider.GetRequiredService();
- await reconciler.HealGuildAsync(guildChannel.Guild.Id).ConfigureAwait(false);
+ await reconciler.HealGuildAsync(guildChannel.Guild.Id, _cts.Token).ConfigureAwait(false);
}
}
+ catch (OperationCanceledException)
+ {
+ // Shutting down.
+ }
catch (Exception ex) // Broad catch is intentional: a faulting self-heal must not crash the host.
{
logger.LogError(ex, "Self-heal failed for guild {GuildId}.", guildChannel.Guild.Id);
diff --git a/tests/RustPlusBot.Features.Connections.Tests/ClanSupervisorTests.cs b/tests/RustPlusBot.Features.Connections.Tests/ClanSupervisorTests.cs
index 4b9b92a1..925ab2f1 100644
--- a/tests/RustPlusBot.Features.Connections.Tests/ClanSupervisorTests.cs
+++ b/tests/RustPlusBot.Features.Connections.Tests/ClanSupervisorTests.cs
@@ -177,9 +177,9 @@ public async Task Publishes_a_clan_state_event_on_connect()
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
- Assert.Equal(ClanProbeStatus.HasClan, evt!.Status);
+ Assert.Equal(ClanProbeStatus.HasClan, evt.Status);
Assert.NotNull(evt.Snapshot);
- Assert.Equal(1L, evt.Snapshot!.ClanId);
+ Assert.Equal(1L, evt.Snapshot.ClanId);
await h.Supervisor.StopAllAsync();
await cts.CancelAsync();
@@ -220,7 +220,7 @@ public async Task Publishes_NoClan_on_connect_when_the_player_has_no_clan()
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
- Assert.Equal(ClanProbeStatus.NoClan, evt!.Status);
+ Assert.Equal(ClanProbeStatus.NoClan, evt.Status);
Assert.Null(evt.Snapshot);
await h.Supervisor.StopAllAsync();
@@ -264,7 +264,7 @@ public async Task Publishes_a_clan_state_event_when_the_socket_reports_a_change(
// connect-time probe call), so RaiseClanChanged below is guaranteed to reach the handler.
await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);
Assert.NotNull(source.LastConnection);
- source.LastConnection!.RaiseClanChanged(ClanProbeResult.NoClan);
+ source.LastConnection.RaiseClanChanged(ClanProbeResult.NoClan);
await WaitUntilAsync(() => captured.Count >= 2, cts.Token);
@@ -321,7 +321,7 @@ public async Task Publishes_clan_messages_and_flags_the_active_player()
await WaitUntilAsync(() => !stateCaptured.IsEmpty, cts.Token);
Assert.NotNull(source.LastConnection);
- source.LastConnection!.RaiseClanMessage(new ClanChatLine(100UL, "Active", "hi", DateTimeOffset.UnixEpoch));
+ source.LastConnection.RaiseClanMessage(new ClanChatLine(100UL, "Active", "hi", DateTimeOffset.UnixEpoch));
source.LastConnection.RaiseClanMessage(new ClanChatLine(999UL, "Other", "yo", DateTimeOffset.UnixEpoch));
await WaitUntilAsync(() => captured.Count >= 2, cts.Token);
@@ -394,7 +394,7 @@ public async Task Stops_publishing_clan_events_after_the_socket_closes()
await WaitUntilAsync(() => !stateCaptured.IsEmpty, cts.Token);
Assert.NotNull(source.LastConnection);
- var closedConnection = source.LastConnection!;
+ var closedConnection = source.LastConnection;
// Stop the connection loop — this joins RunConnectedAsync's finally block, which unsubscribes
// the clan handlers, before StopAsync returns.
@@ -453,7 +453,7 @@ public async Task Routes_a_team_send_to_team_chat_and_a_clan_send_to_clan_chat()
Assert.Equal(ChatSendResult.Sent, clanResult);
Assert.NotNull(source.LastConnection);
- var connection = source.LastConnection!;
+ var connection = source.LastConnection;
// Assert both destinations independently, so swapping the routing fails on both sides.
Assert.Equal("[Alice] team line", Assert.Single(connection.SentMessages));
diff --git a/tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs b/tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs
index 859eb728..c4dd933b 100644
--- a/tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs
+++ b/tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs
@@ -159,7 +159,7 @@ public async Task Connect_Healthy_BecomesConnectedWithPlayerCount()
var state = await WaitForStateAsync(h.Provider, serverId, s => s.Status == ConnectionStatus.Connected);
Assert.NotNull(state);
- Assert.Equal(7, state!.PlayerCount);
+ Assert.Equal(7, state.PlayerCount);
}
[Fact]
@@ -432,7 +432,7 @@ public async Task First_marker_poll_is_a_silent_baseline()
Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
- Assert.Single(evt!.Added);
+ Assert.Single(evt.Added);
Assert.Equal(MarkerKind.PatrolHelicopter, evt.Added[0].Kind);
Assert.Empty(evt.Removed);
@@ -489,7 +489,7 @@ public async Task Marker_added_on_a_later_poll_publishes_changed_event()
Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
- Assert.Single(evt!.Added);
+ Assert.Single(evt.Added);
Assert.Equal(MarkerKind.CargoShip, evt.Added[0].Kind);
Assert.Empty(evt.Removed);
Assert.Equal(expectedDims, evt.Dimensions);
@@ -540,7 +540,7 @@ public async Task Marker_position_change_publishes_moved_bucket()
Assert.Single(captured);
Assert.True(captured.TryPeek(out var evt));
Assert.NotNull(evt);
- Assert.Empty(evt!.Added);
+ Assert.Empty(evt.Added);
Assert.Empty(evt.Removed);
var moved = Assert.Single(evt.Moved);
Assert.Equal(7UL, moved.Id);
@@ -602,7 +602,7 @@ public async Task Failed_marker_poll_retains_previous_snapshot()
await Task.Delay(TimeSpan.FromMilliseconds(60), cts.Token);
// Poll 4: recover — hold-last still returns [cargo], so snapshot is unchanged, no new event.
- source.LastConnection!.MarkersThrow = false;
+ source.LastConnection.MarkersThrow = false;
await Task.Delay(TimeSpan.FromMilliseconds(100), cts.Token);
// Still only one event total — the failed poll did not corrupt the snapshot.
@@ -659,7 +659,7 @@ public async Task Ch47_entering_rig_radius_publishes_activated_once_per_visit()
Assert.Single(rigEvents);
Assert.True(rigEvents.TryPeek(out var evt));
Assert.NotNull(evt);
- Assert.Equal(RigKind.Small, evt!.Rig);
+ Assert.Equal(RigKind.Small, evt.Rig);
Assert.Equal(RigEventKind.Activated, evt.Kind);
Assert.Equal(1000f, evt.X);
Assert.Equal(1000f, evt.Y);
@@ -784,7 +784,7 @@ public async Task TeamChanged_push_publishes_player_state_transition()
await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);
Assert.True(captured.TryDequeue(out var evt));
- var transition = Assert.Single(evt!.Transitions);
+ var transition = Assert.Single(evt.Transitions);
Assert.Equal(PlayerTransitionKind.Disconnect, transition.Kind);
Assert.Equal(100UL, transition.SteamId);
@@ -838,7 +838,7 @@ online with
await WaitUntilAsync(() => !captured.IsEmpty, cts.Token);
Assert.True(captured.TryDequeue(out var evt));
- Assert.Contains(evt!.Transitions, t => t.Kind == PlayerTransitionKind.Disconnect && t.SteamId == 100UL);
+ Assert.Contains(evt.Transitions, t => t.Kind == PlayerTransitionKind.Disconnect && t.SteamId == 100UL);
await h.Supervisor.StopAllAsync();
await cts.CancelAsync();
diff --git a/tests/RustPlusBot.Features.Events.Tests/Messages/ServerEventsMessageRendererTests.cs b/tests/RustPlusBot.Features.Events.Tests/Messages/ServerEventsMessageRendererTests.cs
index 93be0d48..0b9924de 100644
--- a/tests/RustPlusBot.Features.Events.Tests/Messages/ServerEventsMessageRendererTests.cs
+++ b/tests/RustPlusBot.Features.Events.Tests/Messages/ServerEventsMessageRendererTests.cs
@@ -47,7 +47,7 @@ public async Task Renders_all_five_rows()
var payload = await Build(events, rigs).RenderAsync(new MessageRenderContext(1, ServerId, "en"), default);
Assert.NotNull(payload.Embed);
- Assert.Equal(5, payload.Embed!.Fields.Length);
+ Assert.Equal(5, payload.Embed.Fields.Length);
}
[Fact]
diff --git a/tests/RustPlusBot.Features.ItemData.Tests/EmbeddedItemDatabaseTests.cs b/tests/RustPlusBot.Features.ItemData.Tests/EmbeddedItemDatabaseTests.cs
index 92325263..654845ec 100644
--- a/tests/RustPlusBot.Features.ItemData.Tests/EmbeddedItemDatabaseTests.cs
+++ b/tests/RustPlusBot.Features.ItemData.Tests/EmbeddedItemDatabaseTests.cs
@@ -13,7 +13,7 @@ public void GetById_ReturnsKnownItem()
{
var ak = _db.GetById(1545779598);
Assert.NotNull(ak);
- Assert.Equal("Assault Rifle", ak!.Name);
+ Assert.Equal("Assault Rifle", ak.Name);
}
[Fact]
@@ -61,8 +61,8 @@ public void GetById_StoneBarricade_HasDecay()
{
var rec = _db.GetById(15388698);
Assert.NotNull(rec);
- Assert.NotNull(rec!.Decay);
- Assert.Equal(900, rec.Decay!.Seconds);
+ Assert.NotNull(rec.Decay);
+ Assert.Equal(900, rec.Decay.Seconds);
Assert.Equal(100, rec.Decay.Hp);
}
@@ -71,8 +71,8 @@ public void GetById_WoodenDoor_HasUpkeep()
{
var rec = _db.GetById(1729120840);
Assert.NotNull(rec);
- Assert.NotNull(rec!.Upkeep);
- var entry = Assert.Single(rec.Upkeep!.Entries);
+ Assert.NotNull(rec.Upkeep);
+ var entry = Assert.Single(rec.Upkeep.Entries);
Assert.Equal(-151838493, entry.ItemId);
Assert.Equal(30, entry.QuantityMin);
Assert.Equal(100, entry.QuantityMax);
diff --git a/tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs b/tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs
index fe84a1ee..19923e13 100644
--- a/tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs
@@ -99,7 +99,7 @@ public async Task Renders_a_png_when_base_map_available()
var result = await composer.ComposeAsync(Guild, Server, CancellationToken.None);
Assert.NotNull(result);
- using var image = Image.Load(result!.Png);
+ using var image = Image.Load(result.Png);
Assert.Equal(MapRenderer.OutputSize, image.Width);
}
@@ -114,7 +114,7 @@ [new TrailPoint(2000f, 2000f)], null)),
var result = await composer.ComposeAsync(Guild, Server, CancellationToken.None);
Assert.NotNull(result);
- using var image = Image.Load(result!.Png);
+ using var image = Image.Load(result.Png);
Assert.Equal(MapRenderer.OutputSize, image.Width);
}
@@ -158,10 +158,10 @@ public async Task Tunnel_tokens_render_under_the_tunnels_layer_not_the_monuments
Assert.NotNull(monResult);
Assert.NotNull(tunResult);
// Monuments layer must NOT draw the tunnel token -> identical to the bare base render.
- Assert.True(monResult!.Png.SequenceEqual(baseResult!.Png),
+ Assert.True(monResult.Png.SequenceEqual(baseResult.Png),
"tunnel token must be excluded from the Monuments pass");
// Tunnels layer MUST draw the tunnel token -> differs from the bare base render.
- Assert.False(tunResult!.Png.SequenceEqual(baseResult!.Png), "tunnel token must render under the Tunnels layer");
+ Assert.False(tunResult.Png.SequenceEqual(baseResult.Png), "tunnel token must render under the Tunnels layer");
}
private static async Task ComposeTunnelScenarioAsync(bool monuments, bool tunnels)
@@ -219,7 +219,7 @@ public async Task Renders_the_grid_even_with_no_markers()
Assert.NotNull(resultOn);
Assert.NotNull(resultOff);
// The grid layer must have painted at least one pixel differently.
- Assert.False(resultOn!.Png.SequenceEqual(resultOff!.Png), "Grid-on and grid-off renders must differ.");
+ Assert.False(resultOn.Png.SequenceEqual(resultOff.Png), "Grid-on and grid-off renders must differ.");
}
[Fact]
@@ -247,7 +247,7 @@ public async Task ComposeAsync_forwards_vendor_marker_history_into_rendered_trai
Assert.NotNull(resultNoTrail);
// MapRenderer.DrawTrails only paints when Trail.Count >= 2, so a 2-point History must render
// differently from a 1-point History — proving the history ring made it through to the trail.
- Assert.False(resultWithTrail!.Png.SequenceEqual(resultNoTrail!.Png),
+ Assert.False(resultWithTrail.Png.SequenceEqual(resultNoTrail.Png),
"Vendor trail with 2-point history must render differently than a 1-point history.");
}
@@ -267,9 +267,9 @@ public async Task ComposeAsync_builds_a_legend_entry_per_player()
var result = await composer.ComposeAsync(Guild, Server, CancellationToken.None);
Assert.NotNull(result);
- Assert.NotNull(result!.Legend);
+ Assert.NotNull(result.Legend);
// Ordered by SteamId: Ada (10) first, Bob (20) second.
- Assert.Collection(result.Legend!.Entries,
+ Assert.Collection(result.Legend.Entries,
e =>
{
Assert.Equal("Ada", e.Name);
diff --git a/tests/RustPlusBot.Features.Map.Tests/MapIconsTests.cs b/tests/RustPlusBot.Features.Map.Tests/MapIconsTests.cs
index 0727162e..69a3e7f5 100644
--- a/tests/RustPlusBot.Features.Map.Tests/MapIconsTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/MapIconsTests.cs
@@ -18,7 +18,7 @@ public void Sized_marker_icon_fits_the_requested_box()
var icon = MapIcons.Marker(MarkerKind.CargoShip, 40);
Assert.NotNull(icon);
- Assert.True(icon!.Width <= 40 && icon.Height <= 40);
+ Assert.True(icon.Width <= 40 && icon.Height <= 40);
Assert.True(icon.Width == 40 || icon.Height == 40); // aspect-preserving fit, longest edge = size
}
diff --git a/tests/RustPlusBot.Features.Map.Tests/MapLegendEmbedTests.cs b/tests/RustPlusBot.Features.Map.Tests/MapLegendEmbedTests.cs
index e1600058..6b6ed6b1 100644
--- a/tests/RustPlusBot.Features.Map.Tests/MapLegendEmbedTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/MapLegendEmbedTests.cs
@@ -24,7 +24,7 @@ public void Build_lists_one_line_per_player()
var embed = MapLegendEmbed.Build(legend);
Assert.NotNull(embed);
- Assert.Contains("Ada", embed!.Description, StringComparison.Ordinal);
+ Assert.Contains("Ada", embed.Description, StringComparison.Ordinal);
Assert.Contains("online", embed.Description, StringComparison.Ordinal);
Assert.Contains("Bob", embed.Description, StringComparison.Ordinal);
Assert.Contains("🟦", embed.Description, StringComparison.Ordinal);
diff --git a/tests/RustPlusBot.Features.Map.Tests/MonumentIconSourceTests.cs b/tests/RustPlusBot.Features.Map.Tests/MonumentIconSourceTests.cs
index 81ee8c17..bc39c555 100644
--- a/tests/RustPlusBot.Features.Map.Tests/MonumentIconSourceTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/MonumentIconSourceTests.cs
@@ -20,7 +20,7 @@ public void Monument_rasterizes_known_token_at_requested_size()
var icon = CreateSource().Monument("launchsite", 30);
Assert.NotNull(icon);
- Assert.Equal(30, icon!.Width);
+ Assert.Equal(30, icon.Width);
Assert.Equal(30, icon.Height);
}
@@ -77,7 +77,7 @@ public void Every_known_token_rasterizes()
Assert.True(icon is not null, $"{token} produced no icon");
var hasVisiblePixel = false;
- for (var y = 0; y < icon!.Height && !hasVisiblePixel; y++)
+ for (var y = 0; y < icon.Height && !hasVisiblePixel; y++)
{
for (var x = 0; x < icon.Width; x++)
{
@@ -137,7 +137,7 @@ private static MonumentAsset CreateBogusAsset()
var ctor = typeof(MonumentAsset).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic, [typeof(MonumentType), typeof(string)]);
Assert.NotNull(ctor);
- return (MonumentAsset)ctor!.Invoke([MonumentType.LaunchSite, "Definitely_Not_An_Asset"]);
+ return (MonumentAsset)ctor.Invoke([MonumentType.LaunchSite, "Definitely_Not_An_Asset"]);
}
private sealed class RecordingLogger : ILogger
diff --git a/tests/RustPlusBot.Features.Map.Tests/RustMaps/RustMapsMapCoordinatorTests.cs b/tests/RustPlusBot.Features.Map.Tests/RustMaps/RustMapsMapCoordinatorTests.cs
index d4b6237d..4fe6fda0 100644
--- a/tests/RustPlusBot.Features.Map.Tests/RustMaps/RustMapsMapCoordinatorTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/RustMaps/RustMapsMapCoordinatorTests.cs
@@ -65,7 +65,7 @@ public void GetReady_returns_the_view_when_ready()
var view = c.GetReady(Key.Size, Key.Seed);
Assert.NotNull(view);
- Assert.Equal("https://img/x.png", view!.ImageUrl);
+ Assert.Equal("https://img/x.png", view.ImageUrl);
Assert.Equal("https://rustmaps/x", view.RustMapsPageUrl);
}
diff --git a/tests/RustPlusBot.Features.Map.Tests/RustPlusBaseMapSourceTests.cs b/tests/RustPlusBot.Features.Map.Tests/RustPlusBaseMapSourceTests.cs
index 1a29a39d..0b7c5176 100644
--- a/tests/RustPlusBot.Features.Map.Tests/RustPlusBaseMapSourceTests.cs
+++ b/tests/RustPlusBot.Features.Map.Tests/RustPlusBaseMapSourceTests.cs
@@ -21,7 +21,7 @@ public async Task GetAsync_returns_image_when_bytes_and_dimensions_present()
var result = await source.GetAsync(Guild, Server, CancellationToken.None);
Assert.NotNull(result);
- Assert.Equal((byte[])[1, 2, 3], result!.Bytes);
+ Assert.Equal((byte[])[1, 2, 3], result.Bytes);
Assert.Equal(1000, result.PixelWidth);
Assert.Equal(1000, result.PixelHeight);
Assert.Equal(50, result.OceanMarginPx);
diff --git a/tests/RustPlusBot.Features.Pairing.Tests/PairingSupervisorTests.cs b/tests/RustPlusBot.Features.Pairing.Tests/PairingSupervisorTests.cs
index 50d61ef2..2540eaa2 100644
--- a/tests/RustPlusBot.Features.Pairing.Tests/PairingSupervisorTests.cs
+++ b/tests/RustPlusBot.Features.Pairing.Tests/PairingSupervisorTests.cs
@@ -134,7 +134,8 @@ public async Task ConnectedListener_DeliversNotificationWithoutFaulting()
await h.Supervisor.EnsureListenerAsync(10UL, 99UL);
var note = new PairingNotification(PairingKind.Server, "S", "1.2.3.4", 28015, 7UL, "tok");
- await h.Source.LastCallback!(note, CancellationToken.None);
+ Assert.NotNull(h.Source.LastCallback);
+ await h.Source.LastCallback(note, CancellationToken.None);
using var scope = h.Provider.CreateScope();
var reg = await scope.ServiceProvider.GetRequiredService().GetAsync(10UL, 99UL);
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Locating/TeamChatChannelLocatorTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Locating/TeamChatChannelLocatorTests.cs
index da96f408..72fce1a8 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Locating/TeamChatChannelLocatorTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Locating/TeamChatChannelLocatorTests.cs
@@ -85,7 +85,7 @@ public async Task ResolveAsync_maps_channel_to_guild_and_server()
var resolved = await locator.ResolveAsync(777UL, CancellationToken.None);
Assert.NotNull(resolved);
- Assert.Equal(10UL, resolved!.Value.GuildId);
+ Assert.Equal(10UL, resolved.Value.GuildId);
Assert.Equal(serverId, resolved.Value.ServerId);
}
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Messages/MapControlMessageRendererTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Messages/MapControlMessageRendererTests.cs
index 98cf0eeb..ae81bb98 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Messages/MapControlMessageRendererTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Messages/MapControlMessageRendererTests.cs
@@ -13,11 +13,15 @@ public sealed class MapControlMessageRendererTests
{
private static readonly ResxLocalizer Loc = new();
- private static List Buttons(MessagePayload payload) =>
- [
- .. payload.Components!.Components.OfType()
- .SelectMany(r => r.Components).OfType(),
- ];
+ private static List Buttons(MessagePayload payload)
+ {
+ Assert.NotNull(payload.Components);
+ return
+ [
+ .. payload.Components.Components.OfType()
+ .SelectMany(r => r.Components).OfType(),
+ ];
+ }
[Fact]
public async Task Renders_seven_toggle_buttons_reflecting_settings()
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs
index c8201ace..25ca3820 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs
@@ -45,7 +45,7 @@ public async Task Information_ShowsServerCount()
var payload = await renderer.RenderAsync(Global, default);
Assert.NotNull(payload.Embed);
- Assert.Contains("3", payload.Embed!.Description, StringComparison.Ordinal);
+ Assert.Contains("3", payload.Embed.Description, StringComparison.Ordinal);
}
[Fact]
@@ -58,7 +58,7 @@ public async Task Settings_HasLanguageSelectMenu()
var payload = await renderer.RenderAsync(Global, default);
Assert.NotNull(payload.Components);
- var selects = payload.Components!.Components.OfType().SelectMany(r => r.Components)
+ var selects = payload.Components.Components.OfType().SelectMany(r => r.Components)
.OfType();
Assert.Contains(selects, s => s.CustomId == "workspace:settings:culture");
}
@@ -299,7 +299,7 @@ public async Task Setup_HasConnectAccountButton()
var payload = await renderer.RenderAsync(Global, default);
Assert.NotNull(payload.Components);
- var buttons = payload.Components!.Components.OfType()
+ var buttons = payload.Components.Components.OfType()
.SelectMany(r => r.Components).OfType();
Assert.Contains(buttons, b => b.CustomId == "workspace:setup:connect");
}
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Messages/ServerInfoMapMessageRendererTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Messages/ServerInfoMapMessageRendererTests.cs
index 19fc7fdd..2583b0cf 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Messages/ServerInfoMapMessageRendererTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Messages/ServerInfoMapMessageRendererTests.cs
@@ -44,7 +44,7 @@ public async Task Ready_view_sets_the_embed_image_and_url()
var payload = await renderer.RenderAsync(new MessageRenderContext(1, serverId, "en"), default);
Assert.NotNull(payload.Embed);
- Assert.Equal("https://img/icons.png", payload.Embed!.Image!.Value.Url);
+ Assert.Equal("https://img/icons.png", payload.Embed.Image!.Value.Url);
Assert.Equal("https://rustmaps/x", payload.Embed.Url);
}
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerMessageTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerMessageTests.cs
index d1670bd1..967134d1 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerMessageTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerMessageTests.cs
@@ -70,7 +70,7 @@ public async Task NewEarlierMessageBehindLiveOne_DeletesAndRepostsInDeclarationO
var originalInfoMessage = await harness.Store.GetMessageAsync(1, serverId, "server.info");
Assert.NotNull(originalInfoMessage);
- var originalInfoMessageId = originalInfoMessage!.DiscordMessageId;
+ var originalInfoMessageId = originalInfoMessage.DiscordMessageId;
Assert.Equal(1, harness.Gateway.PostedMessages);
Assert.Empty(harness.Gateway.DeletedMessageIds);
@@ -101,7 +101,7 @@ public async Task NewEarlierMessageBehindLiveOne_DeletesAndRepostsInDeclarationO
var infoRecord = await harness.Store.GetMessageAsync(1, serverId, "server.info");
Assert.NotNull(mapRecord);
Assert.NotNull(infoRecord);
- Assert.NotEqual(originalInfoMessageId, infoRecord!.DiscordMessageId);
+ Assert.NotEqual(originalInfoMessageId, infoRecord.DiscordMessageId);
}
[Fact]
diff --git a/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerServerTests.cs b/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerServerTests.cs
index 47b4de0b..67d0b841 100644
--- a/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerServerTests.cs
+++ b/tests/RustPlusBot.Features.Workspace.Tests/Reconciler/WorkspaceReconcilerServerTests.cs
@@ -30,7 +30,7 @@ public async Task ReconcileServer_CreatesCategoryNamedAfterServer_ScopedToServer
Assert.Equal(ReconcileStatus.Provisioned, result.Status);
var category = await harness.Store.GetCategoryAsync(1, serverId);
Assert.NotNull(category);
- Assert.Equal(category!.DiscordCategoryId, await harness.Gateway.FindCategoryAsync(1, "Rustopia EU", default));
+ Assert.Equal(category.DiscordCategoryId, await harness.Gateway.FindCategoryAsync(1, "Rustopia EU", default));
var channels = await harness.Store.GetChannelsAsync(1, serverId);
Assert.Single(channels);
Assert.Equal(serverId, channels[0].RustServerId);
diff --git a/tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreTests.cs b/tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreTests.cs
index f533f34d..0d1f0a4d 100644
--- a/tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreTests.cs
+++ b/tests/RustPlusBot.Persistence.Tests/Workspace/WorkspaceStoreTests.cs
@@ -31,7 +31,7 @@ await store.SaveCategoryAsync(new ProvisionedCategory
var loaded = await store.GetCategoryAsync(1, null);
Assert.NotNull(loaded);
- Assert.Equal(20UL, loaded!.DiscordCategoryId);
+ Assert.Equal(20UL, loaded.DiscordCategoryId);
}
[Fact]
@@ -78,7 +78,7 @@ await store.SaveMessageAsync(new ProvisionedMessage
var loaded = await store.GetMessageAsync(1, null, "information.main");
Assert.NotNull(loaded);
- Assert.Equal(101UL, loaded!.DiscordMessageId);
+ Assert.Equal(101UL, loaded.DiscordMessageId);
Assert.Equal(DateTimeOffset.UnixEpoch, loaded.UpdatedAt);
}