Updated the TelegramNotifier to fetch channel names in a single database query, improving efficiency. Modified notification messages to include channel names for better clarity, ensuring users know where to switch for live content. Added tests to verify the inclusion of channel names in notifications and updated documentation to reflect these changes.
253 lines
10 KiB
C#
253 lines
10 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("Футурама") && s.Contains("Мультреалити")),
|
|
null,
|
|
Arg.Any<CancellationToken>()
|
|
);
|
|
|
|
// Граница разобранного двигается всегда — иначе следующий тик оповестил бы о том же.
|
|
Assert.Equal(Now, settings.NotifiedUntil);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Notifies_UpNext_TellsWhenAndWhere()
|
|
{
|
|
var fixture = new TestDb();
|
|
var channel = Channel.Create("Мультреалити", "mult", Now);
|
|
var soon = Show.Create("Гриффины", ShowKind.Series);
|
|
|
|
var subscriber = TelegramSubscriber.Create(100, Guid.NewGuid(), "viewer", Now);
|
|
subscriber.Toggle(channel.Id, TelegramNotificationKind.UpNext);
|
|
|
|
await using (var seed = fixture.New())
|
|
{
|
|
seed.Channels.Add(channel);
|
|
seed.Shows.Add(soon);
|
|
seed.TelegramSubscribers.Add(subscriber);
|
|
seed.ScheduleEntries.Add(Program(channel.Id, soon.Id, Now.AddMinutes(4), 25));
|
|
await seed.SaveChangesAsync(CancellationToken.None);
|
|
}
|
|
|
|
var api = Substitute.For<ITelegramApi>();
|
|
var settings = Settings();
|
|
// Окно предупреждения — свои пять минут: разобранное кончается на Now, а смотрим до Now+5.
|
|
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,
|
|
Arg.Is<string>(s =>
|
|
s!.Contains("Гриффины") && s.Contains("4 мин") && s.Contains("Мультреалити")
|
|
),
|
|
null,
|
|
Arg.Any<CancellationToken>()
|
|
);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|