diff --git a/NotificationApi/NotificationApi.AcceptanceTests/ApiTests/AcApiTest.cs b/NotificationApi/NotificationApi.AcceptanceTests/ApiTests/AcApiTest.cs index da48f1ac..356e3166 100644 --- a/NotificationApi/NotificationApi.AcceptanceTests/ApiTests/AcApiTest.cs +++ b/NotificationApi/NotificationApi.AcceptanceTests/ApiTests/AcApiTest.cs @@ -59,7 +59,7 @@ private Task GenerateApiToken() private string GenerateCallbackToken() { - return new CustomJwtTokenProvider().GenerateTokenForCallbackEndpoint(_notifyConfiguration.CallbackSecret, 60); + return CustomJwtTokenProvider.GenerateTokenForCallbackEndpoint(_notifyConfiguration.CallbackSecret, 60); } private void RegisterSettings() diff --git a/NotificationApi/NotificationApi.Client/NotificationApiClient.cs b/NotificationApi/NotificationApi.Client/NotificationApiClient.cs index cec8d7fe..8308d56a 100644 --- a/NotificationApi/NotificationApi.Client/NotificationApiClient.cs +++ b/NotificationApi/NotificationApi.Client/NotificationApiClient.cs @@ -29,6 +29,19 @@ namespace NotificationApi.Client [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.7.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial interface INotificationApiClient { + /// + /// Process callbacks from Gov Notify API + /// + /// A server side error occurred. + System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Process callbacks from Gov Notify API + /// + /// A server side error occurred. + System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest, System.Threading.CancellationToken cancellationToken); + /// A server side error occurred. System.Threading.Tasks.Task GetTemplateByNotificationTypeAsync(NotificationType notificationType); @@ -45,19 +58,6 @@ public partial interface INotificationApiClient [System.Obsolete] System.Threading.Tasks.Task CreateNewNotificationAsync(AddNotificationRequest request, System.Threading.CancellationToken cancellationToken); - /// - /// Process callbacks from Gov Notify API - /// - /// A server side error occurred. - System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest); - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Process callbacks from Gov Notify API - /// - /// A server side error occurred. - System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest, System.Threading.CancellationToken cancellationToken); - /// /// Send an email with information about their new hearings account /// @@ -234,18 +234,24 @@ public string BaseUrl partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// + /// Process callbacks from Gov Notify API + /// /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetTemplateByNotificationTypeAsync(NotificationType notificationType) + public virtual System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest) { - return GetTemplateByNotificationTypeAsync(notificationType, System.Threading.CancellationToken.None); + return HandleCallbackAsync(notificationCallbackRequest, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Process callbacks from Gov Notify API + /// /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetTemplateByNotificationTypeAsync(NotificationType notificationType, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest, System.Threading.CancellationToken cancellationToken) { - if (notificationType == null) - throw new System.ArgumentNullException("notificationType"); + if (notificationCallbackRequest == null) + throw new System.ArgumentNullException("notificationCallbackRequest"); var client_ = _httpClient; var disposeClient_ = false; @@ -253,14 +259,16 @@ public virtual async System.Threading.Tasks.Task G { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(notificationCallbackRequest, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "notification/template/{notificationType}" - urlBuilder_.Append("notification/template/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(notificationType, System.Globalization.CultureInfo.InvariantCulture))); + // Operation Path: "notification/callback" + urlBuilder_.Append("notification/callback"); PrepareRequest(client_, request_, urlBuilder_); @@ -297,12 +305,7 @@ public virtual async System.Threading.Tasks.Task G else if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new NotificationApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; + return; } else if (status_ == 400) @@ -315,6 +318,12 @@ public virtual async System.Threading.Tasks.Task G throw new NotificationApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else + if (status_ == 401) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new NotificationApiException("Unauthorized", status_, responseText_, headers_, null); + } + else { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new NotificationApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); @@ -335,19 +344,17 @@ public virtual async System.Threading.Tasks.Task G } /// A server side error occurred. - [System.Obsolete] - public virtual System.Threading.Tasks.Task CreateNewNotificationAsync(AddNotificationRequest request) + public virtual System.Threading.Tasks.Task GetTemplateByNotificationTypeAsync(NotificationType notificationType) { - return CreateNewNotificationAsync(request, System.Threading.CancellationToken.None); + return GetTemplateByNotificationTypeAsync(notificationType, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// A server side error occurred. - [System.Obsolete] - public virtual async System.Threading.Tasks.Task CreateNewNotificationAsync(AddNotificationRequest request, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetTemplateByNotificationTypeAsync(NotificationType notificationType, System.Threading.CancellationToken cancellationToken) { - if (request == null) - throw new System.ArgumentNullException("request"); + if (notificationType == null) + throw new System.ArgumentNullException("notificationType"); var client_ = _httpClient; var disposeClient_ = false; @@ -355,16 +362,14 @@ public virtual async System.Threading.Tasks.Task CreateNewNotificationAsync(AddN { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(request, _settings.Value); - var content_ = new System.Net.Http.StringContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "notification" - urlBuilder_.Append("notification"); + // Operation Path: "notification/template/{notificationType}" + urlBuilder_.Append("notification/template/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(notificationType, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -401,7 +406,12 @@ public virtual async System.Threading.Tasks.Task CreateNewNotificationAsync(AddN else if (status_ == 200) { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new NotificationApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else if (status_ == 400) @@ -433,24 +443,20 @@ public virtual async System.Threading.Tasks.Task CreateNewNotificationAsync(AddN } } - /// - /// Process callbacks from Gov Notify API - /// /// A server side error occurred. - public virtual System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest) + [System.Obsolete] + public virtual System.Threading.Tasks.Task CreateNewNotificationAsync(AddNotificationRequest request) { - return HandleCallbackAsync(notificationCallbackRequest, System.Threading.CancellationToken.None); + return CreateNewNotificationAsync(request, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Process callbacks from Gov Notify API - /// /// A server side error occurred. - public virtual async System.Threading.Tasks.Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest, System.Threading.CancellationToken cancellationToken) + [System.Obsolete] + public virtual async System.Threading.Tasks.Task CreateNewNotificationAsync(AddNotificationRequest request, System.Threading.CancellationToken cancellationToken) { - if (notificationCallbackRequest == null) - throw new System.ArgumentNullException("notificationCallbackRequest"); + if (request == null) + throw new System.ArgumentNullException("request"); var client_ = _httpClient; var disposeClient_ = false; @@ -458,7 +464,7 @@ public virtual async System.Threading.Tasks.Task HandleCallbackAsync(Notificatio { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(notificationCallbackRequest, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(request, _settings.Value); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -466,8 +472,8 @@ public virtual async System.Threading.Tasks.Task HandleCallbackAsync(Notificatio var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "notification/callback" - urlBuilder_.Append("notification/callback"); + // Operation Path: "notification" + urlBuilder_.Append("notification"); PrepareRequest(client_, request_, urlBuilder_); @@ -517,12 +523,6 @@ public virtual async System.Threading.Tasks.Task HandleCallbackAsync(Notificatio throw new NotificationApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new NotificationApiException("Unauthorized", status_, responseText_, headers_, null); - } - else { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new NotificationApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); @@ -1735,4 +1735,4 @@ public NotificationApiException(string message, int statusCode, string response, #pragma warning restore 3016 #pragma warning restore 8603 #pragma warning restore 8604 -#pragma warning restore 8625 \ No newline at end of file +#pragma warning restore 8625 diff --git a/NotificationApi/NotificationApi.Common/BadRequestException.cs b/NotificationApi/NotificationApi.Common/BadRequestException.cs index 955a672d..fef2ba0e 100644 --- a/NotificationApi/NotificationApi.Common/BadRequestException.cs +++ b/NotificationApi/NotificationApi.Common/BadRequestException.cs @@ -1,16 +1,8 @@ using System; -using System.Runtime.Serialization; -namespace NotificationApi.Common -{ - /// - /// Exception to throw when input data passed downstream from the api input is in an invalid format - /// - [Serializable] - public class BadRequestException : Exception - { - protected BadRequestException(SerializationInfo info, StreamingContext context) : base(info, context) { } +namespace NotificationApi.Common; - public BadRequestException(string message) : base(message) { } - } -} +/// +/// Exception to throw when input data passed downstream from the api input is in an invalid format +/// +public class BadRequestException(string message) : Exception(message); diff --git a/NotificationApi/NotificationApi.Common/Helpers/LoggingDataExtractor.cs b/NotificationApi/NotificationApi.Common/Helpers/LoggingDataExtractor.cs index 3be994cf..f382c32d 100644 --- a/NotificationApi/NotificationApi.Common/Helpers/LoggingDataExtractor.cs +++ b/NotificationApi/NotificationApi.Common/Helpers/LoggingDataExtractor.cs @@ -27,6 +27,13 @@ public Dictionary ConvertToDictionary(object input, string path return result; } + IterateTypeProperties(input, path, debth, type, result); + + return result; + } + + private void IterateTypeProperties(object input, string path, int debth, Type type, Dictionary result) + { foreach (var property in type.GetProperties()) { var value = property.GetValue(input); @@ -48,11 +55,9 @@ public Dictionary ConvertToDictionary(object input, string path result.Add(GetPath(path, property.Name), value); } } - - return result; } - - private string GetPath(string path, string property) => $"{path}{(string.IsNullOrEmpty(path) ? string.Empty : ".")}{property}"; + + private static string GetPath(string path, string property) => $"{path}{(string.IsNullOrEmpty(path) ? string.Empty : ".")}{property}"; /// /// Pass in type to see if we should recuse deeper @@ -60,6 +65,6 @@ public Dictionary ConvertToDictionary(object input, string path /// /// /// - private bool IsCustomType(Type type) => !type.IsEnum && type.AssemblyQualifiedName.StartsWith(this.GetType().AssemblyQualifiedName.Split('.')[0]); + private bool IsCustomType(Type type) => !type.IsEnum && type.AssemblyQualifiedName!.StartsWith(GetType().AssemblyQualifiedName!.Split('.')[0]); } } diff --git a/NotificationApi/NotificationApi.Contract/IAssemblyReference.cs b/NotificationApi/NotificationApi.Contract/IAssemblyReference.cs deleted file mode 100644 index 5566c6fb..00000000 --- a/NotificationApi/NotificationApi.Contract/IAssemblyReference.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NotificationApi.Contract -{ - public interface IAssemblyReference - { - } -} diff --git a/NotificationApi/NotificationApi.Contract/MessageType.cs b/NotificationApi/NotificationApi.Contract/MessageType.cs index 6ad240bf..cf27df6d 100644 --- a/NotificationApi/NotificationApi.Contract/MessageType.cs +++ b/NotificationApi/NotificationApi.Contract/MessageType.cs @@ -1,9 +1,7 @@ -namespace NotificationApi.Contract +namespace NotificationApi.Contract; + +public enum MessageType { - // Public contract for NotificationApi.Domain.Enums.MessageType - public enum MessageType - { - Email = 1, - SMS = 2 - } + Email = 1, + SMS = 2 } diff --git a/NotificationApi/NotificationApi.Contract/NotificationType.cs b/NotificationApi/NotificationApi.Contract/NotificationType.cs index bed984fe..5c6cbdde 100644 --- a/NotificationApi/NotificationApi.Contract/NotificationType.cs +++ b/NotificationApi/NotificationApi.Contract/NotificationType.cs @@ -1,61 +1,59 @@ -namespace NotificationApi.Contract +namespace NotificationApi.Contract; + +public enum NotificationType { - // Public contract for NotificationApi.Domain.Enums.NotificationType - public enum NotificationType - { - CreateIndividual = 1, - CreateRepresentative = 2, - PasswordReset = 3, - HearingConfirmationLip = 4, - HearingConfirmationRepresentative = 5, - HearingConfirmationJudge = 6, - HearingConfirmationJoh = 7, - HearingConfirmationLipMultiDay = 8, - HearingConfirmationRepresentativeMultiDay = 9, - HearingConfirmationJudgeMultiDay = 10, - HearingConfirmationJohMultiDay = 11, - HearingAmendmentLip = 12, - HearingAmendmentRepresentative = 13, - HearingAmendmentJudge = 14, - HearingAmendmentJoh = 15, - HearingReminderLip = 16, - HearingReminderRepresentative = 17, - HearingReminderJoh = 18, - HearingConfirmationEJudJudge = 19, - HearingConfirmationEJudJudgeMultiDay = 20, - HearingAmendmentEJudJudge = 21, - HearingAmendmentEJudJoh = 22, - HearingReminderEJudJoh = 23, - HearingConfirmationEJudJoh = 24, - HearingConfirmationEJudJohMultiDay = 25, - EJudJohDemoOrTest = 26, - EJudJudgeDemoOrTest = 27, - JudgeDemoOrTest = 28, - ParticipantDemoOrTest = 29, - TelephoneHearingConfirmation = 30, - TelephoneHearingConfirmationMultiDay = 31, - CreateStaffMember = 32, - HearingAmendmentStaffMember = 33, - HearingConfirmationStaffMember = 34, - HearingConfirmationStaffMemberMultiDay = 35, - StaffMemberDemoOrTest = 36, - NewHearingReminderLIP = 37, - NewHearingReminderRepresentative = 38, - NewHearingReminderJOH = 39, - NewHearingReminderEJudJoh = 40, - NewUserLipWelcome = 41, - NewUserLipConfirmation = 42, - NewUserLipConfirmationMultiDay = 43, - ExistingUserLipConfirmation = 44, - ExistingUserLipConfirmationMultiDay = 45, - NewHearingReminderLipSingleDay = 46, - NewHearingReminderLipMultiDay = 47, - NewUserRepresentativeWelcome = 48, - NewUserRepresentativeConfirmation = 49, - NewUserRepresentativeConfirmationMultiDay = 50, - ExistingUserRepresentativeConfirmation = 51, - ExistingUserRepresentativeConfirmationMultiDay = 52, - NewHearingReminderRepresentativeSingleDay = 53, - NewHearingReminderRepresentativeMultiDay = 54, - } + CreateIndividual = 1, + CreateRepresentative = 2, + PasswordReset = 3, + HearingConfirmationLip = 4, + HearingConfirmationRepresentative = 5, + HearingConfirmationJudge = 6, + HearingConfirmationJoh = 7, + HearingConfirmationLipMultiDay = 8, + HearingConfirmationRepresentativeMultiDay = 9, + HearingConfirmationJudgeMultiDay = 10, + HearingConfirmationJohMultiDay = 11, + HearingAmendmentLip = 12, + HearingAmendmentRepresentative = 13, + HearingAmendmentJudge = 14, + HearingAmendmentJoh = 15, + HearingReminderLip = 16, + HearingReminderRepresentative = 17, + HearingReminderJoh = 18, + HearingConfirmationEJudJudge = 19, + HearingConfirmationEJudJudgeMultiDay = 20, + HearingAmendmentEJudJudge = 21, + HearingAmendmentEJudJoh = 22, + HearingReminderEJudJoh = 23, + HearingConfirmationEJudJoh = 24, + HearingConfirmationEJudJohMultiDay = 25, + EJudJohDemoOrTest = 26, + EJudJudgeDemoOrTest = 27, + JudgeDemoOrTest = 28, + ParticipantDemoOrTest = 29, + TelephoneHearingConfirmation = 30, + TelephoneHearingConfirmationMultiDay = 31, + CreateStaffMember = 32, + HearingAmendmentStaffMember = 33, + HearingConfirmationStaffMember = 34, + HearingConfirmationStaffMemberMultiDay = 35, + StaffMemberDemoOrTest = 36, + NewHearingReminderLIP = 37, + NewHearingReminderRepresentative = 38, + NewHearingReminderJOH = 39, + NewHearingReminderEJudJoh = 40, + NewUserLipWelcome = 41, + NewUserLipConfirmation = 42, + NewUserLipConfirmationMultiDay = 43, + ExistingUserLipConfirmation = 44, + ExistingUserLipConfirmationMultiDay = 45, + NewHearingReminderLipSingleDay = 46, + NewHearingReminderLipMultiDay = 47, + NewUserRepresentativeWelcome = 48, + NewUserRepresentativeConfirmation = 49, + NewUserRepresentativeConfirmationMultiDay = 50, + ExistingUserRepresentativeConfirmation = 51, + ExistingUserRepresentativeConfirmationMultiDay = 52, + NewHearingReminderRepresentativeSingleDay = 53, + NewHearingReminderRepresentativeMultiDay = 54, } diff --git a/NotificationApi/NotificationApi.DAL/Commands/Core/CommandHandlerLoggingDecorator.cs b/NotificationApi/NotificationApi.DAL/Commands/Core/CommandHandlerLoggingDecorator.cs index eb77cfa9..a80f9f5a 100644 --- a/NotificationApi/NotificationApi.DAL/Commands/Core/CommandHandlerLoggingDecorator.cs +++ b/NotificationApi/NotificationApi.DAL/Commands/Core/CommandHandlerLoggingDecorator.cs @@ -3,34 +3,25 @@ using Microsoft.Extensions.Logging; using NotificationApi.Common.Helpers; -namespace NotificationApi.DAL.Commands.Core +namespace NotificationApi.DAL.Commands.Core; + +public class CommandHandlerLoggingDecorator( + ICommandHandler underlyingHandler, + ILogger logger, + ILoggingDataExtractor loggingDataExtractor) + : ICommandHandler + where TCommand : ICommand { - public class CommandHandlerLoggingDecorator : ICommandHandler where TCommand : ICommand + public async Task Handle(TCommand command) { - private readonly ICommandHandler _underlyingHandler; - - private readonly ILogger _logger; - - private readonly ILoggingDataExtractor _loggingDataExtractor; - - public CommandHandlerLoggingDecorator(ICommandHandler underlyingHandler, ILogger logger, ILoggingDataExtractor loggingDataExtractor) - { - _logger = logger; - _underlyingHandler = underlyingHandler; - _loggingDataExtractor = loggingDataExtractor; - } - - public async Task Handle(TCommand command) + var properties = loggingDataExtractor.ConvertToDictionary(command); + properties.Add(nameof(TCommand), typeof(TCommand).Name); + using (logger.BeginScope(properties)) { - var properties = _loggingDataExtractor.ConvertToDictionary(command); - properties.Add(nameof(TCommand), typeof(TCommand).Name); - using (_logger.BeginScope(properties)) - { - _logger.LogDebug("Handling command"); - var sw = Stopwatch.StartNew(); - await _underlyingHandler.Handle(command); - _logger.LogDebug("Handled command in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); - } + logger.LogDebug("Handling command"); + var sw = Stopwatch.StartNew(); + await underlyingHandler.Handle(command); + logger.LogDebug("Handled command in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); } } } diff --git a/NotificationApi/NotificationApi.DAL/Exceptions/DuplicateNotificationTemplateException.cs b/NotificationApi/NotificationApi.DAL/Exceptions/DuplicateNotificationTemplateException.cs index 7f1b5b47..6c7b1963 100644 --- a/NotificationApi/NotificationApi.DAL/Exceptions/DuplicateNotificationTemplateException.cs +++ b/NotificationApi/NotificationApi.DAL/Exceptions/DuplicateNotificationTemplateException.cs @@ -1,21 +1,7 @@ using System; -using System.Runtime.Serialization; using NotificationApi.Domain.Enums; -namespace NotificationApi.DAL.Exceptions -{ +namespace NotificationApi.DAL.Exceptions; - [Serializable] - public class DuplicateNotificationTemplateException : Exception - { - public DuplicateNotificationTemplateException(NotificationType notificationType) : base( - $"Duplicate entry for notification type {notificationType} found") - { - } - - protected DuplicateNotificationTemplateException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} +public class DuplicateNotificationTemplateException(NotificationType notificationType) + : Exception($"Duplicate entry for notification type {notificationType} found"); diff --git a/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandler.cs b/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandler.cs index ae0a90e1..b730e951 100644 --- a/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandler.cs +++ b/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandler.cs @@ -1,20 +1,12 @@ using System.Threading.Tasks; -namespace NotificationApi.DAL.Queries.Core +namespace NotificationApi.DAL.Queries.Core; + +public class QueryHandler(IQueryHandlerFactory queryHandlerFactory) : IQueryHandler { - public class QueryHandler : IQueryHandler + public Task Handle(TQuery query) where TQuery : IQuery where TResult : class { - private readonly IQueryHandlerFactory _queryHandlerFactory; - - public QueryHandler(IQueryHandlerFactory queryHandlerFactory) - { - _queryHandlerFactory = queryHandlerFactory; - } - - public Task Handle(TQuery query) where TQuery : IQuery where TResult : class - { - var handler = _queryHandlerFactory.Create(query); - return handler.Handle(query); - } + var handler = queryHandlerFactory.Create(query); + return handler.Handle(query); } } diff --git a/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandlerLoggingDecorator.cs b/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandlerLoggingDecorator.cs index 4867ff04..b2abed5f 100644 --- a/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandlerLoggingDecorator.cs +++ b/NotificationApi/NotificationApi.DAL/Queries/Core/QueryHandlerLoggingDecorator.cs @@ -3,37 +3,29 @@ using Microsoft.Extensions.Logging; using NotificationApi.Common.Helpers; -namespace NotificationApi.DAL.Queries.Core +namespace NotificationApi.DAL.Queries.Core; + +public class QueryHandlerLoggingDecorator( + IQueryHandler underlyingHandler, + ILogger logger, + ILoggingDataExtractor loggingDataExtractor) + : IQueryHandler + where TQuery : IQuery + where TResult : class { - public class QueryHandlerLoggingDecorator : IQueryHandler where TQuery : IQuery where TResult : class + public async Task Handle(TQuery query) { - private readonly IQueryHandler _underlyingHandler; - - private readonly ILogger _logger; - - private readonly ILoggingDataExtractor _loggingDataExtractor; - - public QueryHandlerLoggingDecorator(IQueryHandler underlyingHandler, ILogger logger, ILoggingDataExtractor loggingDataExtractor) - { - _logger = logger; - _underlyingHandler = underlyingHandler; - _loggingDataExtractor = loggingDataExtractor; - } - - public async Task Handle(TQuery query) + var properties = loggingDataExtractor.ConvertToDictionary(query); + properties.Add(nameof(TQuery), typeof(TQuery).Name); + properties.Add(nameof(TResult), typeof(TResult).Name); + using (logger.BeginScope(properties)) { - var properties = _loggingDataExtractor.ConvertToDictionary(query); - properties.Add(nameof(TQuery), typeof(TQuery).Name); - properties.Add(nameof(TResult), typeof(TResult).Name); - using (_logger.BeginScope(properties)) - { - // Unfortunetely this scope wont apply to the underlying handler as its already been resolved from the logger factory. - _logger.LogDebug("Handling query"); - var sw = Stopwatch.StartNew(); - var result = await _underlyingHandler.Handle(query); - _logger.LogDebug("Handled query in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); - return result; - } + // Unfortunately this scope won't apply to the underlying handler as its already been resolved from the logger factory. + logger.LogDebug("Handling query"); + var sw = Stopwatch.StartNew(); + var result = await underlyingHandler.Handle(query); + logger.LogDebug("Handled query in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); + return result; } } } diff --git a/NotificationApi/NotificationApi.DAL/TemplateDataSeeding.cs b/NotificationApi/NotificationApi.DAL/TemplateDataSeeding.cs index b5a890e0..99802bf4 100644 --- a/NotificationApi/NotificationApi.DAL/TemplateDataSeeding.cs +++ b/NotificationApi/NotificationApi.DAL/TemplateDataSeeding.cs @@ -1,53 +1,43 @@ -using System; -using NotificationApi.DAL; using System.Linq; -namespace NotificationApi +namespace NotificationApi.DAL; + +public class TemplateDataSeeding(NotificationsApiDbContext context) { - public class TemplateDataSeeding + private readonly TemplateDataForEnvironments _templateDataForEnvironments = new (); + + public void Run(string environment) { - private readonly NotificationsApiDbContext _context; - private readonly TemplateDataForEnvironments _templateDataForEnvironments = new TemplateDataForEnvironments(); - - public TemplateDataSeeding(NotificationsApiDbContext context) - { - _context = context; - } - - public void Run(string environment) + context.Database.EnsureCreated(); + + var templates = context.Templates; + var sourceTemplates = _templateDataForEnvironments.Get(environment); + + foreach (var template in sourceTemplates) { - _context.Database.EnsureCreated(); - - var templates = _context.Templates; - var sourceTemplates = _templateDataForEnvironments.Get(environment); - - foreach (var template in sourceTemplates) + var existingTemplates = templates.Where(x => x.NotificationType == template.NotificationType).ToList(); + + // if no templates exist for a given type, just add + if (existingTemplates.Count == 0) { - var existingTemplates = templates.Where(x => x.NotificationType == template.NotificationType).ToList(); - - // if no templates exist for a given type, just add - if (!existingTemplates.Any()) - { - _context.Templates.Add(template); - _context.SaveChanges(); - } - - // if multiple templates exist for a given type, remove all and add again from the source - var duplicateTemplates = existingTemplates.Count > 1; - var nonMatchingTemplate = existingTemplates.Count == 1 && - existingTemplates[0].NotifyTemplateId != template.NotifyTemplateId - && existingTemplates[0].Parameters != template.Parameters; - var paramsDoNotMatch = existingTemplates.Count == 1 && - existingTemplates[0].Parameters != template.Parameters; - if (duplicateTemplates || nonMatchingTemplate || paramsDoNotMatch) - { - _context.Templates.RemoveRange(existingTemplates); - _context.Templates.Add(template); - _context.SaveChanges(); - } + context.Templates.Add(template); + context.SaveChanges(); + } + + // if multiple templates exist for a given type, remove all and add again from the source + var duplicateTemplates = existingTemplates.Count > 1; + var nonMatchingTemplate = existingTemplates.Count == 1 && + existingTemplates[0].NotifyTemplateId != template.NotifyTemplateId + && existingTemplates[0].Parameters != template.Parameters; + var paramsDoNotMatch = existingTemplates.Count == 1 && + existingTemplates[0].Parameters != template.Parameters; + if (duplicateTemplates || nonMatchingTemplate || paramsDoNotMatch) + { + context.Templates.RemoveRange(existingTemplates); + context.Templates.Add(template); + context.SaveChanges(); } - _context.SaveChanges(); } + context.SaveChanges(); } - } diff --git a/NotificationApi/NotificationApi.Domain/Ddd/AggregateRoot.cs b/NotificationApi/NotificationApi.Domain/Ddd/AggregateRoot.cs deleted file mode 100644 index e37515cc..00000000 --- a/NotificationApi/NotificationApi.Domain/Ddd/AggregateRoot.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NotificationApi.Domain.Ddd -{ - public abstract class AggregateRoot : Entity - { - - } -} diff --git a/NotificationApi/NotificationApi.Domain/Ddd/ValueObject.cs b/NotificationApi/NotificationApi.Domain/Ddd/ValueObject.cs deleted file mode 100644 index 670f16aa..00000000 --- a/NotificationApi/NotificationApi.Domain/Ddd/ValueObject.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace NotificationApi.Domain.Ddd -{ - // This base class comes from Jimmy Bogard, but with support of inheritance - // http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html - - public abstract class ValueObject : IEquatable - where T : ValueObject - { - public override bool Equals(object obj) - { - if (obj == null) - return false; - - var other = obj as T; - - return Equals(other); - } - - public virtual bool Equals(T other) - { - if (other == null) - return false; - - var t = GetType(); - var otherType = other.GetType(); - - if (t != otherType) - return false; - - var fields = GetFields(this); - - foreach (var field in fields) - { - var value1 = field.GetValue(other); - var value2 = field.GetValue(this); - - if (value1 == null) - { - if (value2 != null) - return false; - } - else if (!value1.Equals(value2)) - return false; - } - - return true; - } - - public override int GetHashCode() - { - var fields = GetFields(this); - - var startValue = 17; - var multiplier = 59; - - return fields - .Select(field => field.GetValue(this)) - .Where(value => value != null) - .Aggregate( - startValue, - (current, value) => current * multiplier + value.GetHashCode()); - } - - private static IEnumerable GetFields(object obj) - { - var t = obj.GetType(); - - var fields = new List(); - - while (t != typeof(object)) - { - if (t == null) continue; - fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)); - - t = t.BaseType; - } - - return fields; - } - - public static bool operator ==(ValueObject x, ValueObject y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (((object)x == null) || ((object)y == null)) - { - return false; - } - - return x.Equals(y); - } - - public static bool operator !=(ValueObject x, ValueObject y) - { - return !(x == y); - } - } - -} diff --git a/NotificationApi/NotificationApi.Domain/Template.cs b/NotificationApi/NotificationApi.Domain/Template.cs index 1392b006..a44919bd 100644 --- a/NotificationApi/NotificationApi.Domain/Template.cs +++ b/NotificationApi/NotificationApi.Domain/Template.cs @@ -1,28 +1,24 @@ using System; using NotificationApi.Domain.Enums; -namespace NotificationApi.Domain +namespace NotificationApi.Domain; + +public class Template( + Guid notifyTemplateId, + NotificationType notificationType, + MessageType messageType, + string parameters) + : TrackableEntity { - public class Template : TrackableEntity + public Guid NotifyTemplateId { get; } = notifyTemplateId; + public NotificationType NotificationType { get; } = notificationType; + public MessageType MessageType { get; } = messageType; + public string Parameters { get; } = parameters; + + public Template(Guid notifyTemplateId, NotificationType notificationType, MessageType messageType, + string parameters, DateTime createdAt, DateTime updatedAt): this(notifyTemplateId, notificationType, messageType, parameters) { - public Guid NotifyTemplateId { get; } - public NotificationType NotificationType { get; } - public MessageType MessageType { get; } - public string Parameters { get; } - - public Template(Guid notifyTemplateId, NotificationType notificationType, MessageType messageType, string parameters) - { - NotifyTemplateId = notifyTemplateId; - NotificationType = notificationType; - MessageType = messageType; - Parameters = parameters; - } - - public Template(Guid notifyTemplateId, NotificationType notificationType, MessageType messageType, - string parameters, DateTime createdAt, DateTime updatedAt): this(notifyTemplateId, notificationType, messageType, parameters) - { - CreatedAt = createdAt; - UpdatedAt = updatedAt; - } + CreatedAt = createdAt; + UpdatedAt = updatedAt; } } diff --git a/NotificationApi/NotificationApi.IntegrationTests/Api/Setup/ApiTest.cs b/NotificationApi/NotificationApi.IntegrationTests/Api/Setup/ApiTest.cs index b9df7287..12d4519c 100644 --- a/NotificationApi/NotificationApi.IntegrationTests/Api/Setup/ApiTest.cs +++ b/NotificationApi/NotificationApi.IntegrationTests/Api/Setup/ApiTest.cs @@ -57,7 +57,7 @@ private static void GenerateRandomCallbackSecret() protected string GenerateCallbackToken() { - return new CustomJwtTokenProvider().GenerateTokenForCallbackEndpoint(_notifyConfiguration.CallbackSecret, 60); + return CustomJwtTokenProvider.GenerateTokenForCallbackEndpoint(_notifyConfiguration.CallbackSecret, 60); } private void RegisterSettings() diff --git a/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationDeliveryStatusCommandTests.cs b/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationDeliveryStatusCommandTests.cs index ba89771b..a7e7a389 100644 --- a/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationDeliveryStatusCommandTests.cs +++ b/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationDeliveryStatusCommandTests.cs @@ -11,7 +11,7 @@ namespace NotificationApi.IntegrationTests.Database.Commands { public class UpdateNotificationDeliveryStatusCommandTests : DatabaseTestsBase { - private readonly List _notifications = new List(); + private readonly List _notifications = []; private UpdateNotificationDeliveryStatusCommandHandler _handler; [SetUp] @@ -31,7 +31,6 @@ public async Task should_update_delivery_status_for_notification() var command = new UpdateNotificationDeliveryStatusCommand(notification.Id, notification.ExternalId, deliveryStatus); // Act - Thread.Sleep(1000); // wait for a second await _handler.Handle(command); // Assert @@ -39,6 +38,7 @@ public async Task should_update_delivery_status_for_notification() var updatedNotification = await db.Notifications.SingleOrDefaultAsync(x => x.Id == notification.Id); updatedNotification.Should().NotBeNull(); updatedNotification.DeliveryStatus.Should().Be(deliveryStatus); + updatedNotification.CreatedAt.Should().NotBeNull(); updatedNotification.CreatedAt.Should().Be(notification.CreatedAt.Value); updatedNotification.UpdatedAt.Should().BeAfter(updatedNotification.CreatedAt.Value); } diff --git a/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationSentCommandHandlerTests.cs b/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationSentCommandHandlerTests.cs index ab71494e..c3ff36ff 100644 --- a/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationSentCommandHandlerTests.cs +++ b/NotificationApi/NotificationApi.IntegrationTests/Database/Commands/UpdateNotificationSentCommandHandlerTests.cs @@ -11,7 +11,7 @@ namespace NotificationApi.IntegrationTests.Database.Commands { public class UpdateNotificationSentCommandHandlerTests : DatabaseTestsBase { - private readonly List _notifications = new List(); + private readonly List _notifications = new(); private UpdateNotificationSentCommandHandler _handler; [SetUp] @@ -29,7 +29,6 @@ public async Task Should_Update_Delivery_Status_To_Created() const DeliveryStatus expectedDeliveryStatus = DeliveryStatus.Created; var command = new UpdateNotificationSentCommand(notification.Id, notification.ExternalId, notification.Payload); - Thread.Sleep(1000); // wait for a second await _handler.Handle(command); await using var db = new NotificationsApiDbContext(NotifyBookingsDbContextOptions); diff --git a/NotificationApi/NotificationApi.UnitTests/Middleware/AsyncNotificationClientLoggingDecoratorShould.cs b/NotificationApi/NotificationApi.UnitTests/Middleware/AsyncNotificationClientLoggingDecoratorShould.cs index e93c531c..d2a6a034 100644 --- a/NotificationApi/NotificationApi.UnitTests/Middleware/AsyncNotificationClientLoggingDecoratorShould.cs +++ b/NotificationApi/NotificationApi.UnitTests/Middleware/AsyncNotificationClientLoggingDecoratorShould.cs @@ -1,3 +1,4 @@ +using System; using Autofac.Extras.Moq; using Moq; using NotificationApi.Middleware.Logging; @@ -7,233 +8,232 @@ using System.Net.Http; using System.Threading.Tasks; -namespace NotificationApi.UnitTests.Middleware +namespace NotificationApi.UnitTests.Middleware; + +public class AsyncNotificationClientLoggingDecoratorShould { - public class AsyncNotificationClientLoggingDecoratorShould + private AutoMock _mocker; + + private AsyncNotificationClientLoggingDecorator _sut; + + [SetUp] + public void Setup() { - private AutoMock _mocker; - - private AsyncNotificationClientLoggingDecorator _sut; - - [SetUp] - public void Setup() - { - _mocker = AutoMock.GetLoose(); - _sut = _mocker.Create(); - } - - [Test] - public void Should_call_underlying_client_ExtractServiceIdAndApiKey() - { - // Arrange - var fromApiKey = "fromApiKey"; - - // Act - _sut.ExtractServiceIdAndApiKey(fromApiKey); - - // Assert - _mocker.Mock().Verify(x => x.ExtractServiceIdAndApiKey(fromApiKey), Times.Once); - } - - [Test] - public void Should_call_underlying_client_GetUserAgent() - { - // Arrange - - // Act - _sut.GetUserAgent(); - - // Assert - _mocker.Mock().Verify(x => x.GetUserAgent(), Times.Once); - } - - [Test] - public void Should_call_underlying_client_ValidateBaseUri() - { - // Arrange - var baseUrl = "baseUrl"; - - // Act - _sut.ValidateBaseUri(baseUrl); - - // Assert - _mocker.Mock().Verify(x => x.ValidateBaseUri(baseUrl), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GenerateTemplatePreviewAsync() - { - // Arrange - var templateId = "templateId"; - var personalisation = new Dictionary(); - - // Act - await _sut.GenerateTemplatePreviewAsync(templateId, personalisation); - - // Assert - _mocker.Mock().Verify(x => x.GenerateTemplatePreviewAsync(templateId, personalisation), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GetAllTemplatesAsync() - { - // Arrange - var templateType = "templateType"; - - // Act - await _sut.GetAllTemplatesAsync(templateType); - - // Assert - _mocker.Mock().Verify(x => x.GetAllTemplatesAsync(templateType), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GetNotificationByIdAsync() - { - // Arrange - var notificationId = "notificationId"; - - // Act - await _sut.GetNotificationByIdAsync(notificationId); - - // Assert - _mocker.Mock().Verify(x => x.GetNotificationByIdAsync(notificationId), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GetNotificationsAsync() - { - // Arrange - var templateType = "templateType"; - var status = "status"; - var reference = "reference"; - var olderThanId = "olderThanId"; - var includeSpreadsheetUploads = true; - - // Act - await _sut.GetNotificationsAsync(templateType, status, reference, olderThanId, includeSpreadsheetUploads); - - // Assert - _mocker.Mock().Verify(x => x.GetNotificationsAsync(templateType, status, reference, olderThanId, includeSpreadsheetUploads), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GetReceivedTextsAsync() - { - // Arrange - var olderThanId = "olderThanId"; - - // Act - await _sut.GetReceivedTextsAsync(olderThanId); - - // Assert - _mocker.Mock().Verify(x => x.GetReceivedTextsAsync(olderThanId), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_GetTemplateByIdAsync() - { - // Arrange - var templateId = "templateId"; - - // Act - await _sut.GetTemplateByIdAsync(templateId); - - // Assert - _mocker.Mock().Verify(x => x.GetTemplateByIdAsync(templateId), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_MakeRequest() - { - // Arrange - var url = "url"; - var method = HttpMethod.Get; - var content = new MultipartContent(); - - // Act - await _sut.MakeRequest(url, method, content); - - // Assert - _mocker.Mock().Verify(x => x.MakeRequest(url, method, content), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_POST() - { - // Arrange - var url = "url"; - var json = "json"; - - // Act - await _sut.POST(url, json); - - // Assert - _mocker.Mock().Verify(x => x.POST(url, json), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_SendEmailAsync() - { - // Arrange - var emailAddress = "emailAddress"; - var templateId = "templateId"; - var personalisation = new Dictionary(); - var clientReference = "clientReference"; - var emailReplyToId = "emailReplyToId"; - - // Act - await _sut.SendEmailAsync(emailAddress, templateId, personalisation, clientReference, emailReplyToId); - - // Assert - _mocker.Mock().Verify(x => x.SendEmailAsync(emailAddress, templateId, personalisation, clientReference, emailReplyToId), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_SendLetterAsync() - { - // Arrange - var templateId = "templateId"; - var personalisation = new Dictionary(); - var clientReference = "clientReference"; - - // Act - await _sut.SendLetterAsync(templateId, personalisation, clientReference); - - // Assert - _mocker.Mock().Verify(x => x.SendLetterAsync(templateId, personalisation, clientReference), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_SendPrecompiledLetterAsync() - { - // Arrange - var clientReference = "clientReference"; - var pdfContents = new byte[0]; - var postage = "postage"; - - // Act - await _sut.SendPrecompiledLetterAsync(clientReference, pdfContents, postage); - - // Assert - _mocker.Mock().Verify(x => x.SendPrecompiledLetterAsync(clientReference, pdfContents, postage), Times.Once); - } - - [Test] - public async Task Should_call_underlying_client_SendSmsAsync() - { - // Arrange - var mobileNumber = "mobileNumber"; - var templateId = "templateId"; - var personalisation = new Dictionary(); - var clientReference = "clientReference"; - var smsSenderId = "smsSenderId"; - - // Act - await _sut.SendSmsAsync(mobileNumber, templateId, personalisation, clientReference, smsSenderId); - - // Assert - _mocker.Mock().Verify(x => x.SendSmsAsync(mobileNumber, templateId, personalisation, clientReference, smsSenderId), Times.Once); - } + _mocker = AutoMock.GetLoose(); + _sut = _mocker.Create(); + } + + [Test] + public void Should_call_underlying_client_ExtractServiceIdAndApiKey() + { + // Arrange + var fromApiKey = "fromApiKey"; + + // Act + _sut.ExtractServiceIdAndApiKey(fromApiKey); + + // Assert + _mocker.Mock().Verify(x => x.ExtractServiceIdAndApiKey(fromApiKey), Times.Once); + } + + [Test] + public void Should_call_underlying_client_GetUserAgent() + { + // Arrange + + // Act + _sut.GetUserAgent(); + + // Assert + _mocker.Mock().Verify(x => x.GetUserAgent(), Times.Once); + } + + [Test] + public void Should_call_underlying_client_ValidateBaseUri() + { + // Arrange + var baseUrl = "baseUrl"; + + // Act + _sut.ValidateBaseUri(baseUrl); + + // Assert + _mocker.Mock().Verify(x => x.ValidateBaseUri(baseUrl), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GenerateTemplatePreviewAsync() + { + // Arrange + var templateId = "templateId"; + var personalisation = new Dictionary(); + + // Act + await _sut.GenerateTemplatePreviewAsync(templateId, personalisation); + + // Assert + _mocker.Mock().Verify(x => x.GenerateTemplatePreviewAsync(templateId, personalisation), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GetAllTemplatesAsync() + { + // Arrange + var templateType = "templateType"; + + // Act + await _sut.GetAllTemplatesAsync(templateType); + + // Assert + _mocker.Mock().Verify(x => x.GetAllTemplatesAsync(templateType), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GetNotificationByIdAsync() + { + // Arrange + var notificationId = "notificationId"; + + // Act + await _sut.GetNotificationByIdAsync(notificationId); + + // Assert + _mocker.Mock().Verify(x => x.GetNotificationByIdAsync(notificationId), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GetNotificationsAsync() + { + // Arrange + var templateType = "templateType"; + var status = "status"; + var reference = "reference"; + var olderThanId = "olderThanId"; + var includeSpreadsheetUploads = true; + + // Act + await _sut.GetNotificationsAsync(templateType, status, reference, olderThanId, true); + + // Assert + _mocker.Mock().Verify(x => x.GetNotificationsAsync(templateType, status, reference, olderThanId, includeSpreadsheetUploads), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GetReceivedTextsAsync() + { + // Arrange + var olderThanId = "olderThanId"; + + // Act + await _sut.GetReceivedTextsAsync(olderThanId); + + // Assert + _mocker.Mock().Verify(x => x.GetReceivedTextsAsync(olderThanId), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_GetTemplateByIdAsync() + { + // Arrange + var templateId = "templateId"; + + // Act + await _sut.GetTemplateByIdAsync(templateId); + + // Assert + _mocker.Mock().Verify(x => x.GetTemplateByIdAsync(templateId), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_MakeRequest() + { + // Arrange + var url = "url"; + var method = HttpMethod.Get; + var content = new MultipartContent(); + + // Act + await _sut.MakeRequest(url, method, content); + + // Assert + _mocker.Mock().Verify(x => x.MakeRequest(url, method, content), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_POST() + { + // Arrange + var url = "url"; + var json = "json"; + + // Act + await _sut.POST(url, json); + + // Assert + _mocker.Mock().Verify(x => x.POST(url, json), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_SendEmailAsync() + { + // Arrange + var emailAddress = "emailAddress"; + var templateId = "templateId"; + var personalisation = new Dictionary(); + var clientReference = "clientReference"; + var emailReplyToId = "emailReplyToId"; + + // Act + await _sut.SendEmailAsync(emailAddress, templateId, personalisation, clientReference, emailReplyToId); + + // Assert + _mocker.Mock().Verify(x => x.SendEmailAsync(emailAddress, templateId, personalisation, clientReference, emailReplyToId), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_SendLetterAsync() + { + // Arrange + var templateId = "templateId"; + var personalisation = new Dictionary(); + var clientReference = "clientReference"; + + // Act + await _sut.SendLetterAsync(templateId, personalisation, clientReference); + + // Assert + _mocker.Mock().Verify(x => x.SendLetterAsync(templateId, personalisation, clientReference), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_SendPrecompiledLetterAsync() + { + // Arrange + var clientReference = "clientReference"; + var pdfContents = Array.Empty(); + var postage = "postage"; + + // Act + await _sut.SendPrecompiledLetterAsync(clientReference, pdfContents, postage); + + // Assert + _mocker.Mock().Verify(x => x.SendPrecompiledLetterAsync(clientReference, pdfContents, postage), Times.Once); + } + + [Test] + public async Task Should_call_underlying_client_SendSmsAsync() + { + // Arrange + var mobileNumber = "mobileNumber"; + var templateId = "templateId"; + var personalisation = new Dictionary(); + var clientReference = "clientReference"; + var smsSenderId = "smsSenderId"; + + // Act + await _sut.SendSmsAsync(mobileNumber, templateId, personalisation, clientReference, smsSenderId); + + // Assert + _mocker.Mock().Verify(x => x.SendSmsAsync(mobileNumber, templateId, personalisation, clientReference, smsSenderId), Times.Once); } } diff --git a/NotificationApi/NotificationApi.UnitTests/Validation/AddNotificationRequestValidationTests.cs b/NotificationApi/NotificationApi.UnitTests/Validation/AddNotificationRequestValidationTests.cs index d5ebe929..90ce8312 100644 --- a/NotificationApi/NotificationApi.UnitTests/Validation/AddNotificationRequestValidationTests.cs +++ b/NotificationApi/NotificationApi.UnitTests/Validation/AddNotificationRequestValidationTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using FluentAssertions; using NotificationApi.Contract; @@ -8,129 +7,128 @@ using NotificationApi.Validations; using NUnit.Framework; -namespace NotificationApi.UnitTests.Validation +namespace NotificationApi.UnitTests.Validation; + +public class AddNotificationRequestValidationTests { - public class AddNotificationRequestValidationTests + private AddNotificationRequestValidation _validator; + private AddNotificationRequest _request; + + [OneTimeSetUp] + public void OneTimeSetUp() { - private AddNotificationRequestValidation _validator; - private AddNotificationRequest _request; - - [OneTimeSetUp] - public void OneTimeSetUp() - { - _validator = new AddNotificationRequestValidation(); - } - - [SetUp] - public void SetUp() - { - _request = InitRequest(); - } - - [Test] - public async Task Should_Validate_Successfully_With_A_Correct_Model() - { - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeTrue(); - } - - [Test] - public async Task Should_Fail_When_Email_Is_Missing_When_MessageType_Is_Email() - { - _request.ContactEmail = null; - _request.MessageType = MessageType.Email; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingEmailMessage).Should() - .BeTrue(); - } - - [Test] - public async Task Should_Fail_When_HearingId_Is_Missing() - { - _request.HearingId = null; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingHearingIdMessage).Should() - .BeTrue(); - } - - [Test] - public async Task Should_Fail_When_MessageType_Is_Invalid() - { - _request.MessageType = (MessageType)4; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.InvalidMessageTypeMessage).Should() - .BeTrue(); - } - - [Test] - public async Task Should_Fail_When_NotificationType_Is_Invalid() - { - _request.NotificationType = (NotificationType)999; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.InvalidNotificationTypeMessage).Should() - .BeTrue(); - } - - [Test] - public async Task Should_Fail_When_Parameters_Are_Missing() - { - _request.Parameters = null; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingParametersMessage).Should() - .BeTrue(); - } - - [Test] - public async Task Should_Fail_When_ParticipantId_Is_Missing() - { - _request.ParticipantId = null; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingParticipantIdMessage).Should() - .BeTrue(); - } + _validator = new AddNotificationRequestValidation(); + } + + [SetUp] + public void SetUp() + { + _request = InitRequest(); + } + + [Test] + public async Task Should_Validate_Successfully_With_A_Correct_Model() + { + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeTrue(); + } + + [Test] + public async Task Should_Fail_When_Email_Is_Missing_When_MessageType_Is_Email() + { + _request.ContactEmail = null; + _request.MessageType = MessageType.Email; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingEmailMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_HearingId_Is_Missing() + { + _request.HearingId = null; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingHearingIdMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_MessageType_Is_Invalid() + { + _request.MessageType = (MessageType)4; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.InvalidMessageTypeMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_NotificationType_Is_Invalid() + { + _request.NotificationType = (NotificationType)999; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.InvalidNotificationTypeMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_Parameters_Are_Missing() + { + _request.Parameters = null; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingParametersMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_ParticipantId_Is_Missing() + { + _request.ParticipantId = null; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingParticipantIdMessage).Should() + .BeTrue(); + } + + [Test] + public async Task Should_Fail_When_Phone_Number_Is_Missing_When_MessageType_Is_SMS() + { + _request.PhoneNumber = null; + _request.MessageType = MessageType.SMS; + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeFalse(); + result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingPhoneNumberMessage).Should() + .BeTrue(); + } + + [Test] + public async Task should_pass_validation_when_password_reset_and_hearing_details_are_not_provided() + { + _request.HearingId = null; + _request.ParticipantId = null; + _request.NotificationType = NotificationType.PasswordReset; - [Test] - public async Task Should_Fail_When_Phone_Number_Is_Missing_When_MessageType_Is_SMS() - { - _request.PhoneNumber = null; - _request.MessageType = MessageType.SMS; - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeFalse(); - result.Errors.Exists(x => x.ErrorMessage == AddNotificationRequestValidation.MissingPhoneNumberMessage).Should() - .BeTrue(); - } - - [Test] - public async Task should_pass_validation_when_password_reset_and_hearing_details_are_not_provided() - { - _request.HearingId = null; - _request.ParticipantId = null; - _request.NotificationType = NotificationType.PasswordReset; - - var result = await _validator.ValidateAsync(_request); - result.IsValid.Should().BeTrue(); - } + var result = await _validator.ValidateAsync(_request); + result.IsValid.Should().BeTrue(); + } + + private static AddNotificationRequest InitRequest() + { + var parameters = new Dictionary {{"test", "test1"}}; - private AddNotificationRequest InitRequest() + return new AddNotificationRequest { - var parameters = new Dictionary {{"test", "test1"}}; - - return new AddNotificationRequest - { - ContactEmail = "email@hmcts.net", - HearingId = Guid.NewGuid(), - MessageType = MessageType.Email, - NotificationType = NotificationType.CreateIndividual, - Parameters = parameters, - ParticipantId = Guid.NewGuid(), - PhoneNumber = "1234567890" - }; - } + ContactEmail = "email@hmcts.net", + HearingId = Guid.NewGuid(), + MessageType = MessageType.Email, + NotificationType = NotificationType.CreateIndividual, + Parameters = parameters, + ParticipantId = Guid.NewGuid(), + PhoneNumber = "1234567890" + }; } } diff --git a/NotificationApi/NotificationApi/Controllers/NotificationCallbackController.cs b/NotificationApi/NotificationApi/Controllers/NotificationCallbackController.cs new file mode 100644 index 00000000..084ca4e3 --- /dev/null +++ b/NotificationApi/NotificationApi/Controllers/NotificationCallbackController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Authorization; +using NotificationApi.DAL.Commands; +using NotificationApi.DAL.Commands.Core; + +namespace NotificationApi.Controllers; + +[Produces("application/json")] +[Route("notification")] +[ApiController] +public class NotificationCallbackController(ICommandHandler commandHandler) : ControllerBase +{ + + /// + /// Process callbacks from Gov Notify API + /// + /// + [HttpPost("callback")] + [OpenApiOperation("HandleCallback")] + [Authorize(AuthenticationSchemes = "Callback")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] + public async Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest) + { + var notificationId = notificationCallbackRequest.ReferenceAsGuid(); + var deliveryStatus = notificationCallbackRequest.DeliveryStatusAsEnum(); + var externalId = notificationCallbackRequest.Id; + var command = new UpdateNotificationDeliveryStatusCommand(notificationId, externalId, deliveryStatus); + + await commandHandler.Handle(command); + return Ok(); + } +} diff --git a/NotificationApi/NotificationApi/Controllers/NotificationController.cs b/NotificationApi/NotificationApi/Controllers/NotificationController.cs index a4f303b3..527e68d8 100644 --- a/NotificationApi/NotificationApi/Controllers/NotificationController.cs +++ b/NotificationApi/NotificationApi/Controllers/NotificationController.cs @@ -7,90 +7,56 @@ using NotificationApi.Domain; using NotificationApi.Domain.Enums; -namespace NotificationApi.Controllers +namespace NotificationApi.Controllers; + +[Produces("application/json")] +[Route("notification")] +[ApiController] +public class NotificationController(IQueryHandler queryHandler, ICreateNotificationService createNotificationService) : ControllerBase { - [Produces("application/json")] - [Route("notification")] - [ApiController] - public class NotificationController : ControllerBase + [HttpGet("template/{notificationType}")] + [OpenApiOperation("GetTemplateByNotificationType")] + [ProducesResponseType(typeof(NotificationTemplateResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] + public async Task GetTemplateByNotificationTypeAsync(Contract.NotificationType notificationType) { - private readonly IQueryHandler _queryHandler; - private readonly ICommandHandler _commandHandler; - private readonly ICreateNotificationService _createNotificationService; - - public NotificationController(IQueryHandler queryHandler, - ICommandHandler commandHandler, ICreateNotificationService createNotificationService) + var template = await queryHandler.Handle(new GetTemplateByNotificationTypeQuery((NotificationType)notificationType)); + if (template == null) { - _queryHandler = queryHandler; - _commandHandler = commandHandler; - _createNotificationService = createNotificationService; + throw new BadRequestException($"Invalid {nameof(notificationType)}: {notificationType}"); } - - [HttpGet("template/{notificationType}")] - [OpenApiOperation("GetTemplateByNotificationType")] - [ProducesResponseType(typeof(NotificationTemplateResponse), (int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task GetTemplateByNotificationTypeAsync(Contract.NotificationType notificationType) - { - var template = await _queryHandler.Handle(new GetTemplateByNotificationTypeQuery((NotificationType)notificationType)); - if (template == null) - { - throw new BadRequestException($"Invalid {nameof(notificationType)}: {notificationType}"); - } - - return Ok(new NotificationTemplateResponse - { - Id = template.Id, - NotificationType = (Contract.NotificationType)template.NotificationType, - NotifyTemplateId = template.NotifyTemplateId, - Parameters = template.Parameters - }); - } - - [HttpPost] - [OpenApiOperation("CreateNewNotification")] - [ProducesResponseType((int)HttpStatusCode.OK)] - [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - [Obsolete("Please use the journey specific routes")] - public async Task CreateNewNotificationAsync(AddNotificationRequest request) + + return Ok(new NotificationTemplateResponse { - var parameters = JsonConvert.SerializeObject(request.Parameters); - var emailNotifications = await _queryHandler.Handle>( - new GetEmailNotificationQuery(request.HearingId, request.ParticipantId, - (NotificationType)request.NotificationType, request.ContactEmail)); - var emailNotification = emailNotifications.SingleOrDefault(x => x.Parameters == parameters); - - if (emailNotification == null) - { - var notification = new CreateEmailNotificationCommand((NotificationType)request.NotificationType, - request.ContactEmail, request.ParticipantId, request.HearingId, parameters); - await _createNotificationService.CreateEmailNotificationAsync(notification, request.Parameters); - } - - return Ok(); - } - - /// - /// Process callbacks from Gov Notify API - /// - /// - [HttpPost("callback")] - [OpenApiOperation("HandleCallback")] - [Authorize(AuthenticationSchemes = "Callback")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] - public async Task HandleCallbackAsync(NotificationCallbackRequest notificationCallbackRequest) + Id = template.Id, + NotificationType = (Contract.NotificationType)template.NotificationType, + NotifyTemplateId = template.NotifyTemplateId, + Parameters = template.Parameters + }); + } + + [HttpPost] + [OpenApiOperation("CreateNewNotification")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest)] + [Obsolete("Please use the journey specific routes, used to seed tests")] + public async Task CreateNewNotificationAsync(AddNotificationRequest request) + { + var parameters = JsonConvert.SerializeObject(request.Parameters); + var emailNotifications = await queryHandler.Handle>( + new GetEmailNotificationQuery(request.HearingId, request.ParticipantId, + (NotificationType)request.NotificationType, request.ContactEmail)); + var emailNotification = emailNotifications.SingleOrDefault(x => x.Parameters == parameters); + + if (emailNotification == null) { - var notificationId = notificationCallbackRequest.ReferenceAsGuid(); - var deliveryStatus = notificationCallbackRequest.DeliveryStatusAsEnum(); - var externalId = notificationCallbackRequest.Id; - var command = new UpdateNotificationDeliveryStatusCommand(notificationId, externalId, deliveryStatus); - - await _commandHandler.Handle(command); - return Ok(); + var notification = new CreateEmailNotificationCommand((NotificationType)request.NotificationType, + request.ContactEmail, request.ParticipantId, request.HearingId, parameters); + await createNotificationService.CreateEmailNotificationAsync(notification, request.Parameters); } + + return Ok(); } } diff --git a/NotificationApi/NotificationApi/Controllers/ParticipantEmailNotificationsController.cs b/NotificationApi/NotificationApi/Controllers/ParticipantEmailNotificationsController.cs index 491f2395..554f9ecf 100644 --- a/NotificationApi/NotificationApi/Controllers/ParticipantEmailNotificationsController.cs +++ b/NotificationApi/NotificationApi/Controllers/ParticipantEmailNotificationsController.cs @@ -4,342 +4,336 @@ using NotificationApi.Domain; using NotificationApi.Domain.Enums; -namespace NotificationApi.Controllers -{ - [Produces("application/json")] - [ApiController] - public class ParticipantEmailNotificationsController : ControllerBase - { - private readonly IQueryHandler _queryHandler; - private readonly ICreateNotificationService _createNotificationService; - - public ParticipantEmailNotificationsController(IQueryHandler queryHandler, - ICreateNotificationService createNotificationService) - { - _queryHandler = queryHandler; - _createNotificationService = createNotificationService; - } - - /// - /// Send an email with information about their new hearings account - /// - [HttpPost("participant-created-account-email")] - [OpenApiOperation("SendParticipantCreatedAccountEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantCreatedAccountEmailAsync(SignInDetailsEmailRequest request) - { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.CreateIndividual, - RoleNames.Representative => NotificationType.CreateRepresentative, - RoleNames.JudicialOfficeHolder => NotificationType.CreateRepresentative, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToV1AccountCreated(request); - - await ProcessRequest(request.ContactEmail, null, null, notificationType, parameters); - return Ok(); - } - - /// - /// Send an email with a new temporary password - /// - [HttpPost("reset-password-email")] - [OpenApiOperation("SendResetPasswordEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendResetPasswordEmailAsync(PasswordResetEmailRequest request) - { - var notificationType = NotificationType.PasswordReset; - var parameters = NotificationParameterMapper.MapToPasswordReset(request); - - await ProcessRequest(request.ContactEmail, null, null, notificationType, parameters); - return Ok(); - } - - /// - /// Send a welcome to VH email to a participant - /// - /// - [HttpPost("participant-welcome-email")] - [OpenApiOperation("SendParticipantWelcomeEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantWelcomeEmailAsync(NewUserWelcomeEmailRequest request) - { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.NewUserLipWelcome, - RoleNames.Representative => NotificationType.NewUserRepresentativeWelcome, - _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToWelcomeEmail(request); +namespace NotificationApi.Controllers; - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - return Ok(); - } - - /// - /// Send a single day hearing confirmation email for a participant that has a new account - /// - /// - [HttpPost("participant-single-day-hearing-confirmation-email-new-user")] - [OpenApiOperation("SendParticipantSingleDayHearingConfirmationForNewUserEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantSingleDayHearingConfirmationForNewUserEmailAsync( - NewUserSingleDayHearingConfirmationRequest request) +[Produces("application/json")] +[ApiController] +[Route("")] +public class ParticipantEmailNotificationsController( + IQueryHandler queryHandler, + ICreateNotificationService createNotificationService) + : ControllerBase +{ + /// + /// Send an email with information about their new hearings account + /// + [HttpPost("participant-created-account-email")] + [OpenApiOperation("SendParticipantCreatedAccountEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantCreatedAccountEmailAsync(SignInDetailsEmailRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.NewUserLipConfirmation, - RoleNames.Representative => NotificationType.NewUserRepresentativeConfirmation, - RoleNames.JudicialOfficeHolder => NotificationType.CreateRepresentative, - _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToSingleDayConfirmationNewUser(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - return Ok(); - } - - /// - /// Send a multi-day hearing confirmation email for a participant that has a new account - /// - /// - [HttpPost("participant-multi-day-hearing-confirmation-email-new-user")] - [OpenApiOperation("SendParticipantMultiDayHearingConfirmationForNewUserEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantMultiDayHearingConfirmationForNewUserEmailAsync( - NewUserMultiDayHearingConfirmationRequest request) + RoleNames.Individual => NotificationType.CreateIndividual, + RoleNames.Representative => NotificationType.CreateRepresentative, + RoleNames.JudicialOfficeHolder => NotificationType.CreateRepresentative, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToV1AccountCreated(request); + + await ProcessRequest(request.ContactEmail, null, null, notificationType, parameters); + return Ok(); + } + + /// + /// Send an email with a new temporary password + /// + [HttpPost("reset-password-email")] + [OpenApiOperation("SendResetPasswordEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendResetPasswordEmailAsync(PasswordResetEmailRequest request) + { + var notificationType = NotificationType.PasswordReset; + var parameters = NotificationParameterMapper.MapToPasswordReset(request); + + await ProcessRequest(request.ContactEmail, null, null, notificationType, parameters); + return Ok(); + } + + /// + /// Send a welcome to VH email to a participant + /// + /// + [HttpPost("participant-welcome-email")] + [OpenApiOperation("SendParticipantWelcomeEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantWelcomeEmailAsync(NewUserWelcomeEmailRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.NewUserLipConfirmationMultiDay, - RoleNames.Representative => NotificationType.NewUserRepresentativeConfirmationMultiDay, - _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToMultiDayConfirmationNewUser(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - return Ok(); - } - - /// - /// Send a single day hearing confirmation email for a participant that already has a user account - /// - /// - [HttpPost("participant-single-day-hearing-confirmation-email-existing-user")] - [OpenApiOperation("SendParticipantSingleDayHearingConfirmationForExistingUserEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantSingleDayHearingConfirmationForExistingUserEmailAsync( - ExistingUserSingleDayHearingConfirmationRequest request) + RoleNames.Individual => NotificationType.NewUserLipWelcome, + RoleNames.Representative => NotificationType.NewUserRepresentativeWelcome, + _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToWelcomeEmail(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + + return Ok(); + } + + /// + /// Send a single day hearing confirmation email for a participant that has a new account + /// + /// + [HttpPost("participant-single-day-hearing-confirmation-email-new-user")] + [OpenApiOperation("SendParticipantSingleDayHearingConfirmationForNewUserEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantSingleDayHearingConfirmationForNewUserEmailAsync( + NewUserSingleDayHearingConfirmationRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.ExistingUserLipConfirmation, - RoleNames.Representative => NotificationType.ExistingUserRepresentativeConfirmation, - RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationJoh, - RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationEJudJoh, - RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType.HearingConfirmationJudge, - RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType.HearingConfirmationEJudJudge, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToSingleDayConfirmationExistingUser(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - - return Ok(); - } - - /// - /// Send a multi-day hearing confirmation email for a participant that already has a user account - /// - /// - [HttpPost("participant-multi-day-hearing-confirmation-email-existing-user")] - [OpenApiOperation("SendParticipantMultiDayHearingConfirmationForExistingUserEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendParticipantMultiDayHearingConfirmationForExistingUserEmailAsync( - ExistingUserMultiDayHearingConfirmationRequest request) + RoleNames.Individual => NotificationType.NewUserLipConfirmation, + RoleNames.Representative => NotificationType.NewUserRepresentativeConfirmation, + RoleNames.JudicialOfficeHolder => NotificationType.CreateRepresentative, + _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToSingleDayConfirmationNewUser(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + return Ok(); + } + + /// + /// Send a multi-day hearing confirmation email for a participant that has a new account + /// + /// + [HttpPost("participant-multi-day-hearing-confirmation-email-new-user")] + [OpenApiOperation("SendParticipantMultiDayHearingConfirmationForNewUserEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantMultiDayHearingConfirmationForNewUserEmailAsync( + NewUserMultiDayHearingConfirmationRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.ExistingUserLipConfirmationMultiDay, - RoleNames.Representative => NotificationType.ExistingUserRepresentativeConfirmationMultiDay, - RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationJohMultiDay, - RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationEJudJohMultiDay, - RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationJudgeMultiDay, - RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType - .HearingConfirmationEJudJudgeMultiDay, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToMultiDayConfirmationForExistingUser(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - - return Ok(); - } - - /// - /// Send a single day hearing reminder email for a participant - /// - /// - [HttpPost("participant-single-day-hearing-reminder-email")] - [OpenApiOperation("SendSingleDayHearingReminderEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendSingleDayHearingReminderEmailAsync(SingleDayHearingReminderRequest request) + RoleNames.Individual => NotificationType.NewUserLipConfirmationMultiDay, + RoleNames.Representative => NotificationType.NewUserRepresentativeConfirmationMultiDay, + _ => throw new BadRequestException($"Role is not supported, provided role is {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToMultiDayConfirmationNewUser(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + return Ok(); + } + + /// + /// Send a single day hearing confirmation email for a participant that already has a user account + /// + /// + [HttpPost("participant-single-day-hearing-confirmation-email-existing-user")] + [OpenApiOperation("SendParticipantSingleDayHearingConfirmationForExistingUserEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantSingleDayHearingConfirmationForExistingUserEmailAsync( + ExistingUserSingleDayHearingConfirmationRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.NewHearingReminderLipSingleDay, - RoleNames.Representative => NotificationType.NewHearingReminderRepresentativeSingleDay, - RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType - .NewHearingReminderJOH, - RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType - .NewHearingReminderEJudJoh, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToSingleDayReminder(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - return Ok(); - } - - /// - /// Send a multi day hearing reminder email for a participant - /// - /// - [HttpPost("participant-multi-day-hearing-reminder-email")] - [OpenApiOperation("SendMultiDayHearingReminderEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendMultiDayHearingReminderEmailAsync(MultiDayHearingReminderRequest request) + RoleNames.Individual => NotificationType.ExistingUserLipConfirmation, + RoleNames.Representative => NotificationType.ExistingUserRepresentativeConfirmation, + RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationJoh, + RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationEJudJoh, + RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType.HearingConfirmationJudge, + RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType.HearingConfirmationEJudJudge, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToSingleDayConfirmationExistingUser(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + + return Ok(); + } + + /// + /// Send a multi-day hearing confirmation email for a participant that already has a user account + /// + /// + [HttpPost("participant-multi-day-hearing-confirmation-email-existing-user")] + [OpenApiOperation("SendParticipantMultiDayHearingConfirmationForExistingUserEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendParticipantMultiDayHearingConfirmationForExistingUserEmailAsync( + ExistingUserMultiDayHearingConfirmationRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.NewHearingReminderLipMultiDay, - RoleNames.Representative => NotificationType.NewHearingReminderRepresentativeMultiDay, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - var parameters = NotificationParameterMapper.MapToMultiDayReminder(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - - return Ok(); - } - - /// - /// Send a hearing amendment email - /// - [HttpPost("participant-hearing-amendment-email")] - [OpenApiOperation("SendHearingAmendmentEmail")] - [ProducesResponseType((int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task SendHearingAmendmentEmailAsync(HearingAmendmentRequest request) + RoleNames.Individual => NotificationType.ExistingUserLipConfirmationMultiDay, + RoleNames.Representative => NotificationType.ExistingUserRepresentativeConfirmationMultiDay, + RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationJohMultiDay, + RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationEJudJohMultiDay, + RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationJudgeMultiDay, + RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType + .HearingConfirmationEJudJudgeMultiDay, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToMultiDayConfirmationForExistingUser(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + + return Ok(); + } + + /// + /// Send a single day hearing reminder email for a participant + /// + /// + [HttpPost("participant-single-day-hearing-reminder-email")] + [OpenApiOperation("SendSingleDayHearingReminderEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendSingleDayHearingReminderEmailAsync(SingleDayHearingReminderRequest request) + { + var notificationType = request.RoleName switch { - var notificationType = request.RoleName switch - { - RoleNames.Individual => NotificationType.HearingAmendmentLip, - RoleNames.Representative => NotificationType.HearingAmendmentRepresentative, - RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType - .HearingAmendmentJoh, - RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType - .HearingAmendmentEJudJoh, - RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType - .HearingAmendmentJudge, - RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType - .HearingAmendmentEJudJudge, - _ => throw new BadRequestException($"Provided role is not {request.RoleName}") - }; - - - var parameters = NotificationParameterMapper.MapToHearingAmendment(request); - - await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, - notificationType, parameters); - - return Ok(); - } - - private async Task ProcessRequest(string contactEmail, Guid? participantId, - Guid? hearingId, NotificationType notificationType, Dictionary parameters) + RoleNames.Individual => NotificationType.NewHearingReminderLipSingleDay, + RoleNames.Representative => NotificationType.NewHearingReminderRepresentativeSingleDay, + RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType + .NewHearingReminderJOH, + RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType + .NewHearingReminderEJudJoh, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToSingleDayReminder(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + return Ok(); + } + + /// + /// Send a multi day hearing reminder email for a participant + /// + /// + [HttpPost("participant-multi-day-hearing-reminder-email")] + [OpenApiOperation("SendMultiDayHearingReminderEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendMultiDayHearingReminderEmailAsync(MultiDayHearingReminderRequest request) + { + var notificationType = request.RoleName switch { - var parametersJson = JsonConvert.SerializeObject(parameters); - - var hasNotificationAlreadyBeenSent = await HasNotificationAlreadyBeenSent(contactEmail, - participantId, hearingId, notificationType, parametersJson); - if (hasNotificationAlreadyBeenSent) return; - - await SaveAndSendNotification(contactEmail, participantId, hearingId, - notificationType, parametersJson, parameters); - } - - private async Task HasNotificationAlreadyBeenSent(string contactEmail, Guid? participantId, - Guid? hearingId, NotificationType notificationType, - string parametersJson) + RoleNames.Individual => NotificationType.NewHearingReminderLipMultiDay, + RoleNames.Representative => NotificationType.NewHearingReminderRepresentativeMultiDay, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + var parameters = NotificationParameterMapper.MapToMultiDayReminder(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + + return Ok(); + } + + /// + /// Send a hearing amendment email + /// + [HttpPost("participant-hearing-amendment-email")] + [OpenApiOperation("SendHearingAmendmentEmail")] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task SendHearingAmendmentEmailAsync(HearingAmendmentRequest request) + { + var notificationType = request.RoleName switch { - var emailNotifications = await _queryHandler.Handle>( - new GetEmailNotificationQuery(hearingId, participantId, notificationType, contactEmail)); - - // check if notification already exists with the same param values - return emailNotifications.Any(x => x.Parameters == parametersJson); - } - - /// - /// Add a record to the database to track the notification and send the notification to Notify - /// - private async Task SaveAndSendNotification(string contactEmail, Guid? participantId, Guid? hearingId, - NotificationType notificationType, - string parametersJson, Dictionary parameters) + RoleNames.Individual => NotificationType.HearingAmendmentLip, + RoleNames.Representative => NotificationType.HearingAmendmentRepresentative, + RoleNames.JudicialOfficeHolder when !request.Username.IsJudiciaryUsername() => NotificationType + .HearingAmendmentJoh, + RoleNames.JudicialOfficeHolder when request.Username.IsJudiciaryUsername() => NotificationType + .HearingAmendmentEJudJoh, + RoleNames.Judge when !request.Username.IsJudiciaryUsername() => NotificationType + .HearingAmendmentJudge, + RoleNames.Judge when request.Username.IsJudiciaryUsername() => NotificationType + .HearingAmendmentEJudJudge, + _ => throw new BadRequestException($"Provided role is not {request.RoleName}") + }; + + + var parameters = NotificationParameterMapper.MapToHearingAmendment(request); + + await ProcessRequest(request.ContactEmail, request.ParticipantId, request.HearingId, + notificationType, parameters); + + return Ok(); + } + + private async Task ProcessRequest(string contactEmail, Guid? participantId, + Guid? hearingId, NotificationType notificationType, Dictionary parameters) + { + var parametersJson = JsonConvert.SerializeObject(parameters); + + var hasNotificationAlreadyBeenSent = await HasNotificationAlreadyBeenSent(contactEmail, + participantId, hearingId, notificationType, parametersJson); + if (hasNotificationAlreadyBeenSent) return; + + await SaveAndSendNotification(contactEmail, participantId, hearingId, + notificationType, parametersJson, parameters); + } + + private async Task HasNotificationAlreadyBeenSent(string contactEmail, Guid? participantId, + Guid? hearingId, NotificationType notificationType, + string parametersJson) + { + var emailNotifications = await queryHandler.Handle>( + new GetEmailNotificationQuery(hearingId, participantId, notificationType, contactEmail)); + + // check if notification already exists with the same param values + return emailNotifications.Any(x => x.Parameters == parametersJson); + } + + /// + /// Add a record to the database to track the notification and send the notification to Notify + /// + private async Task SaveAndSendNotification(string contactEmail, Guid? participantId, Guid? hearingId, + NotificationType notificationType, + string parametersJson, Dictionary parameters) + { + await AreAllParamsGiven(parameters, notificationType); + var notification = new CreateEmailNotificationCommand(notificationType, + contactEmail, participantId, hearingId, parametersJson); + await createNotificationService.CreateEmailNotificationAsync(notification, parameters); + } + + private async Task AreAllParamsGiven(Dictionary parameters, NotificationType notificationType) + { + var template = + await queryHandler.Handle( + new GetTemplateByNotificationTypeQuery(notificationType)); + if (template == null) { - await AreAllParamsGiven(parameters, notificationType); - var notification = new CreateEmailNotificationCommand(notificationType, - contactEmail, participantId, hearingId, parametersJson); - await _createNotificationService.CreateEmailNotificationAsync(notification, parameters); + throw new BadRequestException($"Invalid {nameof(notificationType)}: {notificationType}"); } - - private async Task AreAllParamsGiven(Dictionary parameters, NotificationType notificationType) + + var paramNames = template.Parameters.Split(',').Select(x => x.Trim()).ToList(); + var missingParams = paramNames.Where(x => !parameters.ContainsKey(x)).ToList(); + if (missingParams.Count != 0) { - var template = - await _queryHandler.Handle( - new GetTemplateByNotificationTypeQuery(notificationType)); - if (template == null) - { - throw new BadRequestException($"Invalid {nameof(notificationType)}: {notificationType}"); - } - - var paramNames = template.Parameters.Split(',').Select(x => x.Trim()).ToList(); - var missingParams = paramNames.Where(x => !parameters.ContainsKey(x)).ToList(); - if (missingParams.Any()) - { - throw new BadRequestException($"Missing parameters: {string.Join(", ", missingParams)}"); - } + throw new BadRequestException($"Missing parameters: {string.Join(", ", missingParams)}"); } } } diff --git a/NotificationApi/NotificationApi/Health/HealthCheckExtensions.cs b/NotificationApi/NotificationApi/Health/HealthCheckExtensions.cs index 6221890d..1ca39bb0 100644 --- a/NotificationApi/NotificationApi/Health/HealthCheckExtensions.cs +++ b/NotificationApi/NotificationApi/Health/HealthCheckExtensions.cs @@ -7,56 +7,56 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using NotificationApi.DAL; -namespace NotificationApi.Health +namespace NotificationApi.Health; + +[ExcludeFromCodeCoverage] +public static class HealthCheckExtensions { - [ExcludeFromCodeCoverage] - public static class HealthCheckExtensions + private static readonly string[] Tags = ["startup", "readiness"]; + public static IServiceCollection AddVhHealthChecks(this IServiceCollection services) { - public static IServiceCollection AddVhHealthChecks(this IServiceCollection services) - { - services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddDbContextCheck("Database VhNotificationsApi", tags: new[] {"startup", "readiness"}); - - return services; - } + services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddDbContextCheck("Database VhNotificationsApi", tags: Tags); + + return services; + } - public static IEndpointRouteBuilder AddVhHealthCheckRouteMaps(this IEndpointRouteBuilder endpoints) + public static IEndpointRouteBuilder AddVhHealthCheckRouteMaps(this IEndpointRouteBuilder endpoints) + { + endpoints.MapHealthChecks("/health/liveness", new HealthCheckOptions() { - endpoints.MapHealthChecks("/health/liveness", new HealthCheckOptions() - { - Predicate = check => check.Tags.Contains("self"), - ResponseWriter = HealthCheckResponseWriter - }); - - endpoints.MapHealthChecks("/health/startup", new HealthCheckOptions() - { - Predicate = check => check.Tags.Contains("startup"), - ResponseWriter = HealthCheckResponseWriter - }); - - endpoints.MapHealthChecks("/health/readiness", new HealthCheckOptions() - { - Predicate = check => check.Tags.Contains("readiness"), - ResponseWriter = HealthCheckResponseWriter - }); - - return endpoints; - } - - private static async Task HealthCheckResponseWriter(HttpContext context, HealthReport report) + Predicate = check => check.Tags.Contains("self"), + ResponseWriter = HealthCheckResponseWriter + }); + + endpoints.MapHealthChecks("/health/startup", new HealthCheckOptions() + { + Predicate = check => check.Tags.Contains("startup"), + ResponseWriter = HealthCheckResponseWriter + }); + + endpoints.MapHealthChecks("/health/readiness", new HealthCheckOptions() + { + Predicate = check => check.Tags.Contains("readiness"), + ResponseWriter = HealthCheckResponseWriter + }); + + return endpoints; + } + + private static async Task HealthCheckResponseWriter(HttpContext context, HealthReport report) + { + var result = JsonConvert.SerializeObject(new { - var result = JsonConvert.SerializeObject(new + status = report.Status.ToString(), + details = report.Entries.Select(e => new { - status = report.Status.ToString(), - details = report.Entries.Select(e => new - { - key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status), - error = e.Value.Exception?.Message - }) - }); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(result); - } + key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status), + error = e.Value.Exception?.Message + }) + }); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(result); } } diff --git a/NotificationApi/NotificationApi/Middleware/Logging/AsyncNotificationClientLoggingDecorator.cs b/NotificationApi/NotificationApi/Middleware/Logging/AsyncNotificationClientLoggingDecorator.cs index b17db1cc..94c2e898 100644 --- a/NotificationApi/NotificationApi/Middleware/Logging/AsyncNotificationClientLoggingDecorator.cs +++ b/NotificationApi/NotificationApi/Middleware/Logging/AsyncNotificationClientLoggingDecorator.cs @@ -7,48 +7,41 @@ namespace NotificationApi.Middleware.Logging { - public class AsyncNotificationClientLoggingDecorator : IAsyncNotificationClient + public abstract class AsyncNotificationClientLoggingDecorator( + IAsyncNotificationClient underlyingNotificationClient, + ILogger logger) + : IAsyncNotificationClient { - private readonly IAsyncNotificationClient _underlyingNotificationClient; + public Tuple ExtractServiceIdAndApiKey(string fromApiKey) => underlyingNotificationClient.ExtractServiceIdAndApiKey(fromApiKey); - private readonly ILogger _logger; + public string GetUserAgent() => underlyingNotificationClient.GetUserAgent(); - public AsyncNotificationClientLoggingDecorator(IAsyncNotificationClient underlyingNotificationClient, ILogger logger) - { - _underlyingNotificationClient = underlyingNotificationClient; - _logger = logger; - } - - public Tuple ExtractServiceIdAndApiKey(string fromApiKey) => _underlyingNotificationClient.ExtractServiceIdAndApiKey(fromApiKey); - - public string GetUserAgent() => _underlyingNotificationClient.GetUserAgent(); - - public Uri ValidateBaseUri(string baseUrl) => _underlyingNotificationClient.ValidateBaseUri(baseUrl); + public Uri ValidateBaseUri(string baseUrl) => underlyingNotificationClient.ValidateBaseUri(baseUrl); public Task GenerateTemplatePreviewAsync(string templateId, Dictionary personalisation = null) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GenerateTemplatePreviewAsync), [nameof(templateId)] = templateId, [nameof(personalisation)] = personalisation - }, _underlyingNotificationClient.GenerateTemplatePreviewAsync, templateId, personalisation); + }, underlyingNotificationClient.GenerateTemplatePreviewAsync, templateId, personalisation); public Task GET(string url) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GET), [nameof(url)] = url - }, _underlyingNotificationClient.GET, url); + }, underlyingNotificationClient.GET, url); public Task GetAllTemplatesAsync(string templateType = "") => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GetAllTemplatesAsync), [nameof(templateType)] = templateType - }, _underlyingNotificationClient.GetAllTemplatesAsync, templateType); + }, underlyingNotificationClient.GetAllTemplatesAsync, templateType); public Task GetNotificationByIdAsync(string notificationId) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GetNotificationByIdAsync), [nameof(notificationId)] = notificationId - }, _underlyingNotificationClient.GetNotificationByIdAsync, notificationId); + }, underlyingNotificationClient.GetNotificationByIdAsync, notificationId); public Task GetNotificationsAsync(string templateType = "", string status = "", string reference = "", string olderThanId = "", bool includeSpreadsheetUploads = false) => LogAndHandle(new Dictionary { @@ -58,26 +51,26 @@ public Task GetNotificationsAsync(string templateType = "", st [nameof(reference)] = reference, [nameof(olderThanId)] = olderThanId, [nameof(includeSpreadsheetUploads)] = includeSpreadsheetUploads - }, _underlyingNotificationClient.GetNotificationsAsync, templateType, status, reference, olderThanId, includeSpreadsheetUploads); + }, underlyingNotificationClient.GetNotificationsAsync, templateType, status, reference, olderThanId, includeSpreadsheetUploads); public Task GetReceivedTextsAsync(string olderThanId = "") => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GetReceivedTextsAsync), [nameof(olderThanId)] = olderThanId - }, _underlyingNotificationClient.GetReceivedTextsAsync, olderThanId); + }, underlyingNotificationClient.GetReceivedTextsAsync, olderThanId); public Task GetTemplateByIdAndVersionAsync(string templateId, int version = 0) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GetTemplateByIdAndVersionAsync), [nameof(templateId)] = templateId, [nameof(version)] = version - }, _underlyingNotificationClient.GetTemplateByIdAndVersionAsync, templateId, version); + }, underlyingNotificationClient.GetTemplateByIdAndVersionAsync, templateId, version); public Task GetTemplateByIdAsync(string templateId) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(GetTemplateByIdAsync), [nameof(templateId)] = templateId - }, _underlyingNotificationClient.GetTemplateByIdAsync, templateId); + }, underlyingNotificationClient.GetTemplateByIdAsync, templateId); public Task MakeRequest(string url, HttpMethod method, HttpContent content = null) => LogAndHandle(new Dictionary { @@ -85,14 +78,14 @@ public Task MakeRequest(string url, HttpMethod method, HttpContent conte [nameof(url)] = url, [nameof(method)] = method, [nameof(content)] = content - }, _underlyingNotificationClient.MakeRequest, url, method, content); + }, underlyingNotificationClient.MakeRequest, url, method, content); public Task POST(string url, string json) => LogAndHandle(new Dictionary { [MethodNameKey] = nameof(POST), [nameof(url)] = url, [nameof(json)] = json - }, _underlyingNotificationClient.POST, url, json); + }, underlyingNotificationClient.POST, url, json); public Task SendEmailAsync(string emailAddress, string templateId, Dictionary personalisation = null, string clientReference = null, string emailReplyToId = null) => LogAndHandle(new Dictionary { @@ -102,7 +95,7 @@ public Task SendEmailAsync(string emailAddress, strin [nameof(personalisation)] = personalisation, [nameof(clientReference)] = clientReference, [nameof(emailReplyToId)] = emailReplyToId - }, _underlyingNotificationClient.SendEmailAsync, emailAddress, templateId, personalisation, clientReference, emailReplyToId); + }, underlyingNotificationClient.SendEmailAsync, emailAddress, templateId, personalisation, clientReference, emailReplyToId); public Task SendLetterAsync(string templateId, Dictionary personalisation, string clientReference = null) => LogAndHandle(new Dictionary { @@ -110,7 +103,7 @@ public Task SendLetterAsync(string templateId, Dicti [nameof(templateId)] = templateId, [nameof(personalisation)] = personalisation, [nameof(clientReference)] = clientReference, - }, _underlyingNotificationClient.SendLetterAsync, templateId, personalisation, clientReference); + }, underlyingNotificationClient.SendLetterAsync, templateId, personalisation, clientReference); public Task SendPrecompiledLetterAsync(string clientReference, byte[] pdfContents, string postage) => LogAndHandle(new Dictionary { @@ -118,7 +111,7 @@ public Task SendPrecompiledLetterAsync(string client [nameof(clientReference)] = clientReference, [nameof(pdfContents)] = pdfContents, [nameof(postage)] = postage, - }, _underlyingNotificationClient.SendPrecompiledLetterAsync, clientReference, pdfContents, postage); + }, underlyingNotificationClient.SendPrecompiledLetterAsync, clientReference, pdfContents, postage); public Task SendSmsAsync(string mobileNumber, string templateId, Dictionary personalisation = null, string clientReference = null, string smsSenderId = null) => LogAndHandle(new Dictionary { @@ -128,7 +121,7 @@ public Task SendSmsAsync(string mobileNumber, string te [nameof(personalisation)] = personalisation, [nameof(clientReference)] = clientReference, [nameof(smsSenderId)] = smsSenderId - }, _underlyingNotificationClient.SendSmsAsync, mobileNumber, templateId, personalisation, clientReference, smsSenderId); + }, underlyingNotificationClient.SendSmsAsync, mobileNumber, templateId, personalisation, clientReference, smsSenderId); private const string MethodNameKey = "Method"; private const string RequestLog = "Sending Request"; @@ -136,47 +129,39 @@ public Task SendSmsAsync(string mobileNumber, string te private async Task LogAndHandle(Dictionary logParameters, Func> method, T1 param1) { - using var loggerScope = _logger.BeginScope(logParameters); - _logger.LogDebug(RequestLog); + using var loggerScope = logger.BeginScope(logParameters); + logger.LogDebug(RequestLog); var sw = Stopwatch.StartNew(); var result = await method(param1); - _logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); + logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); return result; } private async Task LogAndHandle(Dictionary logParameters, Func> method, T1 param1, T2 param2) { - using var loggerScope = _logger.BeginScope(logParameters); - _logger.LogDebug(RequestLog); + using var loggerScope = logger.BeginScope(logParameters); + logger.LogDebug(RequestLog); var sw = Stopwatch.StartNew(); var result = await method(param1, param2); - _logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); + logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); return result; } private async Task LogAndHandle(Dictionary logParameters, Func> method, T1 param1, T2 param2, T3 param3) { - using var loggerScope = _logger.BeginScope(logParameters); - _logger.LogDebug(RequestLog); + using var loggerScope = logger.BeginScope(logParameters); + logger.LogDebug(RequestLog); var sw = Stopwatch.StartNew(); var result = await method(param1, param2, param3); - _logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); - return result; - } - private async Task LogAndHandle(Dictionary logParameters, Func> method, T1 param1, T2 param2, T3 param3, T4 param4) - { - using var loggerScope = _logger.BeginScope(logParameters); - _logger.LogDebug(RequestLog); - var sw = Stopwatch.StartNew(); - var result = await method(param1, param2, param3, param4); - _logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); + logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); return result; } + private async Task LogAndHandle(Dictionary logParameters, Func> method, T1 param1, T2 param2, T3 param3, T4 param4, T5 param5) { - using var loggerScope = _logger.BeginScope(logParameters); - _logger.LogDebug(RequestLog); + using var loggerScope = logger.BeginScope(logParameters); + logger.LogDebug(RequestLog); var sw = Stopwatch.StartNew(); var result = await method(param1, param2, param3, param4, param5); - _logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); + logger.LogDebug(ResponseLog, sw.ElapsedMilliseconds); return result; } } diff --git a/NotificationApi/NotificationApi/Middleware/Logging/LoggingMiddleware.cs b/NotificationApi/NotificationApi/Middleware/Logging/LoggingMiddleware.cs index 918f892b..d070d54a 100644 --- a/NotificationApi/NotificationApi/Middleware/Logging/LoggingMiddleware.cs +++ b/NotificationApi/NotificationApi/Middleware/Logging/LoggingMiddleware.cs @@ -6,23 +6,14 @@ namespace NotificationApi.Middleware.Logging { - public class LoggingMiddleware : IAsyncActionFilter + public class LoggingMiddleware(ILogger logger, ILoggingDataExtractor loggingDataExtractor) + : IAsyncActionFilter { - private readonly ILogger _logger; - - private readonly ILoggingDataExtractor _loggingDataExtractor; - - public LoggingMiddleware(ILogger logger, ILoggingDataExtractor loggingDataExtractor) - { - _logger = logger; - _loggingDataExtractor = loggingDataExtractor; - } - public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var properties = context.ActionDescriptor.Parameters .Select(p => context.ActionArguments.SingleOrDefault(x => x.Key == p.Name)) - .SelectMany(pv => _loggingDataExtractor.ConvertToDictionary(pv.Value, pv.Key)) + .SelectMany(pv => loggingDataExtractor.ConvertToDictionary(pv.Value, pv.Key)) .ToDictionary(x => x.Key, x => x.Value); if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor) @@ -32,18 +23,18 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE properties.Add(nameof(actionDescriptor.DisplayName), actionDescriptor.DisplayName); } - using (_logger.BeginScope(properties)) + using (logger.BeginScope(properties)) { - _logger.LogDebug("Starting request"); + logger.LogDebug("Starting request"); var sw = Stopwatch.StartNew(); var action = await next(); if (action.Exception != null) { var ex = action.Exception; - _logger.LogError(ex, ex.Message); + logger.LogError(ex, "An error occurred: {Message}", ex.Message); } - _logger.LogDebug("Handled request in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); + logger.LogDebug("Handled request in {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds); } } } diff --git a/NotificationApi/NotificationApi/Middleware/Validation/RequestModelValidatorFilter.cs b/NotificationApi/NotificationApi/Middleware/Validation/RequestModelValidatorFilter.cs index 3801a0ba..25be47b1 100644 --- a/NotificationApi/NotificationApi/Middleware/Validation/RequestModelValidatorFilter.cs +++ b/NotificationApi/NotificationApi/Middleware/Validation/RequestModelValidatorFilter.cs @@ -4,27 +4,20 @@ namespace NotificationApi.Middleware.Validation { - public class RequestModelValidatorFilter : IAsyncActionFilter + public class RequestModelValidatorFilter( + IRequestModelValidatorService requestModelValidatorService, + ILogger logger) + : IAsyncActionFilter { - private readonly IRequestModelValidatorService _requestModelValidatorService; - private readonly ILogger _logger; - - public RequestModelValidatorFilter(IRequestModelValidatorService requestModelValidatorService, - ILogger logger) - { - _requestModelValidatorService = requestModelValidatorService; - _logger = logger; - } - public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - _logger.LogDebug("Processing request"); + logger.LogDebug("Processing request"); foreach (var property in context.ActionDescriptor.Parameters) { var valuePair = context.ActionArguments.SingleOrDefault(x => x.Key == property.Name); if (property.BindingInfo?.BindingSource == BindingSource.Body) { - var validationFailures = _requestModelValidatorService.Validate(property.ParameterType, valuePair.Value); + var validationFailures = requestModelValidatorService.Validate(property.ParameterType, valuePair.Value); context.ModelState.AddFluentValidationErrors(validationFailures); } @@ -38,7 +31,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE if (!context.ModelState.IsValid) { var errors = context.ModelState.Values.SelectMany(v => v.Errors.Select(b => b.ErrorMessage)).ToList(); - _logger.LogWarning($"Request Validation Failed: {string.Join("; ", errors)}"); + logger.LogWarning("Request Validation Failed: {Join}", string.Join("; ", errors)); context.Result = new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState)); } else diff --git a/NotificationApi/NotificationApi/NotificationApi.csproj b/NotificationApi/NotificationApi/NotificationApi.csproj index 7ffb671e..cc8d4928 100644 --- a/NotificationApi/NotificationApi/NotificationApi.csproj +++ b/NotificationApi/NotificationApi/NotificationApi.csproj @@ -72,6 +72,18 @@ + + + + + + + + + + + + diff --git a/NotificationApi/NotificationApi/Properties/ServiceDependencies/vh-notification-api-demo - Web Deploy/profile.arm.json b/NotificationApi/NotificationApi/Properties/ServiceDependencies/vh-notification-api-demo - Web Deploy/profile.arm.json deleted file mode 100644 index 6badade3..00000000 --- a/NotificationApi/NotificationApi/Properties/ServiceDependencies/vh-notification-api-demo - Web Deploy/profile.arm.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_dependencyType": "compute.appService.windows" - }, - "parameters": { - "resourceGroupName": { - "type": "string", - "defaultValue": "vh-notification-api-demo", - "metadata": { - "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." - } - }, - "resourceGroupLocation": { - "type": "string", - "defaultValue": "ukwest", - "metadata": { - "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." - } - }, - "resourceName": { - "type": "string", - "defaultValue": "vh-notification-api-demo", - "metadata": { - "description": "Name of the main resource to be created by this template." - } - }, - "resourceLocation": { - "type": "string", - "defaultValue": "[parameters('resourceGroupLocation')]", - "metadata": { - "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." - } - } - }, - "variables": { - "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", - "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/resourceGroups", - "name": "[parameters('resourceGroupName')]", - "location": "[parameters('resourceGroupLocation')]", - "apiVersion": "2019-10-01" - }, - { - "type": "Microsoft.Resources/deployments", - "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", - "resourceGroup": "[parameters('resourceGroupName')]", - "apiVersion": "2019-10-01", - "dependsOn": [ - "[parameters('resourceGroupName')]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [ - { - "location": "[parameters('resourceLocation')]", - "name": "[parameters('resourceName')]", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "tags": { - "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" - }, - "dependsOn": [ - "[variables('appServicePlan_ResourceId')]" - ], - "kind": "app", - "properties": { - "name": "[parameters('resourceName')]", - "kind": "app", - "httpsOnly": true, - "reserved": false, - "serverFarmId": "[variables('appServicePlan_ResourceId')]", - "siteConfig": { - "metadata": [ - { - "name": "CURRENT_STACK", - "value": "dotnetcore" - } - ] - } - }, - "identity": { - "type": "SystemAssigned" - } - }, - { - "location": "[parameters('resourceLocation')]", - "name": "[variables('appServicePlan_name')]", - "type": "Microsoft.Web/serverFarms", - "apiVersion": "2015-08-01", - "sku": { - "name": "S1", - "tier": "Standard", - "family": "S", - "size": "S1" - }, - "properties": { - "name": "[variables('appServicePlan_name')]" - } - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/NotificationApi/NotificationApi/Validations/AddNotificationRequestValidation.cs b/NotificationApi/NotificationApi/Validations/AddNotificationRequestValidation.cs index e94ea747..76e832f7 100644 --- a/NotificationApi/NotificationApi/Validations/AddNotificationRequestValidation.cs +++ b/NotificationApi/NotificationApi/Validations/AddNotificationRequestValidation.cs @@ -4,13 +4,13 @@ namespace NotificationApi.Validations { public class AddNotificationRequestValidation : AbstractValidator { - public static readonly string MissingParametersMessage = "Parameters are required"; - public static readonly string MissingEmailMessage = "Email is required"; - public static readonly string MissingHearingIdMessage = "HearingId is required"; - public static readonly string InvalidMessageTypeMessage = "Message type is invalid"; - public static readonly string InvalidNotificationTypeMessage = "Notification type is invalid"; - public static readonly string MissingParticipantIdMessage = "Participant is required"; - public static readonly string MissingPhoneNumberMessage = "Phone number is required"; + public const string MissingParametersMessage = "Parameters are required"; + public const string MissingEmailMessage = "Email is required"; + public const string MissingHearingIdMessage = "HearingId is required"; + public const string InvalidMessageTypeMessage = "Message type is invalid"; + public const string InvalidNotificationTypeMessage = "Notification type is invalid"; + public const string MissingParticipantIdMessage = "Participant is required"; + public const string MissingPhoneNumberMessage = "Phone number is required"; public AddNotificationRequestValidation() { @@ -23,15 +23,15 @@ public AddNotificationRequestValidation() RuleFor(x => x.PhoneNumber).NotEmpty().When(IsPhone).WithMessage(MissingPhoneNumberMessage); } - private bool ValidNotificationType(NotificationType value) => Enum.IsDefined(typeof(NotificationType), value); + private static bool ValidNotificationType(NotificationType value) => Enum.IsDefined(typeof(NotificationType), value); - private bool ValidMessageType(MessageType value) => Enum.IsDefined(typeof(MessageType), value); + private static bool ValidMessageType(MessageType value) => Enum.IsDefined(typeof(MessageType), value); - private bool IsEmail(AddNotificationRequest arg) => arg.MessageType == MessageType.Email; + private static bool IsEmail(AddNotificationRequest arg) => arg.MessageType == MessageType.Email; - private bool IsPhone(AddNotificationRequest arg) => arg.MessageType == MessageType.SMS; + private static bool IsPhone(AddNotificationRequest arg) => arg.MessageType == MessageType.SMS; - private bool IsHearingNotification(AddNotificationRequest arg) => + private static bool IsHearingNotification(AddNotificationRequest arg) => arg.NotificationType != NotificationType.PasswordReset; } } diff --git a/NotificationApi/Testing.Common/Security/CustomJwtTokenProvider.cs b/NotificationApi/Testing.Common/Security/CustomJwtTokenProvider.cs index 0d942a13..42eef078 100644 --- a/NotificationApi/Testing.Common/Security/CustomJwtTokenProvider.cs +++ b/NotificationApi/Testing.Common/Security/CustomJwtTokenProvider.cs @@ -2,30 +2,29 @@ using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Tokens; -namespace Testing.Common.Security +namespace Testing.Common.Security; + +public static class CustomJwtTokenProvider { - public class CustomJwtTokenProvider + public static string GenerateTokenForCallbackEndpoint(string callbackSecret, int expiresInMinutes) + { + return BuildToken(expiresInMinutes, Convert.FromBase64String(callbackSecret)); + } + + private static string BuildToken(int expiresInMinutes, byte[] key) { - public string GenerateTokenForCallbackEndpoint(string callbackSecret, int expiresInMinutes) + var securityKey = new SymmetricSecurityKey(key); + var descriptor = new SecurityTokenDescriptor { - return BuildToken(expiresInMinutes, Convert.FromBase64String(callbackSecret)); - } + IssuedAt = DateTime.UtcNow.AddMinutes(-1), + NotBefore = DateTime.UtcNow.AddMinutes(-1), + Issuer = "hmcts.video.hearings", + Expires = DateTime.UtcNow.AddMinutes(expiresInMinutes + 1), + SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512) + }; - private static string BuildToken(int expiresInMinutes, byte[] key) - { - var securityKey = new SymmetricSecurityKey(key); - var descriptor = new SecurityTokenDescriptor - { - IssuedAt = DateTime.UtcNow.AddMinutes(-1), - NotBefore = DateTime.UtcNow.AddMinutes(-1), - Issuer = "hmcts.video.hearings", - Expires = DateTime.UtcNow.AddMinutes(expiresInMinutes + 1), - SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512) - }; - - JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); - JwtSecurityToken token = handler.CreateJwtSecurityToken(descriptor); - return handler.WriteToken(token); - } + var handler = new JwtSecurityTokenHandler(); + var token = handler.CreateJwtSecurityToken(descriptor); + return handler.WriteToken(token); } } diff --git a/azure-pipelines.sds.master-release.yml b/azure-pipelines.sds.master-release.yml index e98c65b8..80579f87 100644 --- a/azure-pipelines.sds.master-release.yml +++ b/azure-pipelines.sds.master-release.yml @@ -86,7 +86,7 @@ stages: sonarExtraProperties: | sonar.cs.opencover.reportsPaths=$(System.DefaultWorkingDirectory)/coverage.opencover.xml sonar.cpd.exclusions=**/NotificationType.cs,**/TemplateDataForEnvironments.cs,*/Validations/*.cs,**/Services/NotificationParameterMapper.cs - sonar.exclusions=**/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi/Swagger/**/*, NotificationApi/NotificationApi.DAL/Migrations/*, NotificationApi/NotificationApi.DAL/TemplateDataForEnvironments.cs + sonar.exclusions=**/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi/Swagger/**/*, **/NotificationApi.DAL/Migrations/**, **/NotificationApi.DAL/TemplateDataForEnvironments.cs sonar.coverage.exclusions=**/NotificationApi/Swagger/**/*, **/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi.Common/**/*, **/NotificationApi.IntegrationTests/**/*, **/NotificationApi.UnitTests/**/*, **/NotificationApi/Extensions/*, **/NotificationApi.DAL/Migrations/**/* ##################################################### diff --git a/azure-pipelines.sds.pr-release.yml b/azure-pipelines.sds.pr-release.yml index 78e5bd8d..43ac69a2 100644 --- a/azure-pipelines.sds.pr-release.yml +++ b/azure-pipelines.sds.pr-release.yml @@ -49,7 +49,7 @@ stages: sonarExtraProperties: | sonar.cs.opencover.reportsPaths=$(System.DefaultWorkingDirectory)/coverage.opencover.xml sonar.cpd.exclusions=**/NotificationType.cs,**/TemplateDataForEnvironments.cs,*/Validations/*.cs,**/Services/NotificationParameterMapper.cs - sonar.exclusions=**/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi/Swagger/**/*, NotificationApi/NotificationApi.DAL/Migrations/*, NotificationApi/NotificationApi.DAL/TemplateDataForEnvironments.cs + sonar.exclusions=**/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi/Swagger/**/*, **/NotificationApi.DAL/Migrations/**, **/NotificationApi.DAL/TemplateDataForEnvironments.cs sonar.coverage.exclusions=**/NotificationApi/Swagger/**/*, **/Program.cs, **/Startup.cs, **/Testing.Common/**/*, **/NotificationApi.Common/**/*, **/NotificationApi.IntegrationTests/**/*, **/NotificationApi.UnitTests/**/*, **/NotificationApi/Extensions/*, **/NotificationApi.DAL/Migrations/**/* ####################################################