2 Commits

Author SHA1 Message Date
gorbenko.pavel a28a397205 теперь берем email из токена
Tests / test (push) Successful in 1m47s
2026-07-29 10:23:53 +05:00
gorbenko.pavel 13d929eb5b валидация статуса 2026-07-29 10:19:38 +05:00
4 changed files with 29 additions and 1 deletions
@@ -7,5 +7,7 @@ public sealed class CurrentUserAccessor(IHttpContextAccessor httpContextAccessor
{
public string? Email =>
httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.Email)
?? httpContextAccessor.HttpContext?.User.FindFirstValue("preferred_username");
?? httpContextAccessor.HttpContext?.User.FindFirstValue("preferred_username")
?? httpContextAccessor.HttpContext?.User.FindFirstValue("upn")
?? httpContextAccessor.HttpContext?.User.FindFirstValue("unique_name");
}
@@ -114,6 +114,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
var headType = r.HeadType ?? entity.HeadType;
var status = r.Status ?? entity.Status;
if (status == RouteStepStatus.None)
return Result.Fail<TemplateRouteStepDto>(new ValidationError("Статус обязателен: Approval или Execution."));
if (!route.ByProject && projectRole is not (null or ProjectRole.None))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
@@ -7,6 +7,7 @@ public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator<
{
public CreateTemplateRouteStepRequestValidator()
{
RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
@@ -17,6 +18,7 @@ public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator<
{
public UpdateTemplateRouteStepRequestValidator()
{
RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
@@ -226,6 +226,27 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
patched.IsFailed.ShouldBeTrue();
}
[Fact(DisplayName = "Патч со статусом None возвращает неуспешный результат")]
public async Task PatchAsync_StatusNone_ReturnsFailedResult()
{
// Arrange
var routeId = await CreateRouteAsync("Route to patch step status none", byProject: false);
var created = await _service.CreateAsync(
routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false),
CancellationToken.None);
// Action
var patched = await _service.PatchAsync(
routeId,
created.Value.Id,
new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: null, Position: null, ProjectRole: null, HeadType: null, Status: RouteStepStatus.None, AllowReassignToOtherApprovers: null),
CancellationToken.None);
// Assert
patched.IsFailed.ShouldBeTrue();
}
[Fact(DisplayName = "Явные ProjectRole.None и HeadType.None при патче считаются незаполненными, как и null")]
public async Task PatchAsync_ExplicitNoneValues_TreatedAsUnfilled()
{