using TeleWave.Application.Programming.Templates; using TeleWave.Domain.Library; using Xunit; namespace TeleWave.Application.Tests.Programming; /// Окна детского времени: разрешение по времени суток, включая переход через полночь. public class PlanningRulesTests { [Theory] [InlineData(7, 0, true)] [InlineData(22, 59, true)] [InlineData(6, 0, true)] // ровно начало [InlineData(23, 0, false)] // ровно конец — уже вне окна [InlineData(2, 0, false)] public void AudienceAt_DayWindow(int hour, int minute, bool inside) { var rules = new PlanningRules([ new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13), ]); var result = rules.AudienceAt(new TimeOnly(hour, minute)); Assert.Equal(inside ? ShowAudience.Pg13 : null, result); } [Theory] [InlineData(23, 30, true)] [InlineData(3, 0, true)] [InlineData(12, 0, false)] public void AudienceAt_WindowCrossingMidnight(int hour, int minute, bool inside) { // «С 23:00 до 06:00» — ночное окно, границы сравниваются в обратную сторону. var rules = new PlanningRules([ new AudienceWindow(new TimeOnly(23, 0), new TimeOnly(6, 0), ShowAudience.Nc17), ]); var result = rules.AudienceAt(new TimeOnly(hour, minute)); Assert.Equal(inside ? ShowAudience.Nc17 : null, result); } [Fact] public void AudienceAt_OverlappingWindows_TakesTheStrictest() { // Широкое окно, случайно наложенное поверх детского, не должно его отменять. var rules = new PlanningRules([ new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.R), new AudienceWindow(new TimeOnly(7, 0), new TimeOnly(10, 0), ShowAudience.G), ]); Assert.Equal(ShowAudience.G, rules.AudienceAt(new TimeOnly(8, 0))); Assert.Equal(ShowAudience.R, rules.AudienceAt(new TimeOnly(12, 0))); } [Fact] public void AudienceAt_WithoutWindows_IsUnlimited() { Assert.Null(new PlanningRules().AudienceAt(new TimeOnly(3, 0))); } [Fact] public void Json_RoundTrips() { var rules = new PlanningRules( [new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13)], new RepeatLimitRule(7, 2), MaxBreakMinutesPerHour: 12, MaxGenreSharePercent: 40, MaxFallbackSharePercent: 15 ); var restored = PlanningRules.FromJson(rules.ToJson()); // Сравниваем по полям: у record со списком внутри равенство ссылочное, и `Assert.Equal` // на самих правилах проверял бы не то. Assert.NotNull(restored); Assert.Equal(rules.MaxAudienceByTime!, restored.MaxAudienceByTime!); Assert.Equal(rules.MaxRepeatsInWindow, restored.MaxRepeatsInWindow); Assert.Equal(rules.MaxBreakMinutesPerHour, restored.MaxBreakMinutesPerHour); Assert.Equal(rules.MaxGenreSharePercent, restored.MaxGenreSharePercent); Assert.Equal(rules.MaxFallbackSharePercent, restored.MaxFallbackSharePercent); } [Fact] public void Json_WritesMpaaSpelling() { // Рейтинг уезжает в jsonb шаблона и в API ровно тем написанием, каким приходит от источников. // Round-trip этого не поймает: он одинаково зелёный и на «Pg13», а такое значение потом // придётся переводить на каждой границе. var json = new PlanningRules([ new AudienceWindow(new TimeOnly(6, 0), new TimeOnly(23, 0), ShowAudience.Pg13), ]).ToJson(); Assert.Contains("\"PG-13\"", json, StringComparison.Ordinal); } [Fact] public void FromJson_Garbage_IsNull() { // Битый JSON не должен ронять генерацию: правило просто считается незаданным. Assert.Null(PlanningRules.FromJson("{")); Assert.Null(PlanningRules.FromJson(null)); } }