Files
TeleWave/backend/tests/TeleWave.Application.Tests/Notifications/TelegramNotifierTests.cs
T
Leonid Pershin 0f609db4e9
ci / build-backend (push) Successful in 2m17s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m39s
ci / sonar (push) Failing after 1m23s
Implement Discontinuity Index for Live Streaming Enhancements
Added a DiscontinuityIndex property to ScheduleEntry and related DTOs to track the cumulative number of discontinuities in live streaming. Updated the LiveWindowCalculator to utilize this index for generating accurate EXT-X-DISCONTINUITY-SEQUENCE values, ensuring compatibility with ffmpeg-based players. Enhanced the WriteEntriesAsync method to calculate and persist the discontinuity index during entry creation. Updated relevant tests and documentation to reflect these changes, improving the reliability of live streaming functionality.
2026-08-04 12:43:35 +03:00

254 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),
0
);
[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);
}
}