Files
TeleWave/backend/tests/TeleWave.Application.Tests/Notifications/TelegramNotifierTests.cs
T
Leonid Pershin 3dbe0b7698
ci / build-backend (push) Successful in 1m21s
ci / build-frontend (push) Successful in 51s
ci / tests (push) Successful in 1m33s
ci / sonar (push) Successful in 3m51s
Enhance TelegramNotifier to manage previous alerts and improve notification clarity
Updated the TelegramNotifier to delete previous alert messages before sending new notifications, ensuring that only the latest alert is visible to subscribers. Introduced a new property in TelegramSubscriber to track the latest alert message ID. Adjusted related tests to verify the new alert management behavior and updated documentation to reflect these changes, enhancing the overall user experience in message handling.
2026-07-31 21:20:25 +03:00

207 lines
8.1 KiB
C#

using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Notifications;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Notifications;
using Xunit;
namespace TeleWave.Application.Tests.Notifications;
/// <summary>
/// Рассылка по ленте. Проверяется то, из-за чего бот становится спамом: оповещение шлётся
/// на смену шоу, а не на каждую серию, и только тем, кто подписан на этот канал.
/// </summary>
public class TelegramNotifierTests
{
private static readonly DateTimeOffset Now = new(2026, 8, 1, 20, 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 ScheduleEntry Program(
Guid channelId,
Guid showId,
DateTimeOffset start,
int minutes
) =>
ScheduleEntry.FromSlot(
channelId,
Guid.NewGuid(),
ScheduleEntryKind.Program,
start,
start.AddMinutes(minutes),
new ScheduleEntryOrigin(showId, 0, null, null, null)
);
[Fact]
public async Task Notifies_OnlySubscribersOfTheChannel_AndOnlyOnShowChange()
{
var fixture = new TestDb();
var channel = Channel.Create("Мультреалити", "mult", Now);
var other = Channel.Create("Второй", "second", Now);
var show = Show.Create("Футурама", ShowKind.Series);
var another = Show.Create("Симпсоны", ShowKind.Series);
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
subscriber.Toggle(channel.Id, TelegramNotificationKind.ShowStart);
var stranger = TelegramSubscriber.Create(200, Guid.NewGuid(), "other", Now);
stranger.Toggle(other.Id, TelegramNotificationKind.ShowStart);
await using (var seed = fixture.New())
{
seed.Channels.AddRange(channel, other);
seed.Shows.AddRange(show, another);
seed.TelegramSubscribers.AddRange(subscriber, stranger);
// Предыдущая программа того же шоу: следующая серия сменой не считается.
seed.ScheduleEntries.Add(Program(channel.Id, show.Id, Now.AddMinutes(-30), 25));
seed.ScheduleEntries.Add(Program(channel.Id, show.Id, Now.AddMinutes(-2), 25));
await seed.SaveChangesAsync(CancellationToken.None);
}
var api = Substitute.For<ITelegramApi>();
var settings = Settings();
settings.MarkNotifiedUntil(Now.AddMinutes(-5));
await using var db = fixture.New();
var sent = await new TelegramNotifier(db, api).RunAsync(
settings,
Now,
CancellationToken.None
);
Assert.Equal(0, sent);
await api.DidNotReceive()
.SendMessageAsync(
Arg.Any<TelegramSettings>(),
Arg.Any<long>(),
Arg.Any<string>(),
Arg.Any<IReadOnlyList<IReadOnlyList<TelegramButton>>?>(),
Arg.Any<CancellationToken>()
);
}
[Fact]
public async Task Notifies_WhenShowChanges()
{
var fixture = new TestDb();
var channel = Channel.Create("Мультреалити", "mult", Now);
var previous = Show.Create("Симпсоны", ShowKind.Series);
var started = Show.Create("Футурама", ShowKind.Series);
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
subscriber.Toggle(channel.Id, TelegramNotificationKind.ShowStart);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.AddRange(previous, started);
seed.TelegramSubscribers.Add(subscriber);
seed.ScheduleEntries.Add(Program(channel.Id, previous.Id, Now.AddMinutes(-30), 25));
seed.ScheduleEntries.Add(Program(channel.Id, started.Id, Now.AddMinutes(-1), 25));
await seed.SaveChangesAsync(CancellationToken.None);
}
var api = Substitute.For<ITelegramApi>();
var settings = Settings();
settings.MarkNotifiedUntil(Now.AddMinutes(-5));
await using var db = fixture.New();
var sent = await new TelegramNotifier(db, api).RunAsync(
settings,
Now,
CancellationToken.None
);
Assert.Equal(1, sent);
await api.Received(1)
.SendMessageAsync(
Arg.Any<TelegramSettings>(),
100,
// Предикат NSubstitute принимает аргумент как допускающий null — само сообщение
// объявлено обязательным, потому и разыменовываем без проверки.
Arg.Is<string>(s => s!.Contains("Футурама")),
null,
Arg.Any<CancellationToken>()
);
// Граница разобранного двигается всегда — иначе следующий тик оповестил бы о том же.
Assert.Equal(Now, settings.NotifiedUntil);
}
[Fact]
public async Task Notifies_ReplacingItsPreviousAlert()
{
var fixture = new TestDb();
var channel = Channel.Create("Мультреалити", "mult", Now);
var previous = Show.Create("Симпсоны", ShowKind.Series);
var started = Show.Create("Футурама", ShowKind.Series);
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
subscriber.Toggle(channel.Id, TelegramNotificationKind.ShowStart);
subscriber.SetAlertMessage(777);
// Программа, запрошенная зрителем, — не оповещение: рассылка её трогать не должна.
subscriber.SetNoticeMessage(555);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.AddRange(previous, started);
seed.TelegramSubscribers.Add(subscriber);
seed.ScheduleEntries.Add(Program(channel.Id, previous.Id, Now.AddMinutes(-30), 25));
seed.ScheduleEntries.Add(Program(channel.Id, started.Id, Now.AddMinutes(-1), 25));
await seed.SaveChangesAsync(CancellationToken.None);
}
var api = Substitute.For<ITelegramApi>();
api.SendMessageAsync(
Arg.Any<TelegramSettings>(),
Arg.Any<long>(),
Arg.Any<string>(),
Arg.Any<IReadOnlyList<IReadOnlyList<TelegramButton>>?>(),
Arg.Any<CancellationToken>()
)
.Returns(4242L);
var settings = Settings();
settings.MarkNotifiedUntil(Now.AddMinutes(-5));
await using (var db = fixture.New())
{
await new TelegramNotifier(db, api).RunAsync(settings, Now, CancellationToken.None);
await db.SaveChangesAsync(CancellationToken.None);
}
// Вчерашнее «сейчас в эфире» — уже неправда: старое снимаем, новое запоминаем.
await api.Received(1)
.DeleteMessageAsync(
Arg.Any<TelegramSettings>(),
100,
777,
Arg.Any<CancellationToken>()
);
await using var check = fixture.New();
var stored = check.TelegramSubscribers.Single();
Assert.Equal(4242L, stored.AlertMessageId);
Assert.Equal(555L, stored.NoticeMessageId);
}
}