Refactor LibraryDebugCollector and TelegramApi for improved data handling and clarity
Updated the LibraryDebugCollector to introduce a new CompositionEntry record for better organization of group composition data. Refactored the composition logic to utilize this new structure, enhancing readability and maintainability. In the TelegramApi, replaced inline allowed_updates array with a static readonly field to reduce redundancy and improve clarity in API call configurations. Adjusted related tests to ensure proper handling of message assertions, enhancing overall test reliability.
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
using NSubstitute;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Notifications;
|
||||
using TeleWave.Application.Notifications.Settings;
|
||||
using TeleWave.Application.Tests.Support;
|
||||
using TeleWave.Domain.Notifications;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
using Xunit;
|
||||
|
||||
namespace TeleWave.Application.Tests.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Настройки бота: сохранение проверяет связь делом и приводит режим доставки в соответствие.
|
||||
/// Главное свойство — админка никогда не видит секретов, только признак «задан».
|
||||
/// </summary>
|
||||
public class TelegramSettingsTests
|
||||
{
|
||||
private const string Token = "123:ABC";
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WithPolling_RemovesWebhookAndRemembersBotName()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Api("telewave_bot");
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await Handler(db, api).Handle(Command(token: Token), CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("telewave_bot", result.Value.BotUsername);
|
||||
Assert.Equal("https://t.me/telewave_bot", result.Value.BotLink);
|
||||
Assert.True(result.Value.HasToken);
|
||||
Assert.NotNull(result.Value.LastContactAt);
|
||||
Assert.Null(result.Value.LastError);
|
||||
// Пока стоит вебхук, Telegram не отдаёт обновления опросом — снимаем его явно.
|
||||
await api.Received(1)
|
||||
.DeleteWebhookAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
await api.DidNotReceive()
|
||||
.SetWebhookAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WithWebhook_GeneratesSecretOnce()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Api("telewave_bot");
|
||||
|
||||
await using var db = fixture.New();
|
||||
var handler = Handler(db, api);
|
||||
var command = Command(
|
||||
token: Token,
|
||||
transport: TelegramTransport.Webhook,
|
||||
webhookUrl: "https://tv.example.com/api/telegram/webhook"
|
||||
);
|
||||
|
||||
Assert.True((await handler.Handle(command, CancellationToken.None)).IsSuccess);
|
||||
await db.SaveChangesAsync(CancellationToken.None);
|
||||
var secret = db.TelegramSettings.Single().WebhookSecret;
|
||||
Assert.False(string.IsNullOrWhiteSpace(secret));
|
||||
|
||||
// Повторное сохранение секрет не меняет: он уже стоит на стороне Telegram, и смена
|
||||
// на каждое нажатие «Сохранить» рвала бы приём обновлений до следующего setWebhook.
|
||||
Assert.True((await handler.Handle(command, CancellationToken.None)).IsSuccess);
|
||||
Assert.Equal(secret, db.TelegramSettings.Single().WebhookSecret);
|
||||
await api.Received(2)
|
||||
.SetWebhookAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhenTelegramFails_KeepsWhatAdminTypedAndReportsWhy()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Substitute.For<ITelegramApi>();
|
||||
api.GetMeAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>())
|
||||
.Returns<TelegramBotInfo?>(_ => throw new InvalidOperationException("Unauthorized"));
|
||||
|
||||
await using (var db = fixture.New())
|
||||
{
|
||||
var result = await Handler(db, api)
|
||||
.Handle(Command(token: Token, proxyHost: "127.0.0.1"), CancellationToken.None);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Equal("Telegram.Unreachable", result.Error.Code);
|
||||
}
|
||||
|
||||
// Настройки при этом сохранены: иначе админ набирал бы форму заново из-за одной опечатки.
|
||||
await using var check = fixture.New();
|
||||
var stored = check.TelegramSettings.Single();
|
||||
Assert.Equal("127.0.0.1", stored.ProxyHost);
|
||||
Assert.Equal("Unauthorized", stored.LastError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WithoutToken_DoesNotEnableTheBot()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Api("telewave_bot");
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await Handler(db, api).Handle(Command(token: null), CancellationToken.None);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Equal("Telegram.NotConfigured", result.Error.Code);
|
||||
await api.DidNotReceive()
|
||||
.GetMeAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
TelegramTransport.Webhook,
|
||||
null,
|
||||
TelegramProxyKind.None,
|
||||
null,
|
||||
"Telegram.WebhookUrlRequired"
|
||||
)]
|
||||
[InlineData(
|
||||
TelegramTransport.Polling,
|
||||
null,
|
||||
TelegramProxyKind.Socks5,
|
||||
null,
|
||||
"Telegram.ProxyHostRequired"
|
||||
)]
|
||||
public async Task Update_WithHalfFilledMode_IsRejectedBeforeAnyCall(
|
||||
TelegramTransport transport,
|
||||
string? webhookUrl,
|
||||
TelegramProxyKind proxyKind,
|
||||
string? proxyHost,
|
||||
string expected
|
||||
)
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Api("telewave_bot");
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await Handler(db, api)
|
||||
.Handle(
|
||||
Command(
|
||||
token: Token,
|
||||
transport: transport,
|
||||
webhookUrl: webhookUrl,
|
||||
proxyKind: proxyKind,
|
||||
proxyHost: proxyHost
|
||||
),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Equal(expected, result.Error.Code);
|
||||
await api.DidNotReceive()
|
||||
.GetMeAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_WhileDisabled_SavesWithoutTouchingTelegram()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var api = Api("telewave_bot");
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await Handler(db, api)
|
||||
.Handle(Command(token: Token, isEnabled: false), CancellationToken.None);
|
||||
|
||||
// Выключенный бот в сеть не ходит: проверять нечего, а лишний запрос через прокси
|
||||
// ждал бы таймаута на каждом сохранении.
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.False(result.Value.IsEnabled);
|
||||
Assert.Null(result.Value.BotLink);
|
||||
await api.DidNotReceive()
|
||||
.GetMeAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_WithoutRow_ReturnsDefaultsAndCreatesNothing()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await new GetTelegramSettingsQueryHandler(
|
||||
new TelegramSettingsService(db)
|
||||
).Handle(new GetTelegramSettingsQuery(), CancellationToken.None);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.False(result.Value.IsEnabled);
|
||||
Assert.False(result.Value.HasToken);
|
||||
Assert.Equal(TelegramTransport.Polling, result.Value.Transport);
|
||||
Assert.Equal(0, result.Value.Subscribers);
|
||||
// Чтение не должно заводить строку: GET в базу не пишет.
|
||||
Assert.Empty(db.TelegramSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_CountsLiveSubscribersAndHidesSecrets()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var settings = TelegramSettings.CreateDefault();
|
||||
settings.Update(
|
||||
new TelegramSettingsUpdate(
|
||||
true,
|
||||
TelegramTransport.Polling,
|
||||
Token,
|
||||
null,
|
||||
TelegramProxyKind.Socks5,
|
||||
"127.0.0.1",
|
||||
1080,
|
||||
"user",
|
||||
"secret"
|
||||
)
|
||||
);
|
||||
settings.SetBotUsername("telewave_bot");
|
||||
|
||||
var live = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", DateTimeOffset.UtcNow);
|
||||
var stopped = TelegramSubscriber.Create(101, Guid.NewGuid(), "gone", DateTimeOffset.UtcNow);
|
||||
stopped.Stop();
|
||||
|
||||
await using (var seed = fixture.New())
|
||||
{
|
||||
seed.TelegramSettings.Add(settings);
|
||||
seed.TelegramSubscribers.AddRange(live, stopped);
|
||||
await seed.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await new GetTelegramSettingsQueryHandler(
|
||||
new TelegramSettingsService(db)
|
||||
).Handle(new GetTelegramSettingsQuery(), CancellationToken.None);
|
||||
|
||||
var dto = result.Value;
|
||||
// Ушедший в «стоп» в счётчик не идёт: админ смотрит, скольким реально уходит рассылка.
|
||||
Assert.Equal(1, dto.Subscribers);
|
||||
Assert.True(dto.HasToken);
|
||||
Assert.True(dto.HasProxyPassword);
|
||||
Assert.Equal("user", dto.ProxyUsername);
|
||||
Assert.Equal(1080, dto.ProxyPort);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BotLink_NeedsBothNameAndEnabledFlag()
|
||||
{
|
||||
var settings = TelegramSettings.CreateDefault();
|
||||
Assert.Null(TelegramSettingsService.BotLink(settings));
|
||||
|
||||
settings.SetBotUsername("telewave_bot");
|
||||
// Имя есть, но бот выключен — кнопки у зрителя быть не должно.
|
||||
Assert.Null(TelegramSettingsService.BotLink(settings));
|
||||
|
||||
settings.Update(
|
||||
new TelegramSettingsUpdate(
|
||||
true,
|
||||
TelegramTransport.Polling,
|
||||
Token,
|
||||
null,
|
||||
TelegramProxyKind.None,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
);
|
||||
Assert.Equal("https://t.me/telewave_bot", TelegramSettingsService.BotLink(settings));
|
||||
}
|
||||
|
||||
private static ITelegramApi Api(string username)
|
||||
{
|
||||
var api = Substitute.For<ITelegramApi>();
|
||||
api.GetMeAsync(Arg.Any<TelegramSettings>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new TelegramBotInfo(1, username));
|
||||
return api;
|
||||
}
|
||||
|
||||
private static UpdateTelegramSettingsCommandHandler Handler(
|
||||
AppDbContext db,
|
||||
ITelegramApi api
|
||||
) => new(db, new TelegramSettingsService(db), api);
|
||||
|
||||
private static UpdateTelegramSettingsCommand Command(
|
||||
string? token,
|
||||
bool isEnabled = true,
|
||||
TelegramTransport transport = TelegramTransport.Polling,
|
||||
string? webhookUrl = null,
|
||||
TelegramProxyKind proxyKind = TelegramProxyKind.None,
|
||||
string? proxyHost = null
|
||||
) =>
|
||||
new(
|
||||
isEnabled,
|
||||
transport,
|
||||
token,
|
||||
webhookUrl,
|
||||
proxyKind,
|
||||
proxyHost,
|
||||
proxyHost is null ? null : 1080,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user