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;
///
/// Рассылка по ленте. Проверяется то, из-за чего бот становится спамом: оповещение шлётся
/// на смену шоу, а не на каждую серию, и только тем, кто подписан на этот канал.
///
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();
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(),
Arg.Any(),
Arg.Any(),
Arg.Any>?>(),
Arg.Any()
);
}
[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();
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(),
100,
// Предикат NSubstitute принимает аргумент как допускающий null — само сообщение
// объявлено обязательным, потому и разыменовываем без проверки.
Arg.Is(s => s!.Contains("Футурама")),
null,
Arg.Any()
);
// Граница разобранного двигается всегда — иначе следующий тик оповестил бы о том же.
Assert.Equal(Now, settings.NotifiedUntil);
}
}