using Microsoft.Extensions.Logging; using Moq; using PARR.DAL.Contracts; using PARR.DAL.DomainModels; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; using PARR.DAL.TransformServices; namespace PARR.DAL.Tests.TransformServices { public class EsppScheduleTransformServiceTests { private Mock esppSchTypeConfigServiceMock; private Mock nextRunModifierServiceMock; private ILogger logger; private EsppScheduleTransformService service; public EsppScheduleTransformServiceTests() { esppSchTypeConfigServiceMock = new Mock(); nextRunModifierServiceMock = new Mock(); var loggerFactory = new LoggerFactory(); logger = loggerFactory.CreateLogger(); service = new EsppScheduleTransformService( logger, esppSchTypeConfigServiceMock.Object, nextRunModifierServiceMock.Object ); } [Fact] public async Task GetNextDate_Monthly2_ReturnsSameDateForAllReferenceDates() { // Arrange var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2025, 12, 08, 10, 0, 0, TimeSpan.Zero); // Будущее, не среда и до ожидаемой даты var futureMatchingDate = new DateTimeOffset(2025, 12, 10, 10, 0, 0, TimeSpan.Zero); // Вторая среда и совпадает с ожидаемой датой var expectedDate = futureMatchingDate; var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Monthly2, Name = "Ежемесячно-2", Description = "Расписание по дням недели в месяце" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "Порядок", Description = "Порядковый номер дня недели" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Второй", EsppExportValue = "Второй (экспорт)", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 2 } }, new EsppScheduleValDto { Order = 2, Type = new EsppSchType { Id = 2, Name = "День недели", Description = "День недели (например, понедельник)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Среда", EsppExportValue = "Среда", TypeId = 2, DateCreated = DateTimeOffset.UtcNow, Order = 3 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); //Act var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate); //Assert Assert.Equal(expectedDate, resultFromPast); Assert.Equal(expectedDate, resultFromFutureNonMatching); Assert.Equal(expectedDate, resultFromFutureMatching); } [Fact] public async Task GetNextDate_Monthly_ReturnsSameDateForAllReferenceDates() { // Arrange var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2025, 12, 08, 10, 0, 0, TimeSpan.Zero); // Будущее, не 15-е var futureMatchingDate = new DateTimeOffset(2025, 12, 15, 10, 0, 0, TimeSpan.Zero); // 15-е декабря — совпадает с ожидаемой датой var expectedDate = futureMatchingDate; var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Monthly, Name = "Ежемесячно", Description = "Расписание по числу месяца" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "День месяца", Description = "Число месяца" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "15", EsppExportValue = "15-е число", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate); Assert.Equal(expectedDate, resultFromPast); Assert.Equal(expectedDate, resultFromFutureNonMatching); Assert.Equal(expectedDate, resultFromFutureMatching); } [Fact] public async Task GetNextDate_Annually_ReturnsSameDateForAllReferenceDates() { // Arrange var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2025, 11, 20, 10, 0, 0, TimeSpan.Zero); // Будущее, не январь, не 1-е var futureMatchingDate = new DateTimeOffset(2026, 1, 1, 10, 0, 0, TimeSpan.Zero); // 1 января 2026 — совпадает с ожидаемой датой var expectedDate = futureMatchingDate; // 1 января 2026 var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Annually, Name = "Ежегодно", Description = "Расписание по месяцу и дню года" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "Месяц", Description = "Месяц года" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Январь", EsppExportValue = "Январь", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } }, new EsppScheduleValDto { Order = 2, Type = new EsppSchType { Id = 2, Name = "День месяца", Description = "Число месяца" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "1", EsppExportValue = "1-е число", TypeId = 2, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate); // Assert Assert.Equal(expectedDate, resultFromPast); Assert.Equal(expectedDate, resultFromFutureNonMatching); Assert.Equal(expectedDate, resultFromFutureMatching); } [Fact] public async Task GetNextDate_Annually2_ReturnsSameDateForAllReferenceDates() { // Arrange var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2026, 01, 02, 10, 0, 0, TimeSpan.Zero); // Будущее, не понедельник var futureMatchingDate = new DateTimeOffset(2026, 1, 5, 10, 0, 0, TimeSpan.Zero); // Первый понедельник января 2026 — совпадает с ожидаемой датой var expectedDate = futureMatchingDate; var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Annually2, Name = "Ежегодно-2", Description = "Расписание по порядку дня недели в месяце (например, первый понедельник января)" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "Порядок", Description = "Порядковый номер дня недели (например, первый)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Первый", EsppExportValue = "Первый", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } }, new EsppScheduleValDto { Order = 2, Type = new EsppSchType { Id = 2, Name = "День недели", Description = "День недели (например, понедельник)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Понедельник", EsppExportValue = "Понедельник", TypeId = 2, DateCreated = DateTimeOffset.UtcNow, Order = 2 } }, new EsppScheduleValDto { Order = 3, Type = new EsppSchType { Id = 3, Name = "Месяц", Description = "Месяц года (например, Января)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Января", EsppExportValue = "Января", TypeId = 3, DateCreated = DateTimeOffset.UtcNow, Order = 3 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate); // Assert Assert.Equal(expectedDate, resultFromPast); Assert.Equal(expectedDate, resultFromFutureNonMatching); Assert.Equal(expectedDate, resultFromFutureMatching); } [Fact] public async Task GetNextDate_Weekly_ReturnsSameDateForAllReferenceDates() { // Arrange var pastDate = new DateTimeOffset(2025, 11, 19, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2025, 11, 27, 10, 0, 0, TimeSpan.Zero); // Будущее, четверг — не понедельник var futureMatchingDate = new DateTimeOffset(2025, 12, 01, 10, 0, 0, TimeSpan.Zero); // четверг — совпадает с ожидаемой датой var expectedDate = futureMatchingDate; var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Weekly, Name = "Еженедельно", Description = "Расписание по дню недели" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "День недели", Description = "День недели" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Понедельник", EsppExportValue = "Понедельник", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate); // Assert Assert.Equal(expectedDate, resultFromPast); Assert.Equal(expectedDate, resultFromFutureNonMatching); Assert.Equal(expectedDate, resultFromFutureMatching); } [Fact] public async Task GetNextDate_Regularly_EveryDay() { // Arrange var pastDate = new DateTimeOffset(2025, 11, 20, 10, 0, 0, TimeSpan.Zero); // Прошлое время var futureNonMatchingDate = new DateTimeOffset(2025, 11, 27, 10, 0, 0, TimeSpan.Zero); // Будущее время var matchingDate = new DateTimeOffset(2025, 11, 26, 10, 0, 0, TimeSpan.Zero); // Совпадает время var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Regularly, Name = "Регулярно", Description = "Регулярное расписание" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "Интервал", Description = "Интервал повторения (например, Ежедневно)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Ежедневно", EsppExportValue = "Ежедневно", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate); var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), matchingDate); // Assert Assert.Equal(matchingDate.AddDays(1), resultFromPast); Assert.Equal(futureNonMatchingDate.AddDays(1), resultFromFutureNonMatching); Assert.Equal(matchingDate.AddDays(1), resultFromFutureMatching); } /* /// /// дата в будущем, но не по расписанию /// /// [Fact] public async Task GetNextDate_Weekly_FutureDateNotMatchingSchedule_ReturnsNextScheduledDate() { // Arrange var futureDate = new DateTimeOffset(2025, 12, 04, 10, 0, 0, TimeSpan.Zero); // Четверг var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Weekly, Name = "Еженедельно", Description = "Расписание по дню недели" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "День недели", Description = "День недели" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Понедельник", EsppExportValue = "Понедельник", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var result = await service.GetNextDateAsync(Guid.NewGuid(), futureDate); // Assert Assert.Equal(DayOfWeek.Monday, result.DayOfWeek); Assert.True(result >= DateTimeOffset.UtcNow, "Дата должна быть в будущем"); } /// /// несуществующая дата (31 февраля) /// /// [Fact] public async Task GetNextDate_Monthly_InvalidDay_FallsBackToLastDay() { // Arrange var pastDate = new DateTimeOffset(2025, 1, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Monthly, Name = "Ежемесячно", Description = "Расписание по числу месяца" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "День месяца", Description = "Число месяца" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "31", // <-- 31 февраля не существует EsppExportValue = "31-е число", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var result = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); // Assert // Результат должен быть 28 февраля (или 29 в високосный год) Assert.Equal(28, result.Day); // или 29, если високосный Assert.Equal(2, result.Month); } /// /// корректная рекурсия (без зацикливания) /// /// [Fact] public async Task GetNextDate_Regularly_RecursionDoesNotLoop() { // Arrange var pastDate = new DateTimeOffset(1925, 11, 20, 10, 0, 0, TimeSpan.Zero); // Давно прошло var scheduleDto = new EsppScheduleDto { TypeSchedule = new EsppSchTypeSchedule { Id = (int)EsppSchTypeScheduleEnum.Regularly, Name = "Регулярно", Description = "Регулярное расписание" }, Values = new List { new EsppScheduleValDto { Order = 1, Type = new EsppSchType { Id = 1, Name = "Интервал", Description = "Интервал повторения (например, Ежедневно)" }, Value = new EsppSchTypeValue { Id = Guid.NewGuid(), Value = "Каждые 2 часа", // <-- Интервал 2 часа EsppExportValue = "Каждые 2 часа", TypeId = 1, DateCreated = DateTimeOffset.UtcNow, Order = 1 } } } }; esppSchTypeConfigServiceMock .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny())) .ReturnsAsync(scheduleDto); // Act var result = await service.GetNextDateAsync(Guid.NewGuid(), pastDate); // Assert // Результат должен быть >= DateTimeOffset.UtcNow Assert.True(result >= DateTimeOffset.UtcNow, "Дата должна быть в будущем"); // Должно быть кратно интервалу var diff = (result - pastDate).TotalHours; Assert.True(diff % 2 == 0 || diff % 2 == 2, "Разница должна быть кратна 2 часам"); }*/ } }