using System; using foodmarket.Api.Background; using FluentAssertions; using Xunit; namespace foodmarket.UnitTests; /// Тихие часы агрегированной рассылки уведомлений об отрицательных /// остатках. Время считается в зоне Asia/Almaty (UTC+5). Интервал может /// переходить через полночь; null/равные границы = тихих часов нет. public class NegativeStockNotifyQuietHoursTests { // 03:00 UTC = 08:00 Алматы. private static readonly DateTime At08Almaty = new(2026, 10, 9, 3, 0, 0, DateTimeKind.Utc); // 18:00 UTC = 23:00 Алматы. private static readonly DateTime At23Almaty = new(2026, 10, 9, 18, 0, 0, DateTimeKind.Utc); // 21:00 UTC = 02:00 Алматы (следующих суток). private static readonly DateTime At02Almaty = new(2026, 10, 9, 21, 0, 0, DateTimeKind.Utc); [Fact] public void No_quiet_hours_when_null_or_equal() { NegativeStockNotificationJob.InQuietHours(At23Almaty, null, 8).Should().BeFalse(); NegativeStockNotificationJob.InQuietHours(At23Almaty, 22, null).Should().BeFalse(); NegativeStockNotificationJob.InQuietHours(At23Almaty, 0, 0).Should().BeFalse(); } [Fact] public void Overnight_window_22_to_8_covers_late_night_and_early_morning() { // Тихие часы 22:00–08:00 (через полночь). NegativeStockNotificationJob.InQuietHours(At23Almaty, 22, 8).Should().BeTrue(); // 23:00 — тихо NegativeStockNotificationJob.InQuietHours(At02Almaty, 22, 8).Should().BeTrue(); // 02:00 — тихо NegativeStockNotificationJob.InQuietHours(At08Almaty, 22, 8).Should().BeFalse(); // 08:00 — уже можно } [Fact] public void Daytime_window_9_to_18_is_same_day_range() { NegativeStockNotificationJob.InQuietHours(At08Almaty, 9, 18).Should().BeFalse(); // 08:00 — вне окна NegativeStockNotificationJob.InQuietHours(At23Almaty, 9, 18).Should().BeFalse(); // 23:00 — вне окна // 13:00 Алматы = 08:00 UTC… проверим середину окна: 10:00 UTC = 15:00 Алматы. var at15 = new DateTime(2026, 10, 9, 10, 0, 0, DateTimeKind.Utc); NegativeStockNotificationJob.InQuietHours(at15, 9, 18).Should().BeTrue(); } }