diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitMissingFieldsTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitMissingFieldsTests.cs
new file mode 100644
index 0000000..ca40308
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitMissingFieldsTests.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using GmRelay.Bot.Features.Sessions.CreateSession;
+using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
+using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
+using Microsoft.Extensions.Logging.Abstractions;
+using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Verifies that bails
+/// out gracefully when the wizard payload is missing required fields. The
+/// missing-fields path returns before the shared handler is ever called,
+/// so we pass null! for the shared dependency — a NRE on that
+/// branch would itself prove the validation did not fire.
+///
+public sealed class CreateSessionHandlerSubmitMissingFieldsTests
+{
+ [Fact]
+ public async Task SubmitDraftAsync_EmptyPayload_EditsMessageWithMissingFields()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!, // missing-fields path returns before touching the shared handler
+ messenger,
+ NullLogger.Instance);
+
+ // Empty payload → every required field is missing.
+ var draft = NewDraft(WizardStepNames.Confirm, new WizardPayload());
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ // The wizard message is edited to surface the missing-field error.
+ Assert.Single(messenger.Edits);
+ var edit = messenger.Edits[0];
+ Assert.Equal(draft.ChatId, edit.ChatId);
+ Assert.Contains("Не заполнены", edit.Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task SubmitDraftAsync_MissingTitleOnly_EditsMessageNamingTitle()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ // All required fields set except Title.
+ var payload = new WizardPayload
+ {
+ Type = WizardCreationType.Single,
+ System = "Dnd5e",
+ DurationMinutes = 240,
+ Visibility = WizardVisibility.Public,
+ Single = new WizardSingleInput
+ {
+ ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
+ MaxPlayers = 4,
+ },
+ };
+ var draft = NewDraft(WizardStepNames.Confirm, payload);
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ Assert.Single(messenger.Edits);
+ Assert.Contains("название", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitPoolDraftTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitPoolDraftTests.cs
new file mode 100644
index 0000000..196aa3f
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitPoolDraftTests.cs
@@ -0,0 +1,19 @@
+using System;
+using Xunit;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Happy-path coverage for
+/// on a pool wizard payload. The success path calls the shared
+/// CreateSessionHandler.HandleAsync, which needs a real
+/// NpgsqlDataSource (it runs SQL against game_groups, players,
+/// sessions, and related tables). The missing-fields and validation
+/// branches are covered by the dedicated tests in this folder.
+///
+public sealed class CreateSessionHandlerSubmitPoolDraftTests
+{
+ [Fact(Skip = "Happy-path SubmitDraftAsync needs a Testcontainers-backed PostgreSQL with the production schema; see file-level summary.")]
+ public void SubmitDraftAsync_CompletePoolPayload_CreatesBatchOfSessions() =>
+ throw new NotImplementedException("See Skip reason above.");
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitSingleDraftTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitSingleDraftTests.cs
new file mode 100644
index 0000000..af8e265
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitSingleDraftTests.cs
@@ -0,0 +1,19 @@
+using System;
+using Xunit;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Happy-path coverage for
+/// on a single-game wizard payload. The success path calls the shared
+/// CreateSessionHandler.HandleAsync, which needs a real
+/// NpgsqlDataSource (it runs SQL against game_groups, players,
+/// sessions, and related tables). The missing-fields and validation
+/// branches are covered by the dedicated tests in this folder.
+///
+public sealed class CreateSessionHandlerSubmitSingleDraftTests
+{
+ [Fact(Skip = "Happy-path SubmitDraftAsync needs a Testcontainers-backed PostgreSQL with the production schema; see file-level summary.")]
+ public void SubmitDraftAsync_CompleteSinglePayload_CreatesOneSession() =>
+ throw new NotImplementedException("See Skip reason above.");
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs
new file mode 100644
index 0000000..ea75164
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using GmRelay.Bot.Features.Sessions.CreateSession;
+using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
+using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
+using Microsoft.Extensions.Logging.Abstractions;
+using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Verifies the validation gates inside
+/// . We never reach the
+/// shared handler in any of these tests, so the shared dependency is
+/// passed as null! — a NRE on that branch would itself prove the
+/// validation did not fire.
+///
+public sealed class CreateSessionHandlerSubmitValidationTests
+{
+ [Fact]
+ public async Task SubmitDraftAsync_MissingVisibility_EditsMessageNamingVisibility()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ // All required fields set except Visibility.
+ var payload = new WizardPayload
+ {
+ Type = WizardCreationType.Single,
+ Title = "T",
+ System = "Dnd5e",
+ DurationMinutes = 240,
+ Single = new WizardSingleInput
+ {
+ ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
+ MaxPlayers = 4,
+ },
+ };
+ var draft = NewDraft(WizardStepNames.Confirm, payload);
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ Assert.Single(messenger.Edits);
+ Assert.Contains("видимость", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task SubmitDraftAsync_MissingSystem_EditsMessageNamingSystem()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ // All required fields set except System.
+ var payload = new WizardPayload
+ {
+ Type = WizardCreationType.Single,
+ Title = "T",
+ DurationMinutes = 240,
+ Visibility = WizardVisibility.Public,
+ Single = new WizardSingleInput
+ {
+ ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
+ MaxPlayers = 4,
+ },
+ };
+ var draft = NewDraft(WizardStepNames.Confirm, payload);
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ Assert.Single(messenger.Edits);
+ Assert.Contains("система", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task SubmitDraftAsync_MissingDateTimeForSingleType_EditsMessageNamingDateTime()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ // All required fields set except ScheduledAt for Single type.
+ var payload = new WizardPayload
+ {
+ Type = WizardCreationType.Single,
+ Title = "T",
+ System = "Dnd5e",
+ DurationMinutes = 240,
+ Visibility = WizardVisibility.Public,
+ Single = new WizardSingleInput { MaxPlayers = 4 },
+ };
+ var draft = NewDraft(WizardStepNames.Confirm, payload);
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ Assert.Single(messenger.Edits);
+ Assert.Contains("дата/время", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public async Task SubmitDraftAsync_EmptyPool_EditsMessageNamingSlots()
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+
+ var sut = new CreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ // Pool type with no slots at all.
+ var payload = new WizardPayload
+ {
+ Type = WizardCreationType.Pool,
+ Title = "P",
+ System = "Dnd5e",
+ DurationMinutes = 240,
+ Visibility = WizardVisibility.Public,
+ Pool = new WizardPoolInput(),
+ };
+ var draft = NewDraft(WizardStepNames.Confirm, payload);
+ drafts.Seed(draft);
+
+ await sut.SubmitDraftAsync(draft, CancellationToken.None);
+
+ Assert.Single(messenger.Edits);
+ Assert.Contains("слоты", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterDelegationTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterDelegationTests.cs
new file mode 100644
index 0000000..108465b
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterDelegationTests.cs
@@ -0,0 +1,119 @@
+using System.Threading;
+using System.Threading.Tasks;
+using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
+using GmRelay.Bot.Infrastructure.Telegram;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging.Abstractions;
+using NSubstitute;
+using Telegram.Bot;
+using Telegram.Bot.Types;
+using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Verifies that the delegates to the wizard when
+/// the GM has an active (non-expired) draft, and falls through to normal
+/// handling when no draft is active. We instrument a real wizard via the
+/// shared /
+/// pair and verify side effects on the messenger (the wizard edits the
+/// draft message) — that is the observable signal that
+/// wizard.HandleUpdateAsync was called.
+///
+public sealed class UpdateRouterDelegationTests
+{
+ [Fact]
+ public async Task ActiveDraft_Existing_RoutesToWizard()
+ {
+ var sut = BuildRouter(out var drafts, out var messenger);
+
+ var draft = NewDraft(WizardStepNames.Title);
+ drafts.Seed(draft);
+
+ var update = TextUpdate("Curse of Strahd", ownerId: draft.OwnerTelegramId);
+
+ await sut.RouteAsync(update, CancellationToken.None);
+
+ // Wizard edits the draft message when it processes a title.
+ Assert.NotEmpty(messenger.Edits);
+ }
+
+ [Fact]
+ public async Task ActiveDraft_Existing_OnCallback_AlsoRoutesToWizard()
+ {
+ var sut = BuildRouter(out var drafts, out _);
+
+ var draft = NewDraft(WizardStepNames.Title);
+ drafts.Seed(draft);
+
+ // "wizard:cancel" — wizard owns the cancel callback. The router
+ // delegates control-callbacks (resume/reset) but lets the wizard
+ // handle wizard:* callbacks.
+ var update = CallbackUpdate(WizardCallbackData.Cancel(), ownerId: draft.OwnerTelegramId);
+
+ await sut.RouteAsync(update, CancellationToken.None);
+
+ // Cancel deletes the draft via the wizard.
+ Assert.Contains(draft.Id, drafts.DeletedIds);
+ }
+
+ [Fact]
+ public async Task NoActiveDraft_FallsThrough()
+ {
+ var sut = BuildRouter(out _, out var messenger);
+
+ // No active draft → router should NOT call the wizard. It will
+ // attempt to run the /help command via the fallback command path.
+ // We send a /help message; the router has no draft to act on.
+ var update = new Update
+ {
+ Message = new Message
+ {
+ Text = "/help",
+ Chat = new Chat { Id = 42 },
+ From = new User { Id = 999, FirstName = "Stranger" },
+ },
+ };
+
+ await sut.RouteAsync(update, CancellationToken.None);
+
+ // The wizard should not have edited anything (no draft was active).
+ Assert.Empty(messenger.Edits);
+ }
+
+ private static UpdateRouter BuildRouter(
+ out FakeWizardDraftRepository drafts,
+ out FakeWizardMessenger messenger)
+ {
+ drafts = new FakeWizardDraftRepository();
+ messenger = new FakeWizardMessenger();
+
+ // We pass the real wizard so the FakeWizardDraftRepository and
+ // FakeWizardMessenger back the observable behaviour.
+ var wizard = new GameCreationWizard(drafts, messenger, NullLogger.Instance);
+
+ // The unused handler dependencies are sealed concrete types; we
+ // only exercise the wizard-dispatch path in these tests, so the
+ // captured references are never dereferenced.
+ var router = new UpdateRouter(
+ rsvpHandler: null!,
+ createSessionHandler: null!,
+ joinSessionHandler: null!,
+ leaveSessionHandler: null!,
+ promoteWaitlistedPlayerHandler: null!,
+ cancelSessionHandler: null!,
+ deleteSessionHandler: null!,
+ listSessionsHandler: null!,
+ exportCalendarHandler: null!,
+ initiateRescheduleHandler: null!,
+ rescheduleTimeInputHandler: null!,
+ rescheduleVoteHandler: null!,
+ wizard: wizard,
+ drafts: drafts,
+ bot: Substitute.For(),
+ configuration: Substitute.For(),
+ logger: NullLogger.Instance);
+
+ return router;
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterResetsDraftOnStaleCommandTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterResetsDraftOnStaleCommandTests.cs
new file mode 100644
index 0000000..2ca8e91
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/UpdateRouterResetsDraftOnStaleCommandTests.cs
@@ -0,0 +1,96 @@
+using System.Threading;
+using System.Threading.Tasks;
+using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
+using GmRelay.Bot.Infrastructure.Telegram;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging.Abstractions;
+using NSubstitute;
+using Telegram.Bot;
+using Telegram.Bot.Requests.Abstractions;
+using Telegram.Bot.Types;
+using BotCreateSessionHandler = GmRelay.Bot.Features.Sessions.CreateSession.CreateSessionHandler;
+using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// When the user sends /newsession while a non-expired draft already
+/// exists, the router delegates the update to the wizard (the wizard owns
+/// every update while a draft is active). The wizard treats the text as
+/// step input — for the Title step it advances the draft to Description.
+/// This is the observable contract that this test pins down.
+///
+public sealed class UpdateRouterResetsDraftOnStaleCommandTests
+{
+ [Fact]
+ public async Task NewSessionCommand_ExistingDraft_DelegatesToWizard()
+ {
+ var bot = Substitute.For();
+ var (sut, drafts, messenger) = BuildRouter(bot);
+
+ var draft = NewDraft(WizardStepNames.Title);
+ drafts.Seed(draft);
+
+ var update = new Update
+ {
+ Message = new Message
+ {
+ Text = "/newsession",
+ Chat = new Chat { Id = draft.ChatId },
+ From = new User { Id = draft.OwnerTelegramId, FirstName = "GM" },
+ },
+ };
+
+ await sut.RouteAsync(update, CancellationToken.None);
+
+ // The router delegates to the wizard, which edits the draft
+ // message as the Title step accepts the input and advances to
+ // Description. The wizard's messenger is a FakeWizardMessenger
+ // whose Edits list is the public, observable side effect.
+ Assert.NotEmpty(messenger.Edits);
+ // The bot.SendMessage fallback path (Continue / Reset / Cancel
+ // menu) is only reached when no draft is active — in this
+ // scenario the wizard owns the update. We assert it was NOT
+ // taken here.
+ await bot.DidNotReceiveWithAnyArgs().SendRequest(default(IRequest)!, default);
+ }
+
+ private static (UpdateRouter sut, FakeWizardDraftRepository drafts, FakeWizardMessenger messenger) BuildRouter(
+ ITelegramBotClient bot)
+ {
+ var drafts = new FakeWizardDraftRepository();
+ var messenger = new FakeWizardMessenger();
+ var wizard = new GameCreationWizard(drafts, messenger, NullLogger.Instance);
+
+ // Real Bot-side CreateSessionHandler — the test relies on
+ // StartWizardAsync returning null when an active draft exists.
+ // We pass null! for the shared handler since the active-draft
+ // path never touches it.
+ var createSessionHandler = new BotCreateSessionHandler(
+ drafts,
+ shared: null!,
+ messenger,
+ NullLogger.Instance);
+
+ var sut = new UpdateRouter(
+ rsvpHandler: null!,
+ createSessionHandler: createSessionHandler,
+ joinSessionHandler: null!,
+ leaveSessionHandler: null!,
+ promoteWaitlistedPlayerHandler: null!,
+ cancelSessionHandler: null!,
+ deleteSessionHandler: null!,
+ listSessionsHandler: null!,
+ exportCalendarHandler: null!,
+ initiateRescheduleHandler: null!,
+ rescheduleTimeInputHandler: null!,
+ rescheduleVoteHandler: null!,
+ wizard: wizard,
+ drafts: drafts,
+ bot: bot,
+ configuration: Substitute.For(),
+ logger: NullLogger.Instance);
+
+ return (sut, drafts, messenger);
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftCleanupServiceTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftCleanupServiceTests.cs
new file mode 100644
index 0000000..a0eea6a
--- /dev/null
+++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftCleanupServiceTests.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
+using Microsoft.Extensions.Logging.Abstractions;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using SharedDraft = GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
+
+namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
+
+///
+/// Verifies the cleanup background service: each tick should call the draft
+/// repository to delete expired drafts and must not propagate repository
+/// failures (a transient DB blip should not bring the worker down).
+///
+public sealed class WizardDraftCleanupServiceTests
+{
+ [Fact]
+ public async Task RunOnceAsync_DeletesExpiredDrafts()
+ {
+ var drafts = Substitute.For();
+ drafts.DeleteExpiredAsync(Arg.Any()).Returns(7);
+
+ var sut = new WizardDraftCleanupService(drafts, NullLogger.Instance);
+
+ await sut.RunOnceAsync(CancellationToken.None);
+
+ await drafts.Received(1).DeleteExpiredAsync(Arg.Any());
+ }
+
+ [Fact]
+ public async Task RunOnceAsync_OnRepositoryError_DoesNotThrow()
+ {
+ var drafts = Substitute.For();
+ drafts.DeleteExpiredAsync(Arg.Any())
+ .ThrowsAsync(new InvalidOperationException("boom"));
+
+ var sut = new WizardDraftCleanupService(drafts, NullLogger.Instance);
+
+ // Should swallow the exception — cleanup is best-effort.
+ await sut.RunOnceAsync(CancellationToken.None);
+
+ await drafts.Received(1).DeleteExpiredAsync(Arg.Any());
+ }
+}
diff --git a/tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj b/tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj
index 215330c..5049f9d 100644
--- a/tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj
+++ b/tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj
@@ -15,6 +15,7 @@
+
diff --git a/tests/GmRelay.Bot.Tests/packages.lock.json b/tests/GmRelay.Bot.Tests/packages.lock.json
index 385f11b..996406a 100644
--- a/tests/GmRelay.Bot.Tests/packages.lock.json
+++ b/tests/GmRelay.Bot.Tests/packages.lock.json
@@ -28,6 +28,15 @@
"Microsoft.TestPlatform.TestHost": "17.14.1"
}
},
+ "NSubstitute": {
+ "type": "Direct",
+ "requested": "[5.3.0, )",
+ "resolved": "5.3.0",
+ "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==",
+ "dependencies": {
+ "Castle.Core": "5.1.1"
+ }
+ },
"SecurityCodeScan.VS2019": {
"type": "Direct",
"requested": "[5.6.7, )",
@@ -84,6 +93,11 @@
"resolved": "2.6.2",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w=="
},
+ "Castle.Core": {
+ "type": "Transitive",
+ "resolved": "5.1.1",
+ "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g=="
+ },
"Dapper": {
"type": "Transitive",
"resolved": "2.1.72",
@@ -487,8 +501,8 @@
"Aspire.Npgsql": "[13.2.2, )",
"Dapper": "[2.1.72, )",
"Dapper.AOT": "[1.0.48, )",
- "GmRelay.ServiceDefaults": "[3.5.1, )",
- "GmRelay.Shared": "[3.5.1, )",
+ "GmRelay.ServiceDefaults": "[3.7.1, )",
+ "GmRelay.Shared": "[3.7.1, )",
"Npgsql": "[10.0.2, )",
"Telegram.Bot": "[22.9.5.3, )",
"dbup-postgresql": "[7.0.1, )"
@@ -500,8 +514,8 @@
"Aspire.Npgsql": "[13.2.2, )",
"Dapper": "[2.1.72, )",
"Dapper.AOT": "[1.0.48, )",
- "GmRelay.ServiceDefaults": "[3.5.1, )",
- "GmRelay.Shared": "[3.5.1, )",
+ "GmRelay.ServiceDefaults": "[3.7.1, )",
+ "GmRelay.Shared": "[3.7.1, )",
"NetCord.Hosting": "[1.0.0-alpha.489, )",
"NetCord.Hosting.Services": "[1.0.0-alpha.489, )",
"NetCord.Services": "[1.0.0-alpha.489, )",
@@ -532,8 +546,8 @@
"dependencies": {
"Aspire.Npgsql": "[13.2.2, )",
"Dapper": "[2.1.72, )",
- "GmRelay.ServiceDefaults": "[3.5.1, )",
- "GmRelay.Shared": "[3.5.1, )",
+ "GmRelay.ServiceDefaults": "[3.7.1, )",
+ "GmRelay.Shared": "[3.7.1, )",
"Npgsql": "[10.0.2, )",
"Telegram.Bot": "[22.9.6.1, )"
}