using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Notifications; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Broadcast; using TeleWave.Domain.Notifications; using Xunit; namespace TeleWave.Application.Tests.Notifications; /// /// Разговор с ботом. Главное свойство — подписаться можно только по одноразовому коду: рассылка /// уходит в личные чаты, и «кто нашёл бота, тот и подписался» здесь означало бы чужие оповещения. /// public class TelegramBotTests { private static readonly DateTimeOffset Now = new(2026, 8, 1, 12, 0, 0, TimeSpan.Zero); private static TelegramSettings Settings() { var settings = TelegramSettings.CreateDefault(); settings.Update( new TelegramSettingsUpdate( true, TelegramTransport.Polling, "123:ABC", null, TelegramProxyKind.None, null, null, null, null ) ); return settings; } private static TelegramUpdate Message(long chatId, string text) => new(1, chatId, text, "viewer", null, null, null); private static TelegramUpdate Callback(long chatId, string data) => new(2, chatId, null, "viewer", "cb-1", data, 55); [Fact] public async Task Start_WithoutCode_DoesNotSubscribeAnyone() { var fixture = new TestDb(); var api = Substitute.For(); await using var db = fixture.New(); await new TelegramBotService(db, api).HandleAsync( Settings(), Message(100, "/start"), Now, CancellationToken.None ); Assert.Empty(db.TelegramSubscribers); // Незнакомцу объясняют, где взять код, а не молчат в ответ. await api.Received(1) .SendMessageAsync( Arg.Any(), 100, Arg.Is(s => s.Contains("код")), null, Arg.Any() ); } [Fact] public async Task Start_WithCode_LinksChatToUserAndBurnsCode() { var fixture = new TestDb(); var userId = Guid.NewGuid(); var code = TelegramLinkCode.Issue("abc123", userId, Now); var channel = Channel.Create("Мультреалити", "mult", Now); channel.UpdateSettings(channel.Name, isEnabled: true, null); await using (var seed = fixture.New()) { seed.TelegramLinkCodes.Add(code); seed.Channels.Add(channel); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); await new TelegramBotService(db, Substitute.For()).HandleAsync( Settings(), Message(100, "/start abc123"), Now, CancellationToken.None ); var subscriber = Assert.Single(db.TelegramSubscribers); Assert.Equal(userId, subscriber.UserId); Assert.Equal(100, subscriber.ChatId); // Код одноразовый: ссылка оседает в истории браузера и в чате. Assert.NotNull(db.TelegramLinkCodes.Single().UsedAt); } [Fact] public async Task Start_WithUsedCode_IsRefused() { var fixture = new TestDb(); var code = TelegramLinkCode.Issue("abc123", Guid.NewGuid(), Now); code.MarkUsed(Now); await using (var seed = fixture.New()) { seed.TelegramLinkCodes.Add(code); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); await new TelegramBotService(db, Substitute.For()).HandleAsync( Settings(), Message(100, "/start abc123"), Now, CancellationToken.None ); Assert.Empty(db.TelegramSubscribers); } [Fact] public async Task Callback_TogglesSubscriptionBothWays() { var fixture = new TestDb(); var channel = Channel.Create("Мультреалити", "mult", Now); channel.UpdateSettings(channel.Name, isEnabled: true, null); var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now); await using (var seed = fixture.New()) { seed.Channels.Add(channel); seed.TelegramSubscribers.Add(subscriber); await seed.SaveChangesAsync(CancellationToken.None); } var data = $"{channel.Id}:{TelegramNotificationKind.ShowStart}"; await using (var db = fixture.New()) { await new TelegramBotService(db, Substitute.For()).HandleAsync( Settings(), Callback(100, data), Now, CancellationToken.None ); } await using (var check = fixture.New()) { Assert.Single(check.TelegramSubscriptions); } await using (var db = fixture.New()) { await new TelegramBotService(db, Substitute.For()).HandleAsync( Settings(), Callback(100, data), Now, CancellationToken.None ); } await using var verify = fixture.New(); // Повторное нажатие выключает: кнопка — тумблер, а не «добавить ещё одну подписку». Assert.Empty(verify.TelegramSubscriptions); } [Fact] public async Task Callback_FromUnknownChat_IsRefused() { var fixture = new TestDb(); var api = Substitute.For(); await using var db = fixture.New(); await new TelegramBotService(db, api).HandleAsync( Settings(), Callback(100, $"{Guid.NewGuid()}:{TelegramNotificationKind.ShowStart}"), Now, CancellationToken.None ); Assert.Empty(db.TelegramSubscriptions); } }