Skip to content

Commit 9084ad1

Browse files
HandyS11claude
andcommitted
feat(storage): sweep embeds unreachable only on a drop from Connected
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ae24b34 commit 9084ad1

3 files changed

Lines changed: 38 additions & 38 deletions

File tree

src/RustPlusBot.Features.StorageMonitors/Relaying/StorageMonitorStateRelay.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using RustPlusBot.Abstractions.Connections;
33
using RustPlusBot.Abstractions.Events;
4-
using RustPlusBot.Domain.Connections;
54
using RustPlusBot.Domain.StorageMonitors;
65
using RustPlusBot.Features.StorageMonitors.Posting;
76
using RustPlusBot.Features.StorageMonitors.Rendering;
87
using RustPlusBot.Features.Workspace.Locating;
9-
using RustPlusBot.Persistence.Connections;
108
using RustPlusBot.Persistence.StorageMonitors;
119
using RustPlusBot.Persistence.Workspace;
1210

@@ -98,7 +96,7 @@ await RenderAsync(store, monitor, contents, evt.GuildId, evt.ServerId, culture,
9896
}
9997
}
10098

101-
/// <summary>Handles a connection-status change: a non-Connected server marks its storage monitor embeds unreachable.</summary>
99+
/// <summary>Handles a connection-status change: a drop from Connected marks its storage embeds unreachable.</summary>
102100
/// <param name="evt">The connection-status change.</param>
103101
/// <param name="cancellationToken">A cancellation token.</param>
104102
/// <returns>A task that completes when every affected embed has been re-rendered.</returns>
@@ -107,18 +105,17 @@ public async Task HandleConnectionStatusAsync(
107105
CancellationToken cancellationToken)
108106
{
109107
ArgumentNullException.ThrowIfNull(evt);
108+
if (evt.IsConnected || !evt.WasConnected)
109+
{
110+
// Connected: the supervisor's prime path republishes real state — nothing to do.
111+
// Never-connected in this process (boot, reconnect-loop repeats): keep the last-run
112+
// embeds; only a drop from Connected sweeps them to unreachable.
113+
return;
114+
}
115+
110116
var scope = scopeFactory.CreateAsyncScope();
111117
await using (scope.ConfigureAwait(false))
112118
{
113-
var connections = scope.ServiceProvider.GetRequiredService<IConnectionStore>();
114-
var state = await connections.GetStateAsync(evt.GuildId, evt.ServerId, cancellationToken)
115-
.ConfigureAwait(false);
116-
if (state is { Status: ConnectionStatus.Connected })
117-
{
118-
// The supervisor's prime path republishes real state on connect; nothing to do here.
119-
return;
120-
}
121-
122119
var store = scope.ServiceProvider.GetRequiredService<IStorageMonitorStore>();
123120
var monitors = await store.ListByServerAsync(evt.GuildId, evt.ServerId, cancellationToken)
124121
.ConfigureAwait(false);

tests/RustPlusBot.Features.StorageMonitors.Tests/Hosting/StorageMonitorsHostedServiceTests.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using NSubstitute;
44
using RustPlusBot.Abstractions.Connections;
55
using RustPlusBot.Abstractions.Events;
6-
using RustPlusBot.Domain.Connections;
76
using RustPlusBot.Domain.StorageMonitors;
87
using RustPlusBot.Features.ItemData.Naming;
98
using RustPlusBot.Features.StorageMonitors.Hosting;
@@ -13,7 +12,6 @@
1312
using RustPlusBot.Features.StorageMonitors.Rendering;
1413
using RustPlusBot.Features.Workspace.Locating;
1514
using RustPlusBot.Localization;
16-
using RustPlusBot.Persistence.Connections;
1715
using RustPlusBot.Persistence.StorageMonitors;
1816
using RustPlusBot.Persistence.Workspace;
1917

@@ -26,13 +24,11 @@ public sealed class StorageMonitorsHostedServiceTests
2624
private static Harness Create()
2725
{
2826
var store = Substitute.For<IStorageMonitorStore>();
29-
var connections = Substitute.For<IConnectionStore>();
3027
var workspace = Substitute.For<IWorkspaceStore>();
3128
workspace.GetCultureAsync(Arg.Any<ulong>(), Arg.Any<CancellationToken>()).Returns("en");
3229

3330
var services = new ServiceCollection();
3431
services.AddScoped(_ => store);
35-
services.AddScoped(_ => connections);
3632
services.AddScoped(_ => workspace);
3733
var provider = services.BuildServiceProvider();
3834
var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
@@ -66,7 +62,7 @@ private static Harness Create()
6662
relay,
6763
NullLogger<StorageMonitorsHostedService>.Instance);
6864

69-
return new Harness(service, bus, store, connections, relayPoster, relayLocator, pairingLocator, pairingPoster);
65+
return new Harness(service, bus, store, relayPoster, relayLocator, pairingLocator, pairingPoster);
7066
}
7167

7268
[Fact]
@@ -137,11 +133,6 @@ public async Task ConnectionStatusChangedEvent_non_connected_routes_to_relay_and
137133
await h.Service.StartAsync(default);
138134

139135
var serverId = Guid.NewGuid();
140-
h.Connections.GetStateAsync(Guild, serverId, Arg.Any<CancellationToken>())
141-
.Returns(new ConnectionState
142-
{
143-
GuildId = Guild, RustServerId = serverId, Status = ConnectionStatus.Unreachable,
144-
});
145136
h.Store.ListByServerAsync(Guild, serverId, Arg.Any<CancellationToken>())
146137
.Returns(
147138
[
@@ -242,7 +233,6 @@ private sealed record Harness(
242233
StorageMonitorsHostedService Service,
243234
InMemoryEventBus Bus,
244235
IStorageMonitorStore Store,
245-
IConnectionStore Connections,
246236
IStorageMonitorChannelPoster Poster,
247237
IStorageMonitorChannelLocator Locator,
248238
IStorageMonitorChannelLocator PairingLocator,

tests/RustPlusBot.Features.StorageMonitors.Tests/StorageMonitorStateRelayTests.cs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using NSubstitute;
33
using RustPlusBot.Abstractions.Connections;
44
using RustPlusBot.Abstractions.Events;
5-
using RustPlusBot.Domain.Connections;
65
using RustPlusBot.Domain.StorageMonitors;
76
using RustPlusBot.Features.ItemData.Naming;
87
using RustPlusBot.Features.StorageMonitors.Posting;
@@ -24,13 +23,11 @@ public sealed class StorageMonitorStateRelayTests
2423
private static Harness Create()
2524
{
2625
var store = Substitute.For<IStorageMonitorStore>();
27-
var connections = Substitute.For<IConnectionStore>();
2826
var workspace = Substitute.For<IWorkspaceStore>();
2927
workspace.GetCultureAsync(Arg.Any<ulong>(), Arg.Any<CancellationToken>()).Returns("en");
3028

3129
var services = new ServiceCollection();
3230
services.AddScoped(_ => store);
33-
services.AddScoped(_ => connections);
3431
services.AddScoped(_ => workspace);
3532
var provider = services.BuildServiceProvider();
3633

@@ -49,7 +46,7 @@ private static Harness Create()
4946
var relay = new StorageMonitorStateRelay(
5047
provider.GetRequiredService<IServiceScopeFactory>(), locator, poster, renderer, query);
5148

52-
return new Harness(relay, store, poster, connections, query);
49+
return new Harness(relay, store, poster, query);
5350
}
5451

5552
[Fact]
@@ -95,11 +92,6 @@ await h.Poster.Received(1).EnsureAsync(555UL, Arg.Any<ulong?>(),
9592
public async Task HandleConnectionStatusAsync_NotConnected_PostsUnreachable()
9693
{
9794
var h = Create();
98-
h.Connections.GetStateAsync(Guild, Server, Arg.Any<CancellationToken>())
99-
.Returns(new ConnectionState
100-
{
101-
GuildId = Guild, RustServerId = Server, Status = ConnectionStatus.Unreachable,
102-
});
10395
h.Store.ListByServerAsync(Guild, Server, Arg.Any<CancellationToken>())
10496
.Returns(
10597
[
@@ -126,11 +118,6 @@ await h.Poster.Received(1).EnsureAsync(555UL, Arg.Any<ulong?>(),
126118
public async Task HandleConnectionStatusAsync_Connected_DoesNothing()
127119
{
128120
var h = Create();
129-
h.Connections.GetStateAsync(Guild, Server, Arg.Any<CancellationToken>())
130-
.Returns(new ConnectionState
131-
{
132-
GuildId = Guild, RustServerId = Server, Status = ConnectionStatus.Connected,
133-
});
134121

135122
await h.Relay.HandleConnectionStatusAsync(
136123
new ConnectionStatusChangedEvent(Guild, Server, IsConnected: true, WasConnected: true),
@@ -141,6 +128,33 @@ await h.Poster.DidNotReceive().EnsureAsync(Arg.Any<ulong>(), Arg.Any<ulong?>(),
141128
Arg.Any<CancellationToken>());
142129
}
143130

131+
/// <summary>Boot/reconnect-loop statuses (never Connected in this process) must not sweep — embeds keep their last-run state until the prime republishes.</summary>
132+
[Fact]
133+
public async Task HandleConnectionStatusAsync_BootWithoutPriorConnection_DoesNothing()
134+
{
135+
var h = Create();
136+
h.Store.ListByServerAsync(Guild, Server, Arg.Any<CancellationToken>())
137+
.Returns(
138+
[
139+
new SmartStorageMonitor
140+
{
141+
GuildId = Guild,
142+
ServerId = Server,
143+
EntityId = 7UL,
144+
Name = "TC",
145+
MessageId = null,
146+
},
147+
]);
148+
149+
await h.Relay.HandleConnectionStatusAsync(
150+
new ConnectionStatusChangedEvent(Guild, Server, IsConnected: false, WasConnected: false),
151+
CancellationToken.None);
152+
153+
await h.Poster.DidNotReceive().EnsureAsync(Arg.Any<ulong>(), Arg.Any<ulong?>(),
154+
Arg.Any<global::Discord.Embed>(), Arg.Any<global::Discord.MessageComponent>(),
155+
Arg.Any<CancellationToken>());
156+
}
157+
144158
[Fact]
145159
public async Task HandleReachabilityChangedAsync_ForeignEntity_DoesNothing()
146160
{
@@ -243,6 +257,5 @@ private sealed record Harness(
243257
StorageMonitorStateRelay Relay,
244258
IStorageMonitorStore Store,
245259
IStorageMonitorChannelPoster Poster,
246-
IConnectionStore Connections,
247260
IRustServerQuery Query);
248261
}

0 commit comments

Comments
 (0)