ReminderDto: разделить на ApprovalOverdueReminderDto и ProbationEndingReminderDto

Абстрактный базовый класс + два sealed-наследника вместо одного DTO с
опциональными полями под обе категории — так специфичные для каждого
вида напоминания поля не путаются с полями другого вида. Полиморфная
сериализация System.Text.Json ([JsonPolymorphic]/[JsonDerivedType])
нужна, иначе список IReadOnlyList<ReminderDto> сериализовался бы только
по общим полям базового класса.
This commit is contained in:
gorbenko.pavel
2026-08-27 12:31:45 +05:00
parent 874e41990c
commit 5d7503209a
5 changed files with 99 additions and 68 deletions
@@ -138,7 +138,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
var reminder = result.Value.ShouldHaveSingleItem();
var reminder = result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>();
reminder.Email.ShouldBe("approver@tnsu.kz");
reminder.HoursOverdue.ShouldBe(24);
reminder.RequestId.ShouldBe(1);
@@ -173,7 +173,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(2);
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>().HoursOverdue.ShouldBe(2);
}
[Fact(DisplayName = "Активная строка без даты (Date пуста) — пропускается, а не падает")]
@@ -199,7 +199,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(5);
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>().HoursOverdue.ShouldBe(5);
}
// SharePoint отдаёт Number-колонки ДРОБНЫМИ («1.0», а не «1»), и на этом
@@ -219,7 +219,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().HoursOverdue.ShouldBe(5);
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>().HoursOverdue.ShouldBe(5);
}
[Fact(DisplayName = "Флаг опроса причины наследуется от типа заявки, если в строке не задан")]
@@ -231,7 +231,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().AskOverdueReason.ShouldBeTrue();
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>().AskOverdueReason.ShouldBeTrue();
}
// Портал — одностраничный и живёт в корне сайта; вид ссылки подтверждён
@@ -276,7 +276,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetOverdueAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().Email.ShouldBe("approver@tnsu.kz");
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ApprovalOverdueReminderDto>().Email.ShouldBe("approver@tnsu.kz");
}
private static object HireDataRow(
@@ -355,11 +355,10 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var result = await service.GetProbationEndingAsync(Now, CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
var reminder = result.Value.ShouldHaveSingleItem();
var reminder = result.Value.ShouldHaveSingleItem().ShouldBeOfType<ProbationEndingReminderDto>();
reminder.Category.ShouldBe(ReminderCategory.ProbationEnding);
reminder.ManagerEmail.ShouldBe("manager@tnsu.kz");
reminder.CandidateEmail.ShouldBe("candidate@tnsu.kz");
reminder.Email.ShouldBe("manager@tnsu.kz");
reminder.CandidateName.ShouldBe("Иванов Иван Иванович");
reminder.DaysUntilProbationEnds.ShouldBe(3);
}
@@ -407,7 +406,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var service = BuildProbationService(pageUrl, listUrl, rows, portalClient);
var result = await service.GetProbationEndingAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().CandidateEmail.ShouldBe("romanov@tnsu.kz");
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ProbationEndingReminderDto>().CandidateEmail.ShouldBe("romanov@tnsu.kz");
}
// ФИО в заявке набирают руками — лишние пробелы и другой регистр не должны
@@ -423,7 +422,7 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
var service = BuildProbationService(pageUrl, listUrl, rows, portalClient);
var result = await service.GetProbationEndingAsync(Now, CancellationToken.None);
result.Value.ShouldHaveSingleItem().CandidateEmail.ShouldBe("volkov@tnsu.kz");
result.Value.ShouldHaveSingleItem().ShouldBeOfType<ProbationEndingReminderDto>().CandidateEmail.ShouldBe("volkov@tnsu.kz");
}
[Fact(DisplayName = "Кандидат не найден в /Employee/FullBase — напоминания нет")]