Skip to content

Commit

Permalink
VIH-10126 update default value for IsMultiDayHearing and AudioRecordi…
Browse files Browse the repository at this point in the history
…ngRequired (#713)

Update swagger doc to use fluent validation rules
  • Loading branch information
shaed-parkar authored Sep 11, 2023
1 parent ff1e1bd commit 754513f
Show file tree
Hide file tree
Showing 23 changed files with 129 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using BookingsApi.Contract.V1.Requests;

namespace BookingsApi.Contract.V2.Requests
Expand Down Expand Up @@ -71,8 +72,13 @@ public BookNewHearingRequestV2()
/// <summary>
/// Gets or sets the audio recording required flag, value true is indicated that recording is required, otherwise false
/// </summary>
public bool AudioRecordingRequired { get; set; }
[DefaultValue(false)]
public bool AudioRecordingRequired { get; set; } = false;

/// <summary>
/// Is the booking part of a multi-day hearing?
/// </summary>
[DefaultValue(false)]
public bool IsMultiDayHearing { get; set; } = false;

public List<EndpointRequestV2> Endpoints { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ namespace BookingsApi.Contract.V2.Requests
{
public class LinkedParticipantRequestV2
{
/// <summary>
/// The contact email for the participant to create a link for
/// </summary>
public string ParticipantContactEmail { get; set; }

/// <summary>
/// The contact email for the participant to create a link with
/// </summary>
public string LinkedParticipantContactEmail { get; set; }
public LinkedParticipantTypeV2 TypeV2 { get; set; }

/// <summary>
/// The type of link to create
/// </summary>
public LinkedParticipantTypeV2 Type { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public void Should_throw_exception_on_taking_more_than_available()
[TestCase("637330171125319309", 13u, 4u, "3373")]
[TestCase("607330171125309309", 13u, 4u, "3370")]
public void Should_return_correct_ticks(string source, uint skip, uint take, string expected)

{
var result = _randomGenerator.GetWeakDeterministic(long.Parse(source), skip, take);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ namespace BookingsApi.UnitTests.Validation.V1
public class UpdateHearingRequestValidationTests
{
private UpdateHearingRequestValidation _validator;
private DateTime _scheduledDateTime;
private VideoHearing _hearing;

[SetUp]
public void SetUp()
{
_scheduledDateTime = DateTime.Today.AddDays(5).AddHours(10).AddMinutes(30);
_hearing = new VideoHearingBuilder().WithScheduledDateTime(_scheduledDateTime).Build();
_validator = new UpdateHearingRequestValidation(_hearing);
_validator = new UpdateHearingRequestValidation();
}

[Test]
Expand All @@ -40,7 +36,7 @@ public async Task Should_return_missing_hearing_venue_name_error()
var result = await _validator.ValidateAsync(request);

// assert
result.Errors.Any(x => x.ErrorMessage == UpdateHearingRequestValidation.NoHearingVenueNameErrorMessage)
result.Errors.Exists(x => x.ErrorMessage == UpdateHearingRequestValidation.NoHearingVenueNameErrorMessage)
.Should().BeTrue();
}

Expand All @@ -53,7 +49,7 @@ public async Task Should_return_hearing_schedule_date_time_in_past_error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == UpdateHearingRequestValidation.ScheduleDateTimeInPastErrorMessage)
result.Errors.Exists(x => x.ErrorMessage == UpdateHearingRequestValidation.ScheduleDateTimeInPastErrorMessage)
.Should().BeTrue();
}

Expand All @@ -66,7 +62,7 @@ public async Task Should_return_hearing_schedule_duration_error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == UpdateHearingRequestValidation.NoScheduleDurationErrorMessage)
result.Errors.Exists(x => x.ErrorMessage == UpdateHearingRequestValidation.NoScheduleDurationErrorMessage)
.Should().BeTrue();
}

Expand All @@ -79,7 +75,7 @@ public async Task Should_return_missing_updated_by_error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == UpdateHearingRequestValidation.NoUpdatedByErrorMessage)
result.Errors.Exists(x => x.ErrorMessage == UpdateHearingRequestValidation.NoUpdatedByErrorMessage)
.Should().BeTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public async Task Should_return_missing_display_name_error()
var result = await _validator.ValidateAsync(request);

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDisplayNameErrorMessage).Should().BeTrue();
result.Errors.Exists(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDisplayNameErrorMessage).Should().BeTrue();
}

[Test]
Expand All @@ -56,7 +55,7 @@ public async Task Should_return_missing_defence_advocate_contact_email_error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDefenceAdvocateContactEmailError).Should().BeTrue();
result.Errors.Exists(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDefenceAdvocateContactEmailError).Should().BeTrue();
}

[Test]
Expand All @@ -72,6 +71,6 @@ public async Task Should_return_invalid_defence_advocate_contact_email_error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDefenceAdvocateContactEmailError).Should().BeTrue();
result.Errors.Exists(x => x.ErrorMessage == EndpointRequestValidationV2.InvalidDefenceAdvocateContactEmailError).Should().BeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public async Task Should_Return_Missing_Participant_Email_Error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.NoParticipantEmail)
result.Errors.Exists(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.NoParticipantEmail)
.Should().BeTrue();
}

Expand All @@ -47,20 +47,20 @@ public async Task Should_Return_Missing_LinkedParticipant_Email_Error()

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.NoLinkedParticipantEmail)
result.Errors.Exists(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.NoLinkedParticipantEmail)
.Should().BeTrue();
}

[Test]
public async Task Should_Return_Invalid_LinkedParticipant_Type_Error()
{
_requestV2.TypeV2 = (LinkedParticipantTypeV2)456789;
_requestV2.Type = (LinkedParticipantTypeV2)456789;

var result = await _validator.ValidateAsync(_requestV2);

result.IsValid.Should().BeFalse();
result.Errors.Count.Should().Be(1);
result.Errors.Any(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.InvalidType)
result.Errors.Exists(x => x.ErrorMessage == LinkedParticipantRequestValidationV2.InvalidType)
.Should().BeTrue();
}

Expand All @@ -69,7 +69,7 @@ private LinkedParticipantRequestV2 BuildRequest()
return Builder<LinkedParticipantRequestV2>.CreateNew()
.With(x => x.ParticipantContactEmail = "[email protected]")
.With(x => x.LinkedParticipantContactEmail = "[email protected]")
.With(x => x.TypeV2 = LinkedParticipantTypeV2.Interpreter)
.With(x => x.Type = LinkedParticipantTypeV2.Interpreter)
.Build();
}
}
Expand Down
39 changes: 27 additions & 12 deletions BookingsApi/BookingsApi/ConfigureServicesExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.AspNetCore.Mvc.Versioning;
using Newtonsoft.Json.Converters;
using NSwag.Generation.AspNetCore;
using ZymLabs.NSwag.FluentValidation;


namespace BookingsApi
Expand All @@ -35,30 +36,38 @@ public static IServiceCollection AddApiVersioning(this IServiceCollection servic
}
public static IServiceCollection AddSwagger(this IServiceCollection services)
{
services.AddScoped(provider =>
{
var validationRules = provider.GetService<IEnumerable<FluentValidationRule>>();
var loggerFactory = provider.GetService<ILoggerFactory>();

return new FluentValidationSchemaProcessor(provider, validationRules, loggerFactory);
});

var apiVersionDescription = services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();
foreach (var groupName in apiVersionDescription.ApiVersionDescriptions.Select(x=> x.GroupName))
{
services.AddOpenApiDocument((configure) =>
services.AddOpenApiDocument((configure, servicesProvider) =>
{
ConfigureSwaggerForVersion(configure, groupName, new[] { groupName });
ConfigureSwaggerForVersion(configure, groupName, new[] { groupName }, servicesProvider);
});
}

// to build a single a client for all versions of the api, create one document with all the groups
var groupNames = apiVersionDescription.ApiVersionDescriptions.Select(x => x.GroupName).ToArray();
services.AddOpenApiDocument((configure) =>
services.AddOpenApiDocument((configure, servicesProvider) =>
{
ConfigureSwaggerForVersion(configure, "all", groupNames);
ConfigureSwaggerForVersion(configure, "all", groupNames, servicesProvider);
});
return services;
}

private static void ConfigureSwaggerForVersion(AspNetCoreOpenApiDocumentGeneratorSettings configure,
string documentName, string[] apiGroupNames)
private static void ConfigureSwaggerForVersion(AspNetCoreOpenApiDocumentGeneratorSettings settings,
string documentName, string[] apiGroupNames, IServiceProvider serviceProvider)
{
configure.DocumentName = documentName;
configure.ApiGroupNames = apiGroupNames;
configure.AddSecurity("JWT", Enumerable.Empty<string>(),
settings.DocumentName = documentName;
settings.ApiGroupNames = apiGroupNames;
settings.AddSecurity("JWT", Enumerable.Empty<string>(),
new OpenApiSecurityScheme
{
Type = OpenApiSecuritySchemeType.ApiKey,
Expand All @@ -67,9 +76,15 @@ private static void ConfigureSwaggerForVersion(AspNetCoreOpenApiDocumentGenerato
Description = "Type into the textbox: Bearer {your JWT token}.",
Scheme = "bearer"
});
configure.Title = "Bookings API";
configure.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT"));
configure.OperationProcessors.Add(new AuthResponseOperationProcessor());
settings.Title = "Bookings API";
settings.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT"));
settings.OperationProcessors.Add(new AuthResponseOperationProcessor());

var fluentValidationSchemaProcessor = serviceProvider.CreateScope().ServiceProvider.GetService<FluentValidationSchemaProcessor>();

// Add the fluent validations schema processor
settings.SchemaProcessors.Add(fluentValidationSchemaProcessor);

}

public static IServiceCollection AddCustomTypes(this IServiceCollection services)
Expand Down
11 changes: 10 additions & 1 deletion BookingsApi/BookingsApi/Controllers/V1/HearingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,16 @@ public async Task<IActionResult> UpdateHearingDetails(Guid hearingId, [FromBody]
return NotFound();
}

var result = new UpdateHearingRequestValidation(videoHearing).Validate(request);
var result = new UpdateHearingRequestValidation().Validate(request);

if (videoHearing.ScheduledDateTime != request.ScheduledDateTime &&
request.ScheduledDateTime < DateTime.UtcNow) // ignore if the scheduled date time has not changed
{
ModelState.AddModelError(nameof(request.ScheduledDateTime),
UpdateHearingRequestValidation.ScheduleDateTimeInPastErrorMessage);

}

if (!result.IsValid)
{
ModelState.AddFluentValidationErrors(result.Errors);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<IActionResult> AddParticipantsToHearing(Guid hearingId, [FromB
var caseTypeQuery = new GetCaseRolesForCaseServiceQuery(videoHearing.CaseType.ServiceId);
var caseType = await _queryHandler.Handle<GetCaseRolesForCaseServiceQuery, CaseType>(caseTypeQuery);

var dataValidationResult = await new AddParticipantsToHearingRequestDataValidationV2(caseType).ValidateAsync(request);
var dataValidationResult = await new AddParticipantsToHearingRequestRefDataValidationV2(caseType).ValidateAsync(request);
if (!dataValidationResult.IsValid)
{
ModelState.AddFluentValidationErrors(dataValidationResult.Errors);
Expand Down Expand Up @@ -124,7 +124,7 @@ public async Task<IActionResult> UpdateHearingParticipants(Guid hearingId, [From
var caseTypeQuery = new GetCaseRolesForCaseServiceQuery(videoHearing.CaseType.ServiceId);
var hearingRoles = await _queryHandler.Handle<GetHearingRolesQuery, List<HearingRole>>(new GetHearingRolesQuery());
var caseType = await _queryHandler.Handle<GetCaseRolesForCaseServiceQuery, CaseType>(caseTypeQuery);
var dataValidationResult = await new UpdateHearingParticipantsRequestDataValidationV2(caseType, hearingRoles).ValidateAsync(request);
var dataValidationResult = await new UpdateHearingParticipantsRequestRefDataValidationV2(caseType, hearingRoles).ValidateAsync(request);
if (!dataValidationResult.IsValid)
{
ModelState.AddFluentValidationErrors(dataValidationResult.Errors);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<IActionResult> BookNewHearingWithCode(BookNewHearingRequestV2
var caseType = await _queryHandler.Handle<GetCaseRolesForCaseServiceQuery, CaseType>(new GetCaseRolesForCaseServiceQuery(request.ServiceId));
var hearingVenue = await GetHearingVenue(request.HearingVenueCode);

var dataValidationResult = await new BookNewHearingRequestDataValidationV2(caseType, hearingVenue, hearingRoles).ValidateAsync(request);
var dataValidationResult = await new BookNewHearingRequestRefDataValidationV2(caseType, hearingVenue, hearingRoles).ValidateAsync(request);
if (!dataValidationResult.IsValid)
{
ModelState.AddFluentValidationErrors(dataValidationResult.Errors);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static List<LinkedParticipantDto> MapToDto(List<LinkedParticipantRequestV
foreach (var request in requests)
{
var dto = new LinkedParticipantDto(request.ParticipantContactEmail,
request.LinkedParticipantContactEmail, request.TypeV2.MapToDomainEnum());
request.LinkedParticipantContactEmail, request.Type.MapToDomainEnum());
listOfDtos.Add(dto);
}
}
Expand Down
12 changes: 11 additions & 1 deletion BookingsApi/BookingsApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Reflection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -12,6 +13,8 @@
using Microsoft.Extensions.Hosting;
using BookingsApi.Contract.V1.Configuration;
using BookingsApi.Domain.Configuration;
using BookingsApi.Validations.Common;
using FluentValidation;

namespace BookingsApi
{
Expand Down Expand Up @@ -39,6 +42,13 @@ public void ConfigureServices(IServiceCollection services)

services.AddApplicationInsightsTelemetry();

services.AddValidatorsFromAssemblyContaining<RepresentativeValidation>(ServiceLifetime.Scoped,
filter =>
{
var valid = !filter.ValidatorType.GetInterfaces().ToList().Contains(typeof(IRefDataInputValidator));
return valid;
});

services.AddSwagger();
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
Expand Down
10 changes: 10 additions & 0 deletions BookingsApi/BookingsApi/Validations/Common/DataInputValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using FluentValidation;

namespace BookingsApi.Validations.Common;

public interface IRefDataInputValidator{}

public abstract class RefDataInputValidatorValidator<T> : AbstractValidator<T>, IRefDataInputValidator
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public JudiciaryParticipantRequestValidation()
{
RuleFor(x => x.PersonalCode).NotEmpty().WithMessage(NoPersonalCodeErrorMessage);
RuleFor(x => x.DisplayName).NotEmpty().WithMessage(NoDisplayNameErrorMessage);
RuleFor(x => x.HearingRoleCode).IsInEnum();
RuleFor(x => x.HearingRoleCode).NotEmpty().IsInEnum();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,19 @@ public class UpdateHearingRequestValidation : AbstractValidator<UpdateHearingReq
public static readonly string NoScheduleDurationErrorMessage = "Schedule duration must be greater than 0";
public static readonly string NoUpdatedByErrorMessage = "UpdatedBy is missing";

public UpdateHearingRequestValidation(VideoHearing videoHearing)
public UpdateHearingRequestValidation()
{
RuleFor(x => x.HearingVenueName)
.NotEmpty().WithMessage(NoHearingVenueNameErrorMessage);

RuleFor(x => x.ScheduledDateTime).Custom((dateTime, context) =>
{
if(videoHearing.ScheduledDateTime == dateTime) return; // ignore if the scheduled date time has not changed
if (dateTime < DateTime.UtcNow)
if (dateTime <= DateTime.MinValue)
{
context.AddFailure(ScheduleDateTimeInPastErrorMessage);
}
});

RuleFor(x => x.ScheduledDuration)
.GreaterThan(0).WithMessage(NoScheduleDurationErrorMessage);

Expand Down
Loading

0 comments on commit 754513f

Please sign in to comment.