ADMIN_USER_IDS (Telegram:AdminUserIds) takes a comma separated list; the older ADMIN_USER_ID / Telegram:AdminUserId still works, also takes a list and is merged. Empty entries and duplicates are ignored, non-numeric entries fail validation. Impersonation is now per admin: one admin per target, an impersonating admin can't be impersonated, and every admin's chat menu lists /impersonate.
97 lines
4.3 KiB
C#
97 lines
4.3 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Telegram.Bot.Exceptions;
|
|
using Telegram.Bot.Requests;
|
|
using Telegram.Bot.Requests.Abstractions;
|
|
using Volcanic.AgenticCore.Telegram.Rendering;
|
|
using Volcanic.AgenticCore.Telegram.Tests.Support;
|
|
|
|
namespace Volcanic.AgenticCore.Telegram.Tests;
|
|
|
|
public sealed class BotSetupServiceTests
|
|
{
|
|
private static (BotSetupService Service, FakeBotClient Bot) Create(Action<IRequest>? onSend)
|
|
{
|
|
var bot = new FakeBotClient { OnSend = onSend };
|
|
var options = new TelegramOptions
|
|
{
|
|
BotToken = "t",
|
|
AllowedUserIds = [Updates.AllowedUser, 2002],
|
|
ReconnectInitialDelay = TimeSpan.FromMilliseconds(5),
|
|
ReconnectMaxDelay = TimeSpan.FromMilliseconds(20),
|
|
};
|
|
var sender = new TelegramSender(bot, NullLogger<TelegramSender>.Instance);
|
|
return (new BotSetupService(bot, options, sender, NullLogger<BotSetupService>.Instance), bot);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Online_goes_once_to_the_owner_after_setup_succeeds_on_the_second_try()
|
|
{
|
|
var getMeCalls = 0;
|
|
var (service, bot) = Create(r =>
|
|
{
|
|
if (r is GetMeRequest && Interlocked.Increment(ref getMeCalls) == 1) throw FakeBotClient.NetworkDown();
|
|
});
|
|
|
|
await service.StartAsync(CancellationToken.None);
|
|
await service.ExecuteTask!.WaitAsync(TimeSpan.FromSeconds(5));
|
|
await service.StopAsync(CancellationToken.None);
|
|
|
|
Assert.Equal(2, bot.Requests.OfType<GetMeRequest>().Count());
|
|
var online = Assert.Single(bot.Sent);
|
|
Assert.Equal(Updates.AllowedUser, online.ChatId.Identifier);
|
|
Assert.Equal("🟢 Online.", online.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_forbidden_online_message_is_not_retried_and_does_not_stop_the_service()
|
|
{
|
|
var (service, bot) = Create(r =>
|
|
{
|
|
if (r is SendMessageRequest) throw new ApiRequestException("Forbidden: bot can't initiate conversation with a user", 403);
|
|
});
|
|
|
|
await service.StartAsync(CancellationToken.None);
|
|
await service.ExecuteTask!.WaitAsync(TimeSpan.FromSeconds(5));
|
|
|
|
Assert.True(service.ExecuteTask.IsCompletedSuccessfully);
|
|
Assert.Single(bot.Requests.OfType<SendMessageRequest>());
|
|
Assert.Single(bot.Requests.OfType<GetMeRequest>());
|
|
await service.StopAsync(CancellationToken.None);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task The_admins_chat_menu_also_lists_impersonate()
|
|
{
|
|
var bot = new FakeBotClient();
|
|
var options = new TelegramOptions { BotToken = "t", AllowedUserIds = [Updates.AllowedUser], AdminUserId = 5005 };
|
|
var service = new BotSetupService(bot, options, new TelegramSender(bot, NullLogger<TelegramSender>.Instance), NullLogger<BotSetupService>.Instance);
|
|
|
|
await service.StartAsync(CancellationToken.None);
|
|
await service.ExecuteTask!.WaitAsync(TimeSpan.FromSeconds(5));
|
|
await service.StopAsync(CancellationToken.None);
|
|
|
|
var menus = bot.Requests.OfType<SetMyCommandsRequest>().ToList();
|
|
Assert.Equal(2, menus.Count);
|
|
Assert.DoesNotContain(menus[0].Commands, c => c.Command == "impersonate");
|
|
var admin = Assert.IsType<global::Telegram.Bot.Types.BotCommandScopeChat>(menus[1].Scope);
|
|
Assert.Equal(5005, admin.ChatId.Identifier);
|
|
Assert.Contains(menus[1].Commands, c => c.Command == "impersonate");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Every_admins_chat_menu_lists_impersonate()
|
|
{
|
|
var bot = new FakeBotClient();
|
|
var options = new TelegramOptions { BotToken = "t", AllowedUserIds = [Updates.AllowedUser], AdminUserIds = [5005, 6006] };
|
|
var service = new BotSetupService(bot, options, new TelegramSender(bot, NullLogger<TelegramSender>.Instance), NullLogger<BotSetupService>.Instance);
|
|
|
|
await service.StartAsync(CancellationToken.None);
|
|
await service.ExecuteTask!.WaitAsync(TimeSpan.FromSeconds(5));
|
|
await service.StopAsync(CancellationToken.None);
|
|
|
|
var admins = bot.Requests.OfType<SetMyCommandsRequest>().Skip(1).ToList();
|
|
Assert.Equal([5005L, 6006L], admins.Select(m => Assert.IsType<global::Telegram.Bot.Types.BotCommandScopeChat>(m.Scope).ChatId.Identifier!.Value));
|
|
Assert.All(admins, m => Assert.Contains(m.Commands, c => c.Command == "impersonate"));
|
|
}
|
|
}
|