diff --git a/api/src/Feature.Forms/Specifications/GetCitizenReportingFormsByIdsSpecification.cs b/api/src/Feature.Forms/Specifications/GetCitizenReportingFormsByIdsSpecification.cs new file mode 100644 index 000000000..8f98661d3 --- /dev/null +++ b/api/src/Feature.Forms/Specifications/GetCitizenReportingFormsByIdsSpecification.cs @@ -0,0 +1,15 @@ +using Vote.Monitor.Domain.Entities.FormBase; + +namespace Feature.Forms.Specifications; + +public sealed class GetCitizenReportingFormsByIdsSpecification : Specification +{ + public GetCitizenReportingFormsByIdsSpecification(Guid electionRoundId, Guid ngoId, IEnumerable formIds) + { + Query.Where(x => + x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.FormType == FormType.CitizenReporting + && formIds.Contains(x.Id)); + } +} diff --git a/api/src/Feature.Forms/Specifications/GetFormsByIdsSpecification.cs b/api/src/Feature.Forms/Specifications/GetFormsByIdsSpecification.cs new file mode 100644 index 000000000..da4a5f42a --- /dev/null +++ b/api/src/Feature.Forms/Specifications/GetFormsByIdsSpecification.cs @@ -0,0 +1,16 @@ +using Vote.Monitor.Domain.Entities.FormBase; + +namespace Feature.Forms.Specifications; + +public sealed class GetFormsByIdsSpecification : Specification +{ + public GetFormsByIdsSpecification(Guid electionRoundId, Guid ngoId, IEnumerable formIds) + { + Query.Where(x => + x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.FormType != FormType.CitizenReporting + && x.FormType != FormType.IncidentReporting + && formIds.Contains(x.Id)); + } +} diff --git a/api/src/Feature.Forms/Specifications/GetIncidentReportingFormsByIdsSpecification.cs b/api/src/Feature.Forms/Specifications/GetIncidentReportingFormsByIdsSpecification.cs new file mode 100644 index 000000000..f7921a7af --- /dev/null +++ b/api/src/Feature.Forms/Specifications/GetIncidentReportingFormsByIdsSpecification.cs @@ -0,0 +1,15 @@ +using Vote.Monitor.Domain.Entities.FormBase; + +namespace Feature.Forms.Specifications; + +public sealed class GetIncidentReportingFormsByIdsSpecification : Specification +{ + public GetIncidentReportingFormsByIdsSpecification(Guid electionRoundId, Guid ngoId, IEnumerable formIds) + { + Query.Where(x => + x.ElectionRoundId == electionRoundId + && x.MonitoringNgo.NgoId == ngoId + && x.FormType == FormType.IncidentReporting + && formIds.Contains(x.Id)); + } +} diff --git a/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Endpoint.cs b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Endpoint.cs new file mode 100644 index 000000000..8d127379a --- /dev/null +++ b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Endpoint.cs @@ -0,0 +1,69 @@ +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Feature.Forms.Specifications; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.Forms.UpdateCitizenReportingDisplayOrder; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository monitoringNgoRepository, + IRepository formsRepository) : Endpoint> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/citizen-reporting-forms:display-order"); + Description(x => x.Accepts()); + DontAutoTag(); + Options(x => x.WithTags("citizen-reporting-forms", "forms")); + Policies(PolicyNames.NgoAdminsOnly); + Summary(s => + { + s.Summary = + "Updates display order for published citizen reporting forms belonging to the citizen reporting monitoring NGO"; + }); + } + + public override async Task> ExecuteAsync(Request req, + CancellationToken ct) + { + var requirement = new CitizenReportingNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var formIds = req.Forms.Select(x => x.FormId).ToList(); + var forms = await formsRepository.ListAsync( + new GetCitizenReportingFormsByIdsSpecification(req.ElectionRoundId, req.NgoId, formIds), ct); + + if (forms.Count != formIds.Count) + { + return TypedResults.NotFound(); + } + + if (forms.Any(x => x.Status != FormStatus.Published)) + { + AddError("All forms must be published."); + return new ProblemDetails(ValidationFailures); + } + + var displayOrders = req.Forms.ToDictionary(x => x.FormId, x => x.DisplayOrder); + foreach (var form in forms) + { + form.UpdateDisplayOrder(displayOrders[form.Id]); + } + + await formsRepository.UpdateRangeAsync(forms, ct); + + var monitoringNgo = await monitoringNgoRepository.FirstOrDefaultAsync( + new GetMonitoringNgoSpecification(req.ElectionRoundId, req.NgoId), ct); + monitoringNgo!.UpdateFormVersion(); + await monitoringNgoRepository.UpdateAsync(monitoringNgo, ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Request.cs b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Request.cs new file mode 100644 index 000000000..9055a7a60 --- /dev/null +++ b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Request.cs @@ -0,0 +1,19 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.Forms.UpdateCitizenReportingDisplayOrder; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + + public List Forms { get; set; } = []; +} + +public class FormDisplayOrderModel +{ + public Guid FormId { get; set; } + public int DisplayOrder { get; set; } +} diff --git a/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Validator.cs b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Validator.cs new file mode 100644 index 000000000..95509bda3 --- /dev/null +++ b/api/src/Feature.Forms/UpdateCitizenReportingDisplayOrder/Validator.cs @@ -0,0 +1,20 @@ +namespace Feature.Forms.UpdateCitizenReportingDisplayOrder; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.Forms).NotEmpty(); + RuleFor(x => x.Forms) + .Must(forms => forms.Select(f => f.FormId).Distinct().Count() == forms.Count) + .WithMessage("Form ids must be unique."); + + RuleForEach(x => x.Forms).ChildRules(form => + { + form.RuleFor(x => x.FormId).NotEmpty(); + form.RuleFor(x => x.DisplayOrder).GreaterThanOrEqualTo(0); + }); + } +} diff --git a/api/src/Feature.Forms/UpdateDisplayOrder/Endpoint.cs b/api/src/Feature.Forms/UpdateDisplayOrder/Endpoint.cs new file mode 100644 index 000000000..d94cb59d9 --- /dev/null +++ b/api/src/Feature.Forms/UpdateDisplayOrder/Endpoint.cs @@ -0,0 +1,68 @@ +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Feature.Forms.Specifications; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.Forms.UpdateDisplayOrder; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository monitoringNgoRepository, + IRepository formsRepository) : Endpoint> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/forms:display-order"); + Description(x => x.Accepts()); + DontAutoTag(); + Options(x => x.WithTags("forms")); + Policies(PolicyNames.NgoAdminsOnly); + Summary(s => + { + s.Summary = "Updates display order for published forms belonging to the current monitoring NGO"; + }); + } + + public override async Task> ExecuteAsync(Request req, + CancellationToken ct) + { + var requirement = new MonitoringNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var formIds = req.Forms.Select(x => x.FormId).ToList(); + var forms = await formsRepository.ListAsync( + new GetFormsByIdsSpecification(req.ElectionRoundId, req.NgoId, formIds), ct); + + if (forms.Count != formIds.Count) + { + return TypedResults.NotFound(); + } + + if (forms.Any(x => x.Status != FormStatus.Published)) + { + AddError("All forms must be published."); + return new ProblemDetails(ValidationFailures); + } + + var displayOrders = req.Forms.ToDictionary(x => x.FormId, x => x.DisplayOrder); + foreach (var form in forms) + { + form.UpdateDisplayOrder(displayOrders[form.Id]); + } + + await formsRepository.UpdateRangeAsync(forms, ct); + + var monitoringNgo = await monitoringNgoRepository.FirstOrDefaultAsync( + new GetMonitoringNgoSpecification(req.ElectionRoundId, req.NgoId), ct); + monitoringNgo!.UpdateFormVersion(); + await monitoringNgoRepository.UpdateAsync(monitoringNgo, ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Forms/UpdateDisplayOrder/Request.cs b/api/src/Feature.Forms/UpdateDisplayOrder/Request.cs new file mode 100644 index 000000000..ceab217cf --- /dev/null +++ b/api/src/Feature.Forms/UpdateDisplayOrder/Request.cs @@ -0,0 +1,19 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.Forms.UpdateDisplayOrder; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + + public List Forms { get; set; } = []; +} + +public class FormDisplayOrderModel +{ + public Guid FormId { get; set; } + public int DisplayOrder { get; set; } +} diff --git a/api/src/Feature.Forms/UpdateDisplayOrder/Validator.cs b/api/src/Feature.Forms/UpdateDisplayOrder/Validator.cs new file mode 100644 index 000000000..462b4d7de --- /dev/null +++ b/api/src/Feature.Forms/UpdateDisplayOrder/Validator.cs @@ -0,0 +1,20 @@ +namespace Feature.Forms.UpdateDisplayOrder; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.Forms).NotEmpty(); + RuleFor(x => x.Forms) + .Must(forms => forms.Select(f => f.FormId).Distinct().Count() == forms.Count) + .WithMessage("Form ids must be unique."); + + RuleForEach(x => x.Forms).ChildRules(form => + { + form.RuleFor(x => x.FormId).NotEmpty(); + form.RuleFor(x => x.DisplayOrder).GreaterThanOrEqualTo(0); + }); + } +} diff --git a/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Endpoint.cs b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Endpoint.cs new file mode 100644 index 000000000..ce26d537f --- /dev/null +++ b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Endpoint.cs @@ -0,0 +1,69 @@ +using Authorization.Policies; +using Authorization.Policies.Requirements; +using Feature.Forms.Specifications; +using Microsoft.AspNetCore.Authorization; +using Vote.Monitor.Domain.Entities.FormAggregate; +using Vote.Monitor.Domain.Entities.MonitoringNgoAggregate; + +namespace Feature.Forms.UpdateIncidentReportingDisplayOrder; + +public class Endpoint( + IAuthorizationService authorizationService, + IRepository monitoringNgoRepository, + IRepository formsRepository) : Endpoint> +{ + public override void Configure() + { + Put("/api/election-rounds/{electionRoundId}/incident-reporting-forms:display-order"); + Description(x => x.Accepts()); + DontAutoTag(); + Options(x => x.WithTags("incident-reporting-forms", "forms")); + Policies(PolicyNames.NgoAdminsOnly); + Summary(s => + { + s.Summary = + "Updates display order for published incident reporting forms belonging to the current monitoring NGO"; + }); + } + + public override async Task> ExecuteAsync(Request req, + CancellationToken ct) + { + var requirement = new MonitoringNgoAdminRequirement(req.ElectionRoundId); + var authorizationResult = await authorizationService.AuthorizeAsync(User, requirement); + if (!authorizationResult.Succeeded) + { + return TypedResults.NotFound(); + } + + var formIds = req.Forms.Select(x => x.FormId).ToList(); + var forms = await formsRepository.ListAsync( + new GetIncidentReportingFormsByIdsSpecification(req.ElectionRoundId, req.NgoId, formIds), ct); + + if (forms.Count != formIds.Count) + { + return TypedResults.NotFound(); + } + + if (forms.Any(x => x.Status != FormStatus.Published)) + { + AddError("All forms must be published."); + return new ProblemDetails(ValidationFailures); + } + + var displayOrders = req.Forms.ToDictionary(x => x.FormId, x => x.DisplayOrder); + foreach (var form in forms) + { + form.UpdateDisplayOrder(displayOrders[form.Id]); + } + + await formsRepository.UpdateRangeAsync(forms, ct); + + var monitoringNgo = await monitoringNgoRepository.FirstOrDefaultAsync( + new GetMonitoringNgoSpecification(req.ElectionRoundId, req.NgoId), ct); + monitoringNgo!.UpdateFormVersion(); + await monitoringNgoRepository.UpdateAsync(monitoringNgo, ct); + + return TypedResults.NoContent(); + } +} diff --git a/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Request.cs b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Request.cs new file mode 100644 index 000000000..b3336dcc0 --- /dev/null +++ b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Request.cs @@ -0,0 +1,19 @@ +using Vote.Monitor.Core.Security; + +namespace Feature.Forms.UpdateIncidentReportingDisplayOrder; + +public class Request +{ + public Guid ElectionRoundId { get; set; } + + [FromClaim(ApplicationClaimTypes.NgoId)] + public Guid NgoId { get; set; } + + public List Forms { get; set; } = []; +} + +public class FormDisplayOrderModel +{ + public Guid FormId { get; set; } + public int DisplayOrder { get; set; } +} diff --git a/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Validator.cs b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Validator.cs new file mode 100644 index 000000000..8581f15e4 --- /dev/null +++ b/api/src/Feature.Forms/UpdateIncidentReportingDisplayOrder/Validator.cs @@ -0,0 +1,20 @@ +namespace Feature.Forms.UpdateIncidentReportingDisplayOrder; + +public class Validator : Validator +{ + public Validator() + { + RuleFor(x => x.ElectionRoundId).NotEmpty(); + RuleFor(x => x.NgoId).NotEmpty(); + RuleFor(x => x.Forms).NotEmpty(); + RuleFor(x => x.Forms) + .Must(forms => forms.Select(f => f.FormId).Distinct().Count() == forms.Count) + .WithMessage("Form ids must be unique."); + + RuleForEach(x => x.Forms).ChildRules(form => + { + form.RuleFor(x => x.FormId).NotEmpty(); + form.RuleFor(x => x.DisplayOrder).GreaterThanOrEqualTo(0); + }); + } +} diff --git a/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs b/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs index 8dd5d2acb..601697453 100644 --- a/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs +++ b/api/src/Vote.Monitor.Domain/Entities/FormAggregate/Form.cs @@ -23,6 +23,11 @@ public class Form : BaseForm public ElectionRound ElectionRound { get; private set; } public int DisplayOrder { get; private set; } + public void UpdateDisplayOrder(int displayOrder) + { + DisplayOrder = displayOrder; + } + private Form( ElectionRound electionRound, MonitoringNgo monitoringNgo, diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateCitizenReportingDisplayOrderRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateCitizenReportingDisplayOrderRequestValidatorTests.cs new file mode 100644 index 000000000..eb116dacd --- /dev/null +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateCitizenReportingDisplayOrderRequestValidatorTests.cs @@ -0,0 +1,88 @@ +namespace Feature.Forms.UnitTests.ValidatorTests; + +public class UpdateCitizenReportingDisplayOrderRequestValidatorTests +{ + private readonly UpdateCitizenReportingDisplayOrder.Validator _validator = new(); + + [Fact] + public void Validation_ShouldFail_When_ElectionRoundId_Empty() + { + var request = new UpdateCitizenReportingDisplayOrder.Request { ElectionRoundId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Validation_ShouldFail_When_NgoId_Empty() + { + var request = new UpdateCitizenReportingDisplayOrder.Request { NgoId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.NgoId); + } + + [Fact] + public void Validation_ShouldFail_When_Forms_Empty() + { + var request = new UpdateCitizenReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = [] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldFail_When_FormIds_NotUnique() + { + var formId = Guid.NewGuid(); + var request = new UpdateCitizenReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateCitizenReportingDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 1 }, + new UpdateCitizenReportingDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 2 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldPass_When_ValidRequest() + { + var request = new UpdateCitizenReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateCitizenReportingDisplayOrder.FormDisplayOrderModel + { + FormId = Guid.NewGuid(), + DisplayOrder = 0 + }, + new UpdateCitizenReportingDisplayOrder.FormDisplayOrderModel + { + FormId = Guid.NewGuid(), + DisplayOrder = 1 + } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldNotHaveAnyValidationErrors(); + } +} diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateDisplayOrderRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateDisplayOrderRequestValidatorTests.cs new file mode 100644 index 000000000..fd1ff9eaa --- /dev/null +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateDisplayOrderRequestValidatorTests.cs @@ -0,0 +1,116 @@ +namespace Feature.Forms.UnitTests.ValidatorTests; + +public class UpdateDisplayOrderRequestValidatorTests +{ + private readonly UpdateDisplayOrder.Validator _validator = new(); + + [Fact] + public void Validation_ShouldFail_When_ElectionRoundId_Empty() + { + var request = new UpdateDisplayOrder.Request { ElectionRoundId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Validation_ShouldFail_When_NgoId_Empty() + { + var request = new UpdateDisplayOrder.Request { NgoId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.NgoId); + } + + [Fact] + public void Validation_ShouldFail_When_Forms_Empty() + { + var request = new UpdateDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = [] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldFail_When_FormIds_NotUnique() + { + var formId = Guid.NewGuid(); + var request = new UpdateDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 1 }, + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 2 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldFail_When_FormId_Empty() + { + var request = new UpdateDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = Guid.Empty, DisplayOrder = 1 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor("Forms[0].FormId"); + } + + [Fact] + public void Validation_ShouldFail_When_DisplayOrder_Negative() + { + var request = new UpdateDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = Guid.NewGuid(), DisplayOrder = -1 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor("Forms[0].DisplayOrder"); + } + + [Fact] + public void Validation_ShouldPass_When_ValidRequest() + { + var request = new UpdateDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = Guid.NewGuid(), DisplayOrder = 0 }, + new UpdateDisplayOrder.FormDisplayOrderModel { FormId = Guid.NewGuid(), DisplayOrder = 1 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldNotHaveAnyValidationErrors(); + } +} diff --git a/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateIncidentReportingDisplayOrderRequestValidatorTests.cs b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateIncidentReportingDisplayOrderRequestValidatorTests.cs new file mode 100644 index 000000000..7952d14de --- /dev/null +++ b/api/tests/Feature.Forms.UnitTests/ValidatorTests/UpdateIncidentReportingDisplayOrderRequestValidatorTests.cs @@ -0,0 +1,88 @@ +namespace Feature.Forms.UnitTests.ValidatorTests; + +public class UpdateIncidentReportingDisplayOrderRequestValidatorTests +{ + private readonly UpdateIncidentReportingDisplayOrder.Validator _validator = new(); + + [Fact] + public void Validation_ShouldFail_When_ElectionRoundId_Empty() + { + var request = new UpdateIncidentReportingDisplayOrder.Request { ElectionRoundId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.ElectionRoundId); + } + + [Fact] + public void Validation_ShouldFail_When_NgoId_Empty() + { + var request = new UpdateIncidentReportingDisplayOrder.Request { NgoId = Guid.Empty }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.NgoId); + } + + [Fact] + public void Validation_ShouldFail_When_Forms_Empty() + { + var request = new UpdateIncidentReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = [] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldFail_When_FormIds_NotUnique() + { + var formId = Guid.NewGuid(); + var request = new UpdateIncidentReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateIncidentReportingDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 1 }, + new UpdateIncidentReportingDisplayOrder.FormDisplayOrderModel { FormId = formId, DisplayOrder = 2 } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldHaveValidationErrorFor(x => x.Forms); + } + + [Fact] + public void Validation_ShouldPass_When_ValidRequest() + { + var request = new UpdateIncidentReportingDisplayOrder.Request + { + ElectionRoundId = Guid.NewGuid(), + NgoId = Guid.NewGuid(), + Forms = + [ + new UpdateIncidentReportingDisplayOrder.FormDisplayOrderModel + { + FormId = Guid.NewGuid(), + DisplayOrder = 0 + }, + new UpdateIncidentReportingDisplayOrder.FormDisplayOrderModel + { + FormId = Guid.NewGuid(), + DisplayOrder = 1 + } + ] + }; + + var result = _validator.TestValidate(request); + + result.ShouldNotHaveAnyValidationErrors(); + } +}