using System.Reflection; using GmRelay.Bot.Infrastructure.Telegram; using Microsoft.Extensions.Logging.Abstractions; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; namespace GmRelay.Bot.Tests.Infrastructure.Telegram; public sealed class TelegramBotServiceTests { [Fact] public async Task ExecuteAsync_ShouldStartPollingAfterLastPendingUpdate() { using var cts = new CancellationTokenSource(); var updateSource = new FakeTelegramUpdateSource(cts); var updateHandler = new FakeTelegramUpdateHandler(); var service = new TelegramBotService( updateSource, updateHandler, NullLogger.Instance); await InvokeExecuteAsync(service, cts.Token); Assert.Empty(updateHandler.HandledUpdates); Assert.Collection( updateSource.Calls, call => { Assert.Equal(-1, call.Offset); Assert.Equal(1, call.Limit); Assert.Null(call.Timeout); Assert.Null(call.AllowedUpdates); }, call => { Assert.Equal(43, call.Offset); Assert.Null(call.Limit); Assert.Equal(30, call.Timeout); Assert.Equal([UpdateType.Message, UpdateType.CallbackQuery], call.AllowedUpdates); }); } private static async Task InvokeExecuteAsync(TelegramBotService service, CancellationToken cancellationToken) { var executeAsync = typeof(TelegramBotService).GetMethod( "ExecuteAsync", BindingFlags.Instance | BindingFlags.NonPublic); Assert.NotNull(executeAsync); var task = executeAsync.Invoke(service, [cancellationToken]) as Task; Assert.NotNull(task); await task; } private sealed class FakeTelegramUpdateHandler : ITelegramUpdateHandler { public List HandledUpdates { get; } = []; public Task RouteAsync(Update update, CancellationToken ct) { HandledUpdates.Add(update); return Task.CompletedTask; } } private sealed class FakeTelegramUpdateSource(CancellationTokenSource cts) : ITelegramUpdateSource { public List Calls { get; } = []; public Task GetUpdatesAsync( int offset, int? limit = null, int? timeout = null, IEnumerable? allowedUpdates = null, CancellationToken cancellationToken = default) { Calls.Add(new PollCall(offset, limit, timeout, allowedUpdates?.ToArray())); return Calls.Count switch { 1 => Task.FromResult(new[] { new Update { Id = 42 } }), 2 => ReturnAndCancelAsync(), _ => throw new InvalidOperationException("Unexpected polling call.") }; } private Task ReturnAndCancelAsync() { cts.Cancel(); return Task.FromResult(Array.Empty()); } } private sealed record PollCall( int Offset, int? Limit, int? Timeout, UpdateType[]? AllowedUpdates); }