From dae406528e5e731424745a48b1dd231cb5b441a4 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Thu, 31 Oct 2024 13:19:00 +0100 Subject: [PATCH 01/19] Mark as solved --- EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs b/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs index 44b3bd70..7253f2a8 100644 --- a/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs +++ b/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs @@ -59,7 +59,7 @@ static string ExtractCustomEnumOptionName(TEnum @enum) /// /// The output enum of type B. /// - internal static LogLevel ConvertToLogLevel(this ProcessingResult processingResult) // TODO: Missing code coverage + internal static LogLevel ConvertToLogLevel(this ProcessingResult processingResult) { return processingResult switch { From c8900e83cd978eee7aee741ea81cf48bbeaba6d8 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Thu, 31 Oct 2024 17:09:19 +0100 Subject: [PATCH 02/19] Refactor the code to handle and manage excpeptions in better and more controllable way --- .../StandardizeApiResponsesAttribute.cs | 5 +- .../Controllers/Base/OmcController.cs | 7 +- .../Controllers/EventsController.cs | 71 ++------- .../Controllers/NotifyController.cs | 2 +- .../Controllers/TestController.cs | 18 +-- .../Extensions/EnumExtensions.cs | 18 +-- ...rocessingResult.cs => ProcessingStatus.cs} | 2 +- EventsHandler/Api/EventsHandler/Program.cs | 7 +- .../Properties/Resources.Designer.cs | 42 +++++- .../EventsHandler/Properties/Resources.resx | 23 ++- .../Interfaces/IProcessingService.cs | 13 +- .../DataProcessing/NotifyProcessor.cs | 101 +++++++++---- .../Strategy/Responses/ProcessingResult.cs | 39 +++++ .../Interfaces/IRespondingService.cs | 37 ++--- .../Messages/Models/Details/InfoDetails.cs | 8 +- .../Services/Responding/NotifyResponder.cs | 10 +- .../Services/Responding/OmcResponder.cs | 42 +++--- .../Responding/v1/NotifyCallbackResponder.cs | 6 +- .../Responding/v2/NotifyCallbackResponder.cs | 6 +- .../Controllers/EventsControllerTests.cs | 140 ++---------------- .../Extensions/EnumExtensionsTests.cs | 16 +- .../DataProcessing/NotifyProcessorTests.cs | 121 +++++++++------ .../Templates/NotifyTemplatesAnalyzerTests.cs | 38 ++--- .../_TestHelpers/NotificationEventHandler.cs | 21 ++- 24 files changed, 405 insertions(+), 388 deletions(-) rename EventsHandler/Api/EventsHandler/Mapping/Enums/{ProcessingResult.cs => ProcessingStatus.cs} (97%) create mode 100644 EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs diff --git a/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs b/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs index 2b4154e8..32bc8291 100644 --- a/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs +++ b/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs @@ -5,6 +5,7 @@ using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Base; using Microsoft.AspNetCore.Mvc; @@ -32,8 +33,8 @@ static StandardizeApiResponsesAttribute() { // NOTE: Concept similar to strategy design pattern => decide how and which API Controllers are responding to the end-user s_mappedControllersToResponders.TryAdd(typeof(EventsController), typeof(IRespondingService)); - s_mappedControllersToResponders.TryAdd(typeof(NotifyController), typeof(IRespondingService)); - s_mappedControllersToResponders.TryAdd(typeof(TestController), typeof(IRespondingService)); + s_mappedControllersToResponders.TryAdd(typeof(NotifyController), typeof(IRespondingService)); + s_mappedControllersToResponders.TryAdd(typeof(TestController), typeof(IRespondingService)); } /// diff --git a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs index 76f43ea8..2a490707 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs @@ -2,6 +2,7 @@ using Asp.Versioning; using EventsHandler.Constants; +using EventsHandler.Extensions; using EventsHandler.Properties; using EventsHandler.Services.Responding.Messages.Models.Base; using Microsoft.AspNetCore.Mvc; @@ -68,7 +69,9 @@ protected internal static ObjectResult LogApiResponse(Exception exception, Objec /// internal static void LogMessage(LogLevel logLevel, string logMessage) { - _ = SentrySdk.CaptureMessage($"{Resources.Application_Name} | {logLevel:G} | {logMessage}", s_logMapping[logLevel]); + _ = SentrySdk.CaptureMessage( + message: string.Format(Resources.API_Response_STATUS_Logging, Resources.Application_Name, logLevel.GetEnumName(), logMessage), + level: s_logMapping[logLevel]); } /// @@ -94,7 +97,7 @@ private static string DetermineResultMessage(ObjectResult objectResult) BaseStandardResponseBody baseResponse => baseResponse.ToString(), // Unknown object result - _ => $"{Resources.Processing_ERROR_UnspecifiedResponse} | {objectResult.StatusCode} | {nameof(objectResult.Value)}" + _ => string.Format(Resources.API_Response_ERROR_UnspecifiedResponse, objectResult.StatusCode, nameof(objectResult.Value)) }; } #endregion diff --git a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs index 6241b86e..231e89a4 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs @@ -4,13 +4,11 @@ using EventsHandler.Attributes.Validation; using EventsHandler.Controllers.Base; using EventsHandler.Extensions; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Services.DataProcessing.Interfaces; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Errors; -using EventsHandler.Services.Serialization.Interfaces; -using EventsHandler.Services.Validation.Interfaces; using EventsHandler.Services.Versioning.Interfaces; using EventsHandler.Utilities.Swagger.Examples; using Microsoft.AspNetCore.Mvc; @@ -27,29 +25,21 @@ namespace EventsHandler.Controllers /// public sealed class EventsController : OmcController { - private readonly ISerializationService _serializer; - private readonly IValidationService _validator; - private readonly IProcessingService _processor; - private readonly IRespondingService _responder; + private readonly IProcessingService _processor; + private readonly IRespondingService _responder; private readonly IVersionsRegister _register; /// /// Initializes a new instance of the class. /// - /// The input de(serializing) service. - /// The input validating service. /// The input processing service (business logic). /// The output standardization service (UX/UI). /// The register of versioned services. public EventsController( - ISerializationService serializer, - IValidationService validator, - IProcessingService processor, - IRespondingService responder, + IProcessingService processor, + IRespondingService responder, IVersionsRegister register) { - this._serializer = serializer; - this._validator = validator; this._processor = processor; this._responder = responder; this._register = register; @@ -78,35 +68,23 @@ public EventsController( [ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(string))] // REASON: Operation is not implemented (a new case is not yet supported) public async Task ListenAsync([Required, FromBody] object json) { - /* Validation #1: The validation of JSON payload structure and model-binding of [Required] properties are - * happening on the level of [FromBody] annotation. The attribute [StandardizeApiResponses] - * is meant to intercept native framework errors, raised immediately by ASP.NET Core validation - * mechanism, and to re-pack them ("beautify") into user-friendly standardized API responses */ + /* The validation of JSON payload structure and model-binding of [Required] properties are + * happening on the level of [FromBody] annotation. The attribute [StandardizeApiResponses] + * is meant to intercept native framework errors, raised immediately by ASP.NET Core validation + * mechanism, and to re-pack them ("beautify") into user-friendly standardized API responses */ try { - // Deserialize received JSON payload - NotificationEvent notification = this._serializer.Deserialize(json); + // Try to process the received notification + ProcessingResult result = await this._processor.ProcessAsync(json); - // Validation #2: Structural and data inconsistencies analysis of optional properties - return this._validator.Validate(ref notification) is HealthCheck.OK_Valid - or HealthCheck.OK_Inconsistent - // Try to process the received notification - ? await Task.Run(async () => - { - (ProcessingResult Status, string _) result = await this._processor.ProcessAsync(notification); - - return LogApiResponse(result.Status.ConvertToLogLevel(), // LogLevel - this._responder.GetResponse(GetResult(result, json), notification.Details)); - }) - - // The notification cannot be processed - : LogApiResponse(LogLevel.Error, - this._responder.GetResponse(GetAbortedResult(notification.Details.Message, json), notification.Details)); + return LogApiResponse(result.Status.ConvertToLogLevel(), // LogLevel + this._responder.GetResponse(result)); } catch (Exception exception) { - // Serious problems occurred during the attempt to process the notification - return LogApiResponse(exception, this._responder.GetExceptionResponse(exception)); + // Unhandled problems occurred during the attempt to process the notification + return LogApiResponse(exception, + this._responder.GetExceptionResponse(exception)); } } @@ -128,22 +106,5 @@ public IActionResult Version() return Ok(this._register.GetOmcVersion( this._register.GetApisVersions())); } - - #region Helper methods - private static (ProcessingResult, string) GetResult((ProcessingResult Status, string Description) result, object json) - { - return (result.Status, EnrichDescription(result.Description, json)); - } - - private static (ProcessingResult, string) GetAbortedResult(string message, object json) - { - return (ProcessingResult.NotPossible, EnrichDescription(message, json)); - } - - private static string EnrichDescription(string originalText, object json) - { - return $"{originalText} | Notification: {json}"; - } - #endregion } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs index ac4eea09..808baaa7 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs @@ -27,7 +27,7 @@ public sealed class NotifyController : OmcController /// Initializes a new instance of the class. /// /// The output standardization service (UX/UI). - public NotifyController(IRespondingService responder) + public NotifyController(IRespondingService responder) { this._responder = (NotifyResponder)responder; } diff --git a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs index abb2129a..0aa78029 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs @@ -33,7 +33,7 @@ public sealed class TestController : OmcController private readonly WebApiConfiguration _configuration; private readonly ISerializationService _serializer; private readonly ITelemetryService _telemetry; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; /// /// Initializes a new instance of the class. @@ -46,7 +46,7 @@ public TestController( WebApiConfiguration configuration, ISerializationService serializer, ITelemetryService telemetry, - IRespondingService responder) + IRespondingService responder) { this._configuration = configuration; this._serializer = serializer; @@ -80,9 +80,9 @@ public async Task HealthCheckAsync() // Response return result.IsSuccessStatusCode // HttpStatus Code: 202 Accepted - ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingResult.Success, result.ToString())) + ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingStatus.Success, result.ToString())) // HttpStatus Code: 400 Bad Request - : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingResult.Failure, result.ToString())); + : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingStatus.Failure, result.ToString())); } catch (Exception exception) { @@ -240,9 +240,9 @@ public async Task ConfirmAsync( return response.IsSuccess // HttpStatus Code: 202 Accepted - ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingResult.Success, response.JsonResponse)) + ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingStatus.Success, response.JsonResponse)) // HttpStatus Code: 400 Bad Request - : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingResult.Failure, response.JsonResponse)); + : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingStatus.Failure, response.JsonResponse)); } catch (Exception exception) { @@ -292,7 +292,7 @@ private async Task SendAsync( default: return LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingResult.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); + this._responder.GetResponse(ProcessingStatus.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); } } // Case #2: Personalization was provided by the user @@ -318,13 +318,13 @@ private async Task SendAsync( default: return LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingResult.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); + this._responder.GetResponse(ProcessingStatus.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); } } // HttpStatus Code: 202 Accepted return LogApiResponse(LogLevel.Information, - this._responder.GetResponse(ProcessingResult.Success, + this._responder.GetResponse(ProcessingStatus.Success, string.Format(Resources.Test_NotifyNL_SUCCESS_NotificationSent, notifyMethod.GetEnumName()))); } catch (Exception exception) diff --git a/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs b/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs index 7253f2a8..d3ad3d60 100644 --- a/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs +++ b/EventsHandler/Api/EventsHandler/Extensions/EnumExtensions.cs @@ -53,23 +53,23 @@ static string ExtractCustomEnumOptionName(TEnum @enum) } /// - /// Converts from enum to enum. + /// Converts from enum to enum. /// - /// The input enum of type A. + /// The input enum of type A. /// /// The output enum of type B. /// - internal static LogLevel ConvertToLogLevel(this ProcessingResult processingResult) + internal static LogLevel ConvertToLogLevel(this ProcessingStatus processingStatus) { - return processingResult switch + return processingStatus switch { - ProcessingResult.Success => LogLevel.Information, + ProcessingStatus.Success => LogLevel.Information, - ProcessingResult.Skipped or - ProcessingResult.Aborted => LogLevel.Warning, + ProcessingStatus.Skipped or + ProcessingStatus.Aborted => LogLevel.Warning, - ProcessingResult.NotPossible or - ProcessingResult.Failure => LogLevel.Error, + ProcessingStatus.NotPossible or + ProcessingStatus.Failure => LogLevel.Error, _ => LogLevel.None }; } diff --git a/EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingResult.cs b/EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingStatus.cs similarity index 97% rename from EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingResult.cs rename to EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingStatus.cs index b87cca1c..b7700b9b 100644 --- a/EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingResult.cs +++ b/EventsHandler/Api/EventsHandler/Mapping/Enums/ProcessingStatus.cs @@ -7,7 +7,7 @@ namespace EventsHandler.Mapping.Enums /// /// The status of the core business logic processing. /// - public enum ProcessingResult + public enum ProcessingStatus { /// /// The was processed successfully. diff --git a/EventsHandler/Api/EventsHandler/Program.cs b/EventsHandler/Api/EventsHandler/Program.cs index c7568ae9..e3028c48 100644 --- a/EventsHandler/Api/EventsHandler/Program.cs +++ b/EventsHandler/Api/EventsHandler/Program.cs @@ -14,6 +14,7 @@ using EventsHandler.Services.DataProcessing.Strategy.Manager; using EventsHandler.Services.DataProcessing.Strategy.Manager.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.DataQuerying; using EventsHandler.Services.DataQuerying.Adapter; using EventsHandler.Services.DataQuerying.Adapter.Interfaces; @@ -254,7 +255,7 @@ private static WebApplicationBuilder AddCustomServices(this WebApplicationBuilde // Business logic builder.Services.AddSingleton, NotificationValidator>(); builder.Services.AddSingleton(); - builder.Services.AddSingleton, NotifyProcessor>(); + builder.Services.AddSingleton(); builder.Services.AddSingleton, NotifyTemplatesAnalyzer>(); builder.Services.AddSingleton, NotifyService>(); builder.Services.RegisterNotifyStrategies(); @@ -405,10 +406,10 @@ private static void RegisterResponders(this WebApplicationBuilder builder) .OMC.Features.Workflow_Version(); // Implicit interface (Adapter) used by EventsController => check "IRespondingService" - builder.Services.AddSingleton, OmcResponder>(); + builder.Services.AddSingleton, OmcResponder>(); // Explicit interfaces (generic) used by other controllers => check "IRespondingService" - builder.Services.AddSingleton(typeof(IRespondingService), DetermineResponderVersion(omvWorkflowVersion)); + builder.Services.AddSingleton(typeof(IRespondingService), DetermineResponderVersion(omvWorkflowVersion)); return; diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs index 82af6c12..2ed8ea1e 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs @@ -60,6 +60,24 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Not standardized (unexpected) API response | {0} | {1}. + /// + internal static string API_Response_ERROR_UnspecifiedResponse { + get { + return ResourceManager.GetString("API_Response_ERROR_UnspecifiedResponse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} | {1} | {2}. + /// + internal static string API_Response_STATUS_Logging { + get { + return ResourceManager.GetString("API_Response_STATUS_Logging", resourceCulture); + } + } + /// /// Looks up a localized string similar to OMC. /// @@ -879,6 +897,24 @@ internal static string Processing_ABORT_DoNotSendNotification_Whitelist_Messages } } + /// + /// Looks up a localized string similar to Notify NL Exception | {0}. + /// + internal static string Processing_ERROR_Exception_Notify { + get { + return ResourceManager.GetString("Processing_ERROR_Exception_Notify", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} | {1}. + /// + internal static string Processing_ERROR_Exception_Unhandled { + get { + return ResourceManager.GetString("Processing_ERROR_Exception_Unhandled", resourceCulture); + } + } + /// /// Looks up a localized string similar to It wasn't possible to extract human-friendly error message. /// @@ -961,11 +997,11 @@ internal static string Processing_ERROR_Scenario_NotImplemented { } /// - /// Looks up a localized string similar to Not standardized (unexpected) API response. + /// Looks up a localized string similar to {0} | Notification: {1}.. /// - internal static string Processing_ERROR_UnspecifiedResponse { + internal static string Processing_STATUS_Notification { get { - return ResourceManager.GetString("Processing_ERROR_UnspecifiedResponse", resourceCulture); + return ResourceManager.GetString("Processing_STATUS_Notification", resourceCulture); } } diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.resx b/EventsHandler/Api/EventsHandler/Properties/Resources.resx index ac58b542..de62da8b 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.resx +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.resx @@ -250,7 +250,7 @@ The notification has not been sent to Notify NL: {0}. - {0} = Error Message + {0} = Error message It was not possible to determine what to do with the received notification. @@ -334,8 +334,9 @@ HTTP Request: The case (obtained from OpenZaak Web API service) does not contain any statuses. - - Not standardized (unexpected) API response + + Not standardized (unexpected) API response | {0} | {1} + {0} = HTTP Status Code, {1} = Name of ObjectResult type It was not possible to retrieve any data provider. The loading service might not be set. @@ -491,4 +492,20 @@ The following properties were missing in JSON but they are required. + + {0} | {1} | {2} + {0} = Application name, {1} = Log level, {2} = Log message + + + Notify NL Exception | {0} + {0} = Exception message + + + {0} | {1} + {0} = Name of exception type, {1} = Exception message + + + {0} | Notification: {1}. + {0} = Description of the result, {1} = The initial notification JSON + \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/Interfaces/IProcessingService.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Interfaces/IProcessingService.cs index e09d2be9..b3f445cb 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataProcessing/Interfaces/IProcessingService.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Interfaces/IProcessingService.cs @@ -1,8 +1,7 @@ // © 2023, Worth Systems. -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Enums.OpenKlant; -using EventsHandler.Mapping.Models.Interfaces; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using System.Text.Json; namespace EventsHandler.Services.DataProcessing.Interfaces @@ -13,16 +12,14 @@ namespace EventsHandler.Services.DataProcessing.Interfaces /// This is the heart of the business logic. /// /// - /// The type of the data. - public interface IProcessingService - where TData : IJsonSerializable + public interface IProcessingService { /// /// Processes the given input data in a certain way. /// - /// The data to be processed. + /// The data to be processed. /// - /// The result of the operation + description of the result. + /// The result of the operation + description of the result + details of processed notification. /// /// /// Something could not be queried from external Web API services. @@ -31,6 +28,6 @@ public interface IProcessingService /// /// Strategy could not be determined or option is invalid. /// - internal Task<(ProcessingResult, string)> ProcessAsync(TData data); + internal Task ProcessAsync(object json); } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs index 7627cfed..74a08e26 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs @@ -8,35 +8,63 @@ using EventsHandler.Services.DataProcessing.Strategy.Base.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Manager.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding.Messages.Models.Details; +using EventsHandler.Services.Responding.Messages.Models.Details.Base; +using EventsHandler.Services.Serialization.Interfaces; +using EventsHandler.Services.Validation.Interfaces; using Notify.Exceptions; +using System.Text.Json; using ResourcesEnum = EventsHandler.Mapping.Enums.NotificatieApi.Resources; using ResourcesText = EventsHandler.Properties.Resources; namespace EventsHandler.Services.DataProcessing { - /// - internal sealed class NotifyProcessor : IProcessingService + /// + internal sealed class NotifyProcessor : IProcessingService { + private readonly ISerializationService _serializer; + private readonly IValidationService _validator; private readonly IScenariosResolver _resolver; /// /// Initializes a new instance of the class. /// - public NotifyProcessor(IScenariosResolver resolver) + /// The input de(serializing) service. + /// The input validating service. + /// The strategies resolving service. + public NotifyProcessor( + ISerializationService serializer, + IValidationService validator, + IScenariosResolver resolver) { + this._serializer = serializer; + this._validator = validator; this._resolver = resolver; } - /// - async Task<(ProcessingResult, string)> IProcessingService.ProcessAsync(NotificationEvent notification) // TODO: Introduce model in place of tuple + /// + async Task IProcessingService.ProcessAsync(object json) { + BaseEnhancedDetails details = InfoDetails.Empty; + try { + // Deserialize received JSON payload + NotificationEvent notification = this._serializer.Deserialize(json); + details = notification.Details; + + // Validate deserialized notification to check if it's sufficiently complete to be processed + if (this._validator.Validate(ref notification) is HealthCheck.ERROR_Invalid) + { + // STOP: The notification is not complete; any further processing of it would be pointless + return new ProcessingResult(ProcessingStatus.NotPossible, notification.Details.Message, json, details); + } + // Determine if the received notification is "test" (ping) event => In this case, do nothing if (IsTest(notification)) { - // NOTE: The notification SHOULD not be sent, but it's not a failure and shouldn't be retried - return (ProcessingResult.Skipped, ResourcesText.Processing_ERROR_Notification_Test); + // STOP: The notification SHOULD not be sent; it's just a connectivity test not a failure + return new ProcessingResult(ProcessingStatus.Skipped, ResourcesText.Processing_ERROR_Notification_Test, json, details); } // Choose an adequate business-scenario (strategy) to process the notification @@ -46,43 +74,30 @@ public NotifyProcessor(IScenariosResolver re GettingDataResponse gettingDataResponse; if ((gettingDataResponse = await scenario.TryGetDataAsync(notification)).IsFailure) { - // NOTE: The notification COULD not be sent due to missing or inconsistent data. Retry is necessary - return (ProcessingResult.Failure, string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, gettingDataResponse.Message)); + // RETRY: The notification COULD not be sent due to missing or inconsistent data + return new ProcessingResult(ProcessingStatus.Failure, + string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, gettingDataResponse.Message), json, details); } // Processing the prepared data in a specific way (e.g., sending to "Notify NL") ProcessingDataResponse processingDataResponse = await scenario.ProcessDataAsync(notification, gettingDataResponse.Content); return processingDataResponse.IsFailure - // NOTE: Something bad happened and "Notify NL" did not send the notification as expected - ? (ProcessingResult.Failure, string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, processingDataResponse.Message)) + // RETRY: Something bad happened and "Notify NL" did not send the notification as expected + ? new ProcessingResult(ProcessingStatus.Failure, + string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, processingDataResponse.Message), json, details) - // NOTE: The notification was sent and the completion status was reported to the telemetry API - : (ProcessingResult.Success, ResourcesText.Processing_SUCCESS_Scenario_NotificationSent); - } - // TODO: Include ProcessingResult enum in GettingDataResponse, ProcessingDataResponse, and other... responses - catch (NotImplementedException) - { - // NOTE: The notification COULD not be sent, but it's not a failure, and it shouldn't be retried - return (ProcessingResult.Skipped, ResourcesText.Processing_ERROR_Scenario_NotImplemented); - } - catch (AbortedNotifyingException exception) - { - // NOTE: The notification SHOULD not be sent due to internal condition, and it shouldn't be retried - return (ProcessingResult.Aborted, exception.Message); - } - catch (NotifyClientException exception) - { - // NOTE: The notification COULD not be sent because of issues with "Notify NL" (e.g., authorization or service being down) - return (ProcessingResult.Failure, $"Notify NL Exception | {exception.Message}"); + // SUCCESS: The notification was sent and the completion status was reported to the telemetry API + : new ProcessingResult(ProcessingStatus.Success, ResourcesText.Processing_SUCCESS_Scenario_NotificationSent, json, details); } + // Handling errors in a specific way depends on their types or severities catch (Exception exception) { - // NOTE: The notification COULD not be sent. Retry is necessary - return (ProcessingResult.Failure, $"{exception.GetType().Name} | {exception.Message}"); + return HandleException(exception, json, details); } } + #region Helper methods /// /// Determines whether the received is just a "test" ping. /// @@ -98,5 +113,29 @@ private static bool IsTest(NotificationEvent notification) string.Equals(notification.MainObjectUri.AbsoluteUri, testUrl) && string.Equals(notification.ResourceUri.AbsoluteUri, testUrl); } + + private static ProcessingResult HandleException(Exception exception, object json, BaseEnhancedDetails details) + { + return exception switch + { + // STOP: The JSON payload COULD not be deserialized; any further processing of it would be pointless + JsonException => new ProcessingResult(ProcessingStatus.Skipped, exception.Message, json, details), + + // STOP: The notification COULD not be sent, but it's not a failure + NotImplementedException => new ProcessingResult(ProcessingStatus.Skipped, ResourcesText.Processing_ERROR_Scenario_NotImplemented, json, details), + + // STOP: The notification SHOULD not be sent due to internal condition + AbortedNotifyingException => new ProcessingResult(ProcessingStatus.Aborted, exception.Message, json, details), + + // RETRY: The notification COULD not be sent because of issues with "Notify NL" (e.g., authorization or service being down) + NotifyClientException => new ProcessingResult(ProcessingStatus.Failure, + string.Format(ResourcesText.Processing_ERROR_Exception_Notify, exception.Message), json, details), + + // RETRY: The notification COULD not be sent + _ => new ProcessingResult(ProcessingStatus.Failure, + string.Format(ResourcesText.Processing_ERROR_Exception_Unhandled, exception.GetType().Name, exception.Message), json, details) + }; + } + #endregion } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs new file mode 100644 index 00000000..134e0888 --- /dev/null +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs @@ -0,0 +1,39 @@ +// © 2024, Worth Systems. + +using EventsHandler.Mapping.Enums; +using EventsHandler.Properties; +using EventsHandler.Services.Responding.Messages.Models.Details.Base; + +namespace EventsHandler.Services.DataProcessing.Strategy.Responses +{ + /// + /// Contains the result of processing the given notification JSON. + /// + public readonly struct ProcessingResult // NOTE: Has to be public to be used in Dependency Injection + { + /// + /// The details of processing result. + /// + internal ProcessingStatus Status { get; } + + /// + /// The description of the processing result. + /// + internal string Description { get; } + + /// + /// The details of the processing result. + /// + internal BaseEnhancedDetails Details { get; } + + /// + /// Initializes a new instance of the struct. + /// + public ProcessingResult(ProcessingStatus status, string description, object json, BaseEnhancedDetails details) + { + this.Status = status; + this.Description = string.Format(Resources.Processing_STATUS_Notification, description, json); + this.Details = details; + } + } +} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs index 825e2fea..7c54b87a 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs @@ -1,8 +1,5 @@ // © 2023, Worth Systems. -using EventsHandler.Mapping.Enums; -using EventsHandler.Mapping.Models.Interfaces; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -45,6 +42,25 @@ public interface IRespondingService internal bool ContainsErrorMessage(IDictionary errorDetails, out string errorMessage); } + /// + /// + /// + /// Specialized in processing generic . + /// + /// + /// The generic type of the processing result. + /// + public interface IRespondingService : IRespondingService + { + /// + /// Gets standardized based on the received generic . + /// + /// + /// + /// + internal ObjectResult GetResponse(TResult result); + } + /// /// /// @@ -67,19 +83,4 @@ public interface IRespondingService : IRespondingServic /// internal ObjectResult GetResponse(TResult result, TDetails details); } - - /// - /// - /// - /// Dedicated to be used with objects. - /// - /// - /// The type of the model. - /// - public interface IRespondingService : IRespondingService<(ProcessingResult, string), BaseEnhancedDetails> // NOTE: This interface is implicitly following Adapter Design Pattern - where TModel : IJsonSerializable - { - /// - internal new ObjectResult GetResponse((ProcessingResult Status, string Description) result, BaseEnhancedDetails details); - } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs index f4bc587d..235e4898 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Constants; using EventsHandler.Services.Responding.Messages.Models.Details.Base; namespace EventsHandler.Services.Responding.Messages.Models.Details @@ -13,7 +14,12 @@ internal sealed record InfoDetails : BaseEnhancedDetails /// /// Gets the default . /// - internal static InfoDetails Empty { get; } = new(string.Empty, string.Empty, Array.Empty()); + internal static InfoDetails Empty { get; } = new + ( + message: DefaultValues.Models.DefaultEnumValueName, + cases: DefaultValues.Models.DefaultEnumValueName, + reasons: Array.Empty() + ); /// /// Initializes a new instance of the class. diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs index 93f58be8..6a0b9d26 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs @@ -17,7 +17,7 @@ namespace EventsHandler.Services.Responding { /// - public abstract partial class NotifyResponder : IRespondingService // NOTE: "partial" is introduced by the new RegEx generation approach + public abstract partial class NotifyResponder : IRespondingService // NOTE: "partial" is introduced by the new RegEx generation approach { /// protected ISerializationService Serializer { get; } @@ -199,15 +199,15 @@ bool IRespondingService.ContainsErrorMessage(IDictionary error #region IRespondingService /// - ObjectResult IRespondingService.GetResponse(ProcessingResult result, string details) + ObjectResult IRespondingService.GetResponse(ProcessingStatus status, string details) { - return result switch + return status switch { // HttpStatus Code: 202 Accepted - ProcessingResult.Success => ObjectResultExtensions.AsResult_202(details), + ProcessingStatus.Success => ObjectResultExtensions.AsResult_202(details), // HttpStatus Code: 400 BadRequest - ProcessingResult.Failure => ((IRespondingService)this).GetExceptionResponse(details), + ProcessingStatus.Failure => ((IRespondingService)this).GetExceptionResponse(details), // HttpStatus Code: 501 Not Implemented _ => ObjectResultExtensions.AsResult_501() diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs index ddf3656c..9c66a972 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs @@ -3,11 +3,10 @@ using EventsHandler.Constants; using EventsHandler.Extensions; using EventsHandler.Mapping.Enums; -using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Details; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Services.Responding.Messages.Models.Information; using EventsHandler.Services.Responding.Messages.Models.Successes; @@ -21,7 +20,7 @@ namespace EventsHandler.Services.Responding { /// - internal sealed class OmcResponder : IRespondingService + internal sealed class OmcResponder : IRespondingService { private readonly IDetailsBuilder _detailsBuilder; @@ -42,7 +41,7 @@ ObjectResult IRespondingService.GetExceptionResponse(Exception exception) return this._detailsBuilder.Get(Reasons.HttpRequestError, exception.Message).AsResult_400(); } - return ((IRespondingService)this).GetExceptionResponse(exception.Message); + return ((IRespondingService)this).GetExceptionResponse(exception.Message); } /// @@ -114,36 +113,29 @@ bool IRespondingService.ContainsErrorMessage(IDictionary error } #endregion - #region IRespondingService - /// - ObjectResult IRespondingService<(ProcessingResult, string), BaseEnhancedDetails>.GetResponse((ProcessingResult, string) result, BaseEnhancedDetails details) - { - return ((IRespondingService)this).GetResponse(result, details); - } - #endregion - - #region Implementation - /// - ObjectResult IRespondingService.GetResponse((ProcessingResult Status, string Description) result, BaseEnhancedDetails details) + #region IRespondingService + /// + ObjectResult IRespondingService.GetResponse(ProcessingResult result) { return result.Status switch { - ProcessingResult.Success + ProcessingStatus.Success => new ProcessingSucceeded(result.Description).AsResult_202(), - ProcessingResult.Skipped or - ProcessingResult.Aborted + ProcessingStatus.Skipped or + ProcessingStatus.Aborted => new ProcessingSkipped(result.Description).AsResult_206(), - ProcessingResult.Failure - => details.Message.StartsWith(DefaultValues.Validation.HttpRequest_ErrorMessage) // NOTE: HTTP Request error messages are always simplified - ? new HttpRequestFailed.Simplified(details).AsResult_400() - : details.Cases.IsNotNullOrEmpty() && details.Reasons.Any() - ? new ProcessingFailed.Detailed(HttpStatusCode.UnprocessableEntity, result.Description, details).AsResult_400() + ProcessingStatus.Failure + => result.Details.Message.StartsWith(DefaultValues.Validation.HttpRequest_ErrorMessage) // NOTE: HTTP Request error messages are always simplified + ? new HttpRequestFailed.Simplified(result.Details).AsResult_400() + + : result.Details.Cases.IsNotNullOrEmpty() && result.Details.Reasons.HasAny() + ? new ProcessingFailed.Detailed(HttpStatusCode.UnprocessableEntity, result.Description, result.Details).AsResult_400() : new ProcessingFailed.Simplified(HttpStatusCode.UnprocessableEntity, result.Description).AsResult_400(), - ProcessingResult.NotPossible - => new DeserializationFailed(details).AsResult_422(), + ProcessingStatus.NotPossible + => new DeserializationFailed(result.Details).AsResult_422(), _ => ObjectResultExtensions.AsResult_501() }; diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs index fe545044..1c3a5039 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs @@ -25,7 +25,7 @@ namespace EventsHandler.Services.Responding.v1 internal sealed class NotifyCallbackResponder : NotifyResponder { private readonly WebApiConfiguration _configuration; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; private readonly ITelemetryService _telemetry; /// @@ -60,11 +60,11 @@ DeliveryStatuses.TemporaryFailure or // Positive status was returned by Notify NL ? OmcController.LogApiResponse(LogLevel.Information, - this._responder.GetResponse(ProcessingResult.Success, GetDeliveryStatusLogMessage(callback))) + this._responder.GetResponse(ProcessingStatus.Success, GetDeliveryStatusLogMessage(callback))) // Failure status was returned by Notify NL : OmcController.LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingResult.Failure, GetDeliveryStatusLogMessage(callback))); + this._responder.GetResponse(ProcessingStatus.Failure, GetDeliveryStatusLogMessage(callback))); } catch (Exception exception) { diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs index 28424799..75d8a902 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs @@ -23,7 +23,7 @@ namespace EventsHandler.Services.Responding.v2 internal sealed class NotifyCallbackResponder : NotifyResponder { private readonly WebApiConfiguration _configuration; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; private readonly ITelemetryService _telemetry; /// @@ -100,8 +100,8 @@ private IActionResult LogContactRegistration(DeliveryReceipt callback, FeedbackT this._responder.GetResponse(feedbackType is FeedbackTypes.Success or FeedbackTypes.Info - ? ProcessingResult.Success // NOTE: Everything good (either final or intermediate state) - : ProcessingResult.Failure, // NOTE: The notification couldn't be delivered as planned + ? ProcessingStatus.Success // NOTE: Everything good (either final or intermediate state) + : ProcessingStatus.Failure, // NOTE: The notification couldn't be delivered as planned GetDeliveryStatusLogMessage(callback))); } catch (Exception exception) diff --git a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs index d776f348..8e4f04b9 100644 --- a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs +++ b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs @@ -5,6 +5,7 @@ using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Properties; using EventsHandler.Services.DataProcessing.Interfaces; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding; using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Base; @@ -12,33 +13,23 @@ using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Builder; -using EventsHandler.Services.Serialization.Interfaces; -using EventsHandler.Services.Validation.Interfaces; using EventsHandler.Services.Versioning.Interfaces; -using EventsHandler.Utilities._TestHelpers; using Microsoft.AspNetCore.Mvc; using System.Net; -using System.Text.Json; namespace EventsHandler.IntegrationTests.Controllers { [TestFixture] public sealed class EventsControllerTests { - private static readonly object s_testJson = new(); - - private Mock _serializerMock = null!; - private Mock> _validatorMock = null!; - private Mock> _processorMock = null!; + private Mock _processorMock = null!; private Mock> _responderMock = null!; private Mock _registerMock = null!; [OneTimeSetUp] public void InitializeMocks() { - this._serializerMock = new Mock(MockBehavior.Strict); - this._validatorMock = new Mock>(MockBehavior.Strict); - this._processorMock = new Mock>(MockBehavior.Strict); + this._processorMock = new Mock(MockBehavior.Strict); this._responderMock = new Mock>(MockBehavior.Strict); this._registerMock = new Mock(MockBehavior.Strict); } @@ -46,66 +37,12 @@ public void InitializeMocks() [SetUp] public void InitializeTests() { - this._serializerMock.Reset(); - this._serializerMock.Setup(mock => mock.Deserialize( - It.IsAny())) - .Returns(NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated); - - this._validatorMock.Reset(); - this._validatorMock.Setup(mock => mock.Validate( - ref It.Ref.IsAny)) - .Returns(HealthCheck.OK_Inconsistent); - this._processorMock.Reset(); this._responderMock.Reset(); + this._registerMock.Reset(); } #region Testing IActionResult API responses - [Test] - public async Task ListenAsync_Failure_Deserialize_ReturnsErrorResult() - { - // Arrange - this._serializerMock.Setup(mock => mock.Deserialize(It.IsAny())) - .Throws(); - - EventsController testController = GetTestEventsController_WithRealResponder(); - - // Act - IActionResult actualResult = await testController.ListenAsync(s_testJson); - - // Assert - AssertWithConditions( - actualResult, - HttpStatusCode.UnprocessableEntity, - Resources.Operation_ERROR_Deserialization_Failure, - Resources.Deserialization_ERROR_InvalidJson_Message); - } - - [Test] - public async Task ListenAsync_Failure_Validate_ReturnsErrorResult() - { - // Arrange - this._validatorMock.Setup(mock => mock.Validate(ref It.Ref.IsAny)) - .Returns((ref NotificationEvent notificationEvent) => - { - notificationEvent.Details = GetTestErrorDetails_Notification_Properties(); // NOTE: Other ErrorDetails are also possible, but that's covered in Validator tests - - return HealthCheck.ERROR_Invalid; - }); - - EventsController testController = GetTestEventsController_WithRealResponder(); - - // Act - IActionResult actualResult = await testController.ListenAsync(s_testJson); - - // Assert - AssertWithConditions( - actualResult, - HttpStatusCode.UnprocessableEntity, - Resources.Operation_ERROR_Deserialization_Failure, - Resources.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message); - } - [Test] public async Task ListenAsync_Failure_ProcessAsync_ReturnsErrorResult() { @@ -116,7 +53,7 @@ public async Task ListenAsync_Failure_ProcessAsync_ReturnsErrorResult() EventsController testController = GetTestEventsController_WithRealResponder(); // Act - IActionResult actualResult = await testController.ListenAsync(s_testJson); + IActionResult actualResult = await testController.ListenAsync(default!); // Assert AssertWithConditions( @@ -126,77 +63,31 @@ public async Task ListenAsync_Failure_ProcessAsync_ReturnsErrorResult() Resources.HttpRequest_ERROR_Message); } - [Test] - public async Task ListenAsync_Success_Validate_HealthCheck_OK_Inconsistent_ReturnsInfoResult() - { - // Arrange - this._validatorMock.Setup(mock => mock.Validate(ref It.Ref.IsAny)) - .Returns((ref NotificationEvent notificationEvent) => - { - notificationEvent.Details = GetTestInfoDetails_Partial(); - - return HealthCheck.OK_Inconsistent; - }); - - this._processorMock.Setup(mock => mock.ProcessAsync(It.IsAny())) - .ReturnsAsync((ProcessingResult.Success, Resources.Processing_SUCCESS_Scenario_NotificationSent)); - - EventsController testController = GetTestEventsController_WithRealResponder(); - - // Act - IActionResult actualResult = await testController.ListenAsync(s_testJson); - - // Assert - AssertWithConditions( - actualResult, - HttpStatusCode.Accepted, - Resources.Processing_SUCCESS_Scenario_NotificationSent + AddNotificationDetails(s_testJson), - Resources.Operation_SUCCESS_Deserialization_Partial); - } - [Test] public async Task ListenAsync_Success_Validate_HealthCheck_OK_Valid_ReturnsInfoResult() { // Arrange - this._validatorMock.Setup(mock => mock.Validate(ref It.Ref.IsAny)) - .Returns((ref NotificationEvent notificationEvent) => - { - notificationEvent.Details = GetTestInfoDetails_Success(); - - return HealthCheck.OK_Valid; - }); - - this._processorMock.Setup(mock => mock.ProcessAsync(It.IsAny())) - .ReturnsAsync((ProcessingResult.Success, Resources.Processing_SUCCESS_Scenario_NotificationSent)); + this._processorMock + .Setup(mock => mock.ProcessAsync( + It.IsAny())) + .ReturnsAsync( + new ProcessingResult(ProcessingStatus.Success, Resources.Processing_SUCCESS_Scenario_NotificationSent, default!, GetTestInfoDetails_Success())); EventsController testController = GetTestEventsController_WithRealResponder(); // Act - IActionResult actualResult = await testController.ListenAsync(s_testJson); + IActionResult actualResult = await testController.ListenAsync(default!); // Assert AssertWithConditions( actualResult, HttpStatusCode.Accepted, - Resources.Processing_SUCCESS_Scenario_NotificationSent + AddNotificationDetails(s_testJson), + Resources.Processing_SUCCESS_Scenario_NotificationSent, Resources.Operation_SUCCESS_Deserialization_Success); } #endregion #region Helper methods - private static ErrorDetails GetTestErrorDetails_Notification_Properties() - { - return new ErrorDetails( - Resources.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message, - "hoofdObject, resourceUrl", - new[] - { - Resources.Deserialization_ERROR_NotDeserialized_Notification_Properties_Reason1, - Resources.Deserialization_ERROR_NotDeserialized_Notification_Properties_Reason2, - Resources.Deserialization_ERROR_NotDeserialized_Notification_Properties_Reason3 - }); - } - private static InfoDetails GetTestInfoDetails_Partial() => new(Resources.Operation_SUCCESS_Deserialization_Partial, string.Empty, Array.Empty()); @@ -205,12 +96,11 @@ private static InfoDetails GetTestInfoDetails_Success() private EventsController GetTestEventsController_WithRealResponder() { - return new EventsController(this._serializerMock.Object, this._validatorMock.Object, - this._processorMock.Object, GetRealResponderService(), + return new EventsController(this._processorMock.Object, GetRealResponderService(), this._registerMock.Object); } - private static IRespondingService GetRealResponderService() => new OmcResponder(new DetailsBuilder()); + private static IRespondingService GetRealResponderService() => new OmcResponder(new DetailsBuilder()); private static void AssertWithConditions @@ -247,8 +137,6 @@ string expectedDetailsMessage } }); } - - private static string AddNotificationDetails(object json) => $" | Notification: {json}"; #endregion } } \ No newline at end of file diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Extensions/EnumExtensionsTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Extensions/EnumExtensionsTests.cs index 1b24452c..038d09d6 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Extensions/EnumExtensionsTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Extensions/EnumExtensionsTests.cs @@ -49,12 +49,12 @@ public void GetEnumName_NotDefined_ReturnsDefaultEnumOptionName() #endregion #region ConvertToLogLevel - [TestCase(ProcessingResult.Success, LogLevel.Information)] - [TestCase(ProcessingResult.Skipped, LogLevel.Warning)] - [TestCase(ProcessingResult.Aborted, LogLevel.Warning)] - [TestCase(ProcessingResult.NotPossible, LogLevel.Error)] - [TestCase(ProcessingResult.Failure, LogLevel.Error)] - public void ConvertToLogLevel_ForValidEnum_ReturnsExpectedConvertedValue(ProcessingResult testStartValue, LogLevel expectedEndValue) + [TestCase(ProcessingStatus.Success, LogLevel.Information)] + [TestCase(ProcessingStatus.Skipped, LogLevel.Warning)] + [TestCase(ProcessingStatus.Aborted, LogLevel.Warning)] + [TestCase(ProcessingStatus.NotPossible, LogLevel.Error)] + [TestCase(ProcessingStatus.Failure, LogLevel.Error)] + public void ConvertToLogLevel_ForValidEnum_ReturnsExpectedConvertedValue(ProcessingStatus testStartValue, LogLevel expectedEndValue) { // Act LogLevel actualValue = testStartValue.ConvertToLogLevel(); @@ -63,8 +63,8 @@ public void ConvertToLogLevel_ForValidEnum_ReturnsExpectedConvertedValue(Process Assert.That(actualValue, Is.EqualTo(expectedEndValue)); } - [TestCase((ProcessingResult)666, LogLevel.None)] - public void ConvertToLogLevel_ForInvalidEnum_ReturnsExpectedConvertedValue(ProcessingResult testStartValue, LogLevel expectedEndValue) + [TestCase((ProcessingStatus)666, LogLevel.None)] + public void ConvertToLogLevel_ForInvalidEnum_ReturnsExpectedConvertedValue(ProcessingStatus testStartValue, LogLevel expectedEndValue) { // Act LogLevel actualValue = testStartValue.ConvertToLogLevel(); diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs index 95ae93ee..5e910ffc 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs @@ -1,7 +1,6 @@ // © 2024, Worth Systems. using EventsHandler.Mapping.Enums; -using EventsHandler.Mapping.Enums.NotificatieApi; using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Services.DataProcessing; using EventsHandler.Services.DataProcessing.Enums; @@ -10,8 +9,11 @@ using EventsHandler.Services.DataProcessing.Strategy.Manager.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Serialization.Interfaces; +using EventsHandler.Services.Validation.Interfaces; using EventsHandler.Utilities._TestHelpers; using Moq; +using System.Text.Json; using ResourcesText = EventsHandler.Properties.Resources; namespace EventsHandler.UnitTests.Services.DataProcessing @@ -19,52 +21,79 @@ namespace EventsHandler.UnitTests.Services.DataProcessing [TestFixture] public sealed class NotifyProcessorTests { - private Mock> _mockedScenariosResolver = null!; - private IProcessingService _processor = null!; + private Mock _serializerMock = null!; + private Mock> _validatorMock = null!; + private Mock> _mockedResolver = null!; + + private IProcessingService _processor = null!; [OneTimeSetUp] public void InitializeTests() { - this._mockedScenariosResolver = new Mock>(MockBehavior.Strict); - this._processor = new NotifyProcessor(this._mockedScenariosResolver.Object); + this._serializerMock = new Mock(MockBehavior.Strict); + this._validatorMock = new Mock>(MockBehavior.Strict); + this._mockedResolver = new Mock>(MockBehavior.Strict); + + this._processor = new NotifyProcessor(this._serializerMock.Object, this._validatorMock.Object, this._mockedResolver.Object); } [SetUp] public void ResetTests() { - this._mockedScenariosResolver.Reset(); + this._serializerMock.Reset(); + this._serializerMock + .Setup(mock => mock.Deserialize( + It.IsAny())) + .Returns(NotificationEventHandler.GetNotification_Real_CaseCreateScenario_TheHague().Deserialized); + + this._validatorMock.Reset(); + this._validatorMock + .Setup(mock => mock.Validate( + ref It.Ref.IsAny)) + .Returns(HealthCheck.OK_Valid); + + this._mockedResolver.Reset(); } #region Test data - private static readonly NotificationEvent s_validNotification = - NotificationEventHandler.GetNotification_Real_CaseUpdateScenario_TheHague().Deserialized(); + private static readonly string s_validNotification = + NotificationEventHandler.GetNotification_Real_CaseUpdateScenario_TheHague(); #endregion #region ProcessAsync() [Test] - public async Task ProcessAsync_TestNotification_ReturnsProcessingResult_Skipped() + public async Task ProcessAsync_Failed_Deserialization_ReturnsProcessingResult_Skipped() { // Arrange - var testUri = new Uri("http://some.hoofdobject.nl/"); + this._serializerMock + .Setup(mock => mock.Deserialize( + It.IsAny())) + .Throws(); - var testNotification = new NotificationEvent + // Act + ProcessingResult result = await this._processor.ProcessAsync(new object()); + + // Assert + Assert.Multiple(() => { - Channel = Channels.Unknown, - Resource = Resources.Unknown, - MainObjectUri = testUri, - ResourceUri = testUri - }; + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); + Assert.That(result.Description, Is.EqualTo("Exception of type 'System.Text.Json.JsonException' was thrown. | Notification: System.Object.")); + }); + } + [Test] + public async Task ProcessAsync_TestNotification_ReturnsProcessingResult_Skipped() + { // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(testNotification); + ProcessingResult result = await this._processor.ProcessAsync(NotificationEventHandler.GetNotification_Test_Ping()); // Assert VerifyMethodsCalls(0); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Skipped)); - Assert.That(message, Is.EqualTo(ResourcesText.Processing_ERROR_Notification_Test)); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_ERROR_Notification_Test)); }); } @@ -72,20 +101,20 @@ public async Task ProcessAsync_TestNotification_ReturnsProcessingResult_Skipped( public async Task ProcessAsync_ValidNotification_UnknownScenario_ReturnsProcessingResult_Skipped() { // Arrange - this._mockedScenariosResolver.Setup(mock => mock.DetermineScenarioAsync( + this._mockedResolver.Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) .Throws(); // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(s_validNotification); + ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert VerifyMethodsCalls(1); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Skipped)); - Assert.That(message, Is.EqualTo(ResourcesText.Processing_ERROR_Scenario_NotImplemented)); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_ERROR_Scenario_NotImplemented)); }); } @@ -93,20 +122,21 @@ public async Task ProcessAsync_ValidNotification_UnknownScenario_ReturnsProcessi public async Task ProcessAsync_InternalErrors_WhenResolvingScenario_ReturnsProcessingResult_Failure() { // Arrange - this._mockedScenariosResolver.Setup(mock => mock.DetermineScenarioAsync( + this._mockedResolver.Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) .Throws(); // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(s_validNotification); + ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert VerifyMethodsCalls(1); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Failure)); - Assert.That(message, Does.StartWith($"{nameof(HttpRequestException)} | Exception of type '{typeof(HttpRequestException).FullName}' was")); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); + Assert.That(result.Description, Does.StartWith( + $"{nameof(HttpRequestException)} | Exception of type '{typeof(HttpRequestException).FullName}' was")); }); } @@ -120,26 +150,22 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_FailedGetDataResp It.IsAny())) .ReturnsAsync(GettingDataResponse.Failure()); - this._mockedScenariosResolver + this._mockedResolver .Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) .ReturnsAsync(mockedNotifyScenario.Object); // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(s_validNotification); + ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert VerifyMethodsCalls(1); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Failure)); - - string expectedMessage = - ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", - ResourcesText.Processing_ERROR_Scenario_NotificationMethod); - - Assert.That(message, Is.EqualTo(expectedMessage)); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); + Assert.That(result.Description, Is.EqualTo( + ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotificationMethod))); }); } @@ -163,25 +189,22 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataRes It.IsAny>())) .ReturnsAsync(ProcessingDataResponse.Failure(processingErrorText)); - this._mockedScenariosResolver + this._mockedResolver .Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) .ReturnsAsync(mockedNotifyScenario.Object); // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(s_validNotification); + ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert VerifyMethodsCalls(1); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Failure)); - - string expectedMessage = - ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", processingErrorText); - - Assert.That(message, Is.EqualTo(expectedMessage)); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); + Assert.That(result.Description, Is.EqualTo( + ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", processingErrorText))); }); } @@ -204,21 +227,21 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataRes It.IsAny>())) .ReturnsAsync(ProcessingDataResponse.Success); - this._mockedScenariosResolver + this._mockedResolver .Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) .ReturnsAsync(mockedNotifyScenario.Object); // Act - (ProcessingResult status, string? message) = await this._processor.ProcessAsync(s_validNotification); + ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert VerifyMethodsCalls(1); Assert.Multiple(() => { - Assert.That(status, Is.EqualTo(ProcessingResult.Success)); - Assert.That(message, Is.EqualTo(ResourcesText.Processing_SUCCESS_Scenario_NotificationSent)); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Success)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_SUCCESS_Scenario_NotificationSent)); }); } #endregion @@ -226,7 +249,7 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataRes #region Verify private void VerifyMethodsCalls(int determineScenarioInvokeCount) { - this._mockedScenariosResolver + this._mockedResolver .Verify(mock => mock.DetermineScenarioAsync( It.IsAny()), Times.Exactly(determineScenarioInvokeCount)); diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Templates/NotifyTemplatesAnalyzerTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Templates/NotifyTemplatesAnalyzerTests.cs index 09e032f1..8639e9a6 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Templates/NotifyTemplatesAnalyzerTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Templates/NotifyTemplatesAnalyzerTests.cs @@ -209,11 +209,11 @@ private static IEnumerable GetNotifications_WithOrphans() Description = "3 mappable Placeholders used with manually created Notification having 3 matching properties (Orphans) returns 3 expected Personalizations", Placeholders = new[] { - NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root + NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root NotificationEventHandler.Orphan_Test_Property_2, // Matching placeholder (the POCO model has this property) => nested - NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested + NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested }, - Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans(), SerializedExpectedPersonalization = $"{{" + $"\"{NotificationEventHandler.Orphan_Test_Property_1}\":{NotificationEventHandler.Orphan_Test_Value_1}," + @@ -227,9 +227,9 @@ private static IEnumerable GetNotifications_WithOrphans() Description = "3 mappable Placeholders used with dynamically deserialized Notification having 3 matching properties (Orphans) returns 3 expected Personalizations", Placeholders = new[] { - NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root + NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root NotificationEventHandler.Orphan_Test_Property_2, // Matching placeholder (the POCO model has this property) => nested - NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested + NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested }, Notification = NotificationEventHandler.GetNotification_Test_AllAttributes_WithOrphans().Deserialized(), SerializedExpectedPersonalization = @@ -248,7 +248,7 @@ private static IEnumerable GetNotifications_WithOrphans() notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) notExistingProperty2 // Unmatching placeholder (the POCO model doesn't have such property) }, - Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans(), SerializedExpectedPersonalization = $"{{" + $"\"{notExistingProperty1}\":\"{NotifyTemplatesAnalyzer.ValueNotAvailable}\"," + @@ -262,11 +262,11 @@ private static IEnumerable GetNotifications_WithOrphans() "(1 unmatching and 2 matching Orphans) returns 3 Personalizations (1 default, 2 expected)", Placeholders = new[] { - notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) + notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root - NotificationEventHandler.Orphan_Test_Property_2 // Matching placeholder (the POCO model has this property) => nested + NotificationEventHandler.Orphan_Test_Property_2 // Matching placeholder (the POCO model has this property) => nested }, - Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_WithOrphans(), SerializedExpectedPersonalization = $"{{" + $"\"{notExistingProperty1}\":\"{NotifyTemplatesAnalyzer.ValueNotAvailable}\"," + @@ -281,10 +281,10 @@ private static IEnumerable GetNotifications_WithOrphans() Description = "2 Placeholders (2 mappable) used with Notification having 2 matching properties (regular) returns 2 expected Personalizations", Placeholders = new[] { - NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root + NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root NotificationEventHandler.Regular_Real_Property_SourceOrganization // Matching placeholder (the POCO model has this property) => nested }, - Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization(), SerializedExpectedPersonalization = $"{{" + $"\"{NotificationEventHandler.Regular_Real_Property_Channel}\":\"{NotificationEventHandler.Regular_Real_Value_Channel_String}\"," + @@ -297,11 +297,11 @@ private static IEnumerable GetNotifications_WithOrphans() Description = "3 Placeholders (1 unmappable, 2 mappable) used with Notification having 2 matching properties (regular) returns 2 Personalizations (1 default, 2 expected)", Placeholders = new[] { - notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) - NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root + notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) + NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root NotificationEventHandler.Regular_Real_Property_SourceOrganization // Matching placeholder (the POCO model has this property) => nested }, - Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization(), SerializedExpectedPersonalization = $"{{" + $"\"{notExistingProperty1}\":\"{NotifyTemplatesAnalyzer.ValueNotAvailable}\"," + @@ -319,14 +319,14 @@ private static IEnumerable GetNotifications_WithOrphans() notExistingProperty1, // Unmatching placeholder (the POCO model doesn't have such property) notExistingProperty2, // Unmatching placeholder (the POCO model doesn't have such property) // Regular - NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root + NotificationEventHandler.Regular_Real_Property_Channel, // Matching placeholder (the POCO model has this property) => root NotificationEventHandler.Regular_Real_Property_SourceOrganization, // Matching placeholder (the POCO model has this property) => nested // Orphans - NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root - NotificationEventHandler.Orphan_Test_Property_2, // Matching placeholder (the POCO model has this property) => nested - NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested + NotificationEventHandler.Orphan_Test_Property_1, // Matching placeholder (the POCO model has this property) => root + NotificationEventHandler.Orphan_Test_Property_2, // Matching placeholder (the POCO model has this property) => nested + NotificationEventHandler.Orphan_Test_Property_3 // Matching placeholder (the POCO model has this property) => nested }, - Notification = NotificationEventHandler.GetNotification_Test_WithMixed_RegularAndOrphans_Properties_ManuallyCreated(), + Notification = NotificationEventHandler.GetNotification_Test_WithMixed_RegularAndOrphans_Properties(), SerializedExpectedPersonalization = $"{{" + $"\"{notExistingProperty1}\":\"{NotifyTemplatesAnalyzer.ValueNotAvailable}\"," + diff --git a/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs b/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs index 03d48fa4..d803392c 100644 --- a/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs +++ b/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs @@ -37,7 +37,20 @@ internal static class NotificationEventHandler #endregion #region Notifications (Test) - internal static NotificationEvent GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated() + internal static NotificationEvent GetNotification_Test_Ping() + { + var testUri = new Uri("http://some.hoofdobject.nl/"); + + return new NotificationEvent + { + Channel = Channels.Unknown, + Resource = Resources.Unknown, + MainObjectUri = testUri, + ResourceUri = testUri + }; + } + + internal static NotificationEvent GetNotification_Test_EmptyAttributes_WithOrphans() { // CASE #1: Manual creation of NotificationEvent NotificationEvent testNotification = new() @@ -118,7 +131,7 @@ internal static string GetNotification_Test_AllAttributes_Null_WithoutOrphans() return jsonPayload; } - internal static NotificationEvent GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization_ManuallyCreated() + internal static NotificationEvent GetNotification_Test_EmptyAttributes_With_Channel_And_SourceOrganization() { return new NotificationEvent { @@ -130,9 +143,9 @@ internal static NotificationEvent GetNotification_Test_EmptyAttributes_With_Chan }; } - internal static NotificationEvent GetNotification_Test_WithMixed_RegularAndOrphans_Properties_ManuallyCreated() + internal static NotificationEvent GetNotification_Test_WithMixed_RegularAndOrphans_Properties() { - NotificationEvent mixedNotification = GetNotification_Test_EmptyAttributes_WithOrphans_ManuallyCreated(); + NotificationEvent mixedNotification = GetNotification_Test_EmptyAttributes_WithOrphans(); // Update attributes EventAttributes updatedAttributes = mixedNotification.Attributes; From 5047dba8661abeb954adebb38ff6f06242bbd8f9 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Fri, 1 Nov 2024 16:06:32 +0100 Subject: [PATCH 03/19] Adjust unit tests and remove integration tests (for now) --- .../Controllers/EventsControllerTests.cs | 134 +-------------- .../DataProcessing/NotifyProcessorTests.cs | 157 +++++++++++++----- .../_TestHelpers/NotificationEventHandler.cs | 9 +- 3 files changed, 125 insertions(+), 175 deletions(-) diff --git a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs index 8e4f04b9..628582ee 100644 --- a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs +++ b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Controllers/EventsControllerTests.cs @@ -1,142 +1,10 @@ // © 2023, Worth Systems. -using EventsHandler.Controllers; -using EventsHandler.Mapping.Enums; -using EventsHandler.Mapping.Models.POCOs.NotificatieApi; -using EventsHandler.Properties; -using EventsHandler.Services.DataProcessing.Interfaces; -using EventsHandler.Services.DataProcessing.Strategy.Responses; -using EventsHandler.Services.Responding; -using EventsHandler.Services.Responding.Interfaces; -using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details; -using EventsHandler.Services.Responding.Messages.Models.Errors; -using EventsHandler.Services.Responding.Messages.Models.Successes; -using EventsHandler.Services.Responding.Results.Builder; -using EventsHandler.Services.Versioning.Interfaces; -using Microsoft.AspNetCore.Mvc; -using System.Net; - namespace EventsHandler.IntegrationTests.Controllers { [TestFixture] public sealed class EventsControllerTests { - private Mock _processorMock = null!; - private Mock> _responderMock = null!; - private Mock _registerMock = null!; - - [OneTimeSetUp] - public void InitializeMocks() - { - this._processorMock = new Mock(MockBehavior.Strict); - this._responderMock = new Mock>(MockBehavior.Strict); - this._registerMock = new Mock(MockBehavior.Strict); - } - - [SetUp] - public void InitializeTests() - { - this._processorMock.Reset(); - this._responderMock.Reset(); - this._registerMock.Reset(); - } - - #region Testing IActionResult API responses - [Test] - public async Task ListenAsync_Failure_ProcessAsync_ReturnsErrorResult() - { - // Arrange - this._processorMock.Setup(mock => mock.ProcessAsync(It.IsAny())) - .Throws(); - - EventsController testController = GetTestEventsController_WithRealResponder(); - - // Act - IActionResult actualResult = await testController.ListenAsync(default!); - - // Assert - AssertWithConditions( - actualResult, - HttpStatusCode.BadRequest, - Resources.Operation_ERROR_HttpRequest_Failure, - Resources.HttpRequest_ERROR_Message); - } - - [Test] - public async Task ListenAsync_Success_Validate_HealthCheck_OK_Valid_ReturnsInfoResult() - { - // Arrange - this._processorMock - .Setup(mock => mock.ProcessAsync( - It.IsAny())) - .ReturnsAsync( - new ProcessingResult(ProcessingStatus.Success, Resources.Processing_SUCCESS_Scenario_NotificationSent, default!, GetTestInfoDetails_Success())); - - EventsController testController = GetTestEventsController_WithRealResponder(); - - // Act - IActionResult actualResult = await testController.ListenAsync(default!); - - // Assert - AssertWithConditions( - actualResult, - HttpStatusCode.Accepted, - Resources.Processing_SUCCESS_Scenario_NotificationSent, - Resources.Operation_SUCCESS_Deserialization_Success); - } - #endregion - - #region Helper methods - private static InfoDetails GetTestInfoDetails_Partial() - => new(Resources.Operation_SUCCESS_Deserialization_Partial, string.Empty, Array.Empty()); - - private static InfoDetails GetTestInfoDetails_Success() - => new(Resources.Operation_SUCCESS_Deserialization_Success, string.Empty, Array.Empty()); - - private EventsController GetTestEventsController_WithRealResponder() - { - return new EventsController(this._processorMock.Object, GetRealResponderService(), - this._registerMock.Object); - } - - private static IRespondingService GetRealResponderService() => new OmcResponder(new DetailsBuilder()); - - private static void AssertWithConditions - - ( - IActionResult actualResult, - HttpStatusCode expectedHttpStatusCode, - string expectedStatusDescription, - string expectedDetailsMessage - ) - where TExpectedApiActionResultType : IActionResult - { - Assert.Multiple(() => - { - Assert.That(actualResult, Is.TypeOf()); - - var castResult = (ObjectResult)actualResult; - Assert.That(castResult.StatusCode, Is.EqualTo((int)expectedHttpStatusCode), "Status code"); - - if (castResult.Value is BaseStandardResponseBody simpleResponse) - { - Assert.That(simpleResponse.StatusCode, Is.EqualTo(expectedHttpStatusCode), "Status code"); - Assert.That(simpleResponse.StatusDescription, Is.EqualTo(expectedStatusDescription), "Status description"); - - if (castResult.Value is BaseEnhancedStandardResponseBody enhancedResponse) - { - Assert.That(enhancedResponse.Details.Message, Is.EqualTo(expectedDetailsMessage), "Details message"); - } - } - else - { - Assert.Fail($"Wrong type of API response body.{Environment.NewLine}" + - $"Expected: {typeof(TExpectedApiResponseBodyType)}{Environment.NewLine}" + - $"But was: {castResult.Value!.GetType()}"); - } - }); - } - #endregion + // TODO: New approach of integration tests is required } } \ No newline at end of file diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs index 5e910ffc..620551cd 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs @@ -9,6 +9,7 @@ using EventsHandler.Services.DataProcessing.Strategy.Manager.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Serialization.Interfaces; using EventsHandler.Services.Validation.Interfaces; using EventsHandler.Utilities._TestHelpers; @@ -21,8 +22,8 @@ namespace EventsHandler.UnitTests.Services.DataProcessing [TestFixture] public sealed class NotifyProcessorTests { - private Mock _serializerMock = null!; - private Mock> _validatorMock = null!; + private Mock _mockedSerializer = null!; + private Mock> _mockedValidator = null!; private Mock> _mockedResolver = null!; private IProcessingService _processor = null!; @@ -30,24 +31,26 @@ public sealed class NotifyProcessorTests [OneTimeSetUp] public void InitializeTests() { - this._serializerMock = new Mock(MockBehavior.Strict); - this._validatorMock = new Mock>(MockBehavior.Strict); + this._mockedSerializer = new Mock(MockBehavior.Strict); + this._mockedValidator = new Mock>(MockBehavior.Strict); this._mockedResolver = new Mock>(MockBehavior.Strict); - this._processor = new NotifyProcessor(this._serializerMock.Object, this._validatorMock.Object, this._mockedResolver.Object); + this._processor = new NotifyProcessor(this._mockedSerializer.Object, this._mockedValidator.Object, this._mockedResolver.Object); } [SetUp] public void ResetTests() { - this._serializerMock.Reset(); - this._serializerMock + // By default, Serializer would succeed + this._mockedSerializer.Reset(); + this._mockedSerializer .Setup(mock => mock.Deserialize( It.IsAny())) .Returns(NotificationEventHandler.GetNotification_Real_CaseCreateScenario_TheHague().Deserialized); - this._validatorMock.Reset(); - this._validatorMock + // By default, Validator would succeed + this._mockedValidator.Reset(); + this._mockedValidator .Setup(mock => mock.Validate( ref It.Ref.IsAny)) .Returns(HealthCheck.OK_Valid); @@ -56,6 +59,8 @@ public void ResetTests() } #region Test data + private const string TestExceptionMessage = "Test exception message."; + private static readonly string s_validNotification = NotificationEventHandler.GetNotification_Real_CaseUpdateScenario_TheHague(); #endregion @@ -65,10 +70,10 @@ public void ResetTests() public async Task ProcessAsync_Failed_Deserialization_ReturnsProcessingResult_Skipped() { // Arrange - this._serializerMock + this._mockedSerializer .Setup(mock => mock.Deserialize( It.IsAny())) - .Throws(); + .Throws(() => new JsonException(TestExceptionMessage)); // NOTE: Overrule Serializer mock and make it fail // Act ProcessingResult result = await this._processor.ProcessAsync(new object()); @@ -76,29 +81,80 @@ public async Task ProcessAsync_Failed_Deserialization_ReturnsProcessingResult_Sk // Assert Assert.Multiple(() => { + VerifyMethodsCalls(1, 0, 0); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); - Assert.That(result.Description, Is.EqualTo("Exception of type 'System.Text.Json.JsonException' was thrown. | Notification: System.Object.")); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", TestExceptionMessage) + .Replace("{1}", "System.Object"))); }); } [Test] - public async Task ProcessAsync_TestNotification_ReturnsProcessingResult_Skipped() + public async Task ProcessAsync_Failed_Validation_ReturnsProcessingResult_NotPossible() { + // Arrange + NotificationEvent notification = new() + { + Details = new ErrorDetails(TestExceptionMessage, string.Empty, Array.Empty()) + }; + + this._mockedSerializer + .Setup(mock => mock.Deserialize( + It.IsAny())) + .Returns(notification); + + this._mockedValidator + .Setup(mock => mock.Validate( + ref notification)) + .Returns(HealthCheck.ERROR_Invalid); // NOTE: Overrule Validator mock and make it fail + // Act - ProcessingResult result = await this._processor.ProcessAsync(NotificationEventHandler.GetNotification_Test_Ping()); + ProcessingResult result = await this._processor.ProcessAsync(new object()); // Assert - VerifyMethodsCalls(0); + Assert.Multiple(() => + { + VerifyMethodsCalls(1, 1, 0); + + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.NotPossible)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", TestExceptionMessage) + .Replace("{1}", "System.Object"))); + }); + } + + [Test] + public async Task ProcessAsync_Failed_Ping_ReturnsProcessingResult_Skipped() + { + // Arrange + NotificationEvent notificationJson = NotificationEventHandler.GetNotification_Test_Ping(); + this._mockedSerializer + .Setup(mock => mock.Deserialize( + It.IsAny())) + .Returns(notificationJson); + + // Act + ProcessingResult result = await this._processor.ProcessAsync(notificationJson.Serialized()); + + // Assert Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 0); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); - Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_ERROR_Notification_Test)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", ResourcesText.Processing_ERROR_Notification_Test) + .Replace("{1}", "{\"actie\":\"-\",\"kanaal\":\"-\",\"resource\":\"-\",\"kenmerken\":{\"zaaktype\":null," + + "\"bronorganisatie\":null,\"vertrouwelijkheidaanduiding\":null,\"objectType\":null,\"besluittype\":null," + + "\"verantwoordelijkeOrganisatie\":null},\"hoofdObject\":\"http://some.hoofdobject.nl/\",\"resourceUrl\":" + + "\"http://some.hoofdobject.nl/\",\"aanmaakdatum\":\"0001-01-01T00:00:00\"}"))); }); } [Test] - public async Task ProcessAsync_ValidNotification_UnknownScenario_ReturnsProcessingResult_Skipped() + public async Task ProcessAsync_Failed_UnknownScenario_ReturnsProcessingResult_Skipped() { // Arrange this._mockedResolver.Setup(mock => mock.DetermineScenarioAsync( @@ -109,39 +165,42 @@ public async Task ProcessAsync_ValidNotification_UnknownScenario_ReturnsProcessi ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert - VerifyMethodsCalls(1); - Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 1); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Skipped)); - Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_ERROR_Scenario_NotImplemented)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotImplemented) + .Replace("{1}", s_validNotification))); }); } [Test] - public async Task ProcessAsync_InternalErrors_WhenResolvingScenario_ReturnsProcessingResult_Failure() + public async Task ProcessAsync_Failed_InternalErrors_WhenResolvingScenario_ReturnsProcessingResult_Failure() { // Arrange this._mockedResolver.Setup(mock => mock.DetermineScenarioAsync( It.IsAny())) - .Throws(); + .Throws(() => new HttpRequestException(TestExceptionMessage)); // Act ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert - VerifyMethodsCalls(1); - Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 1); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); - Assert.That(result.Description, Does.StartWith( - $"{nameof(HttpRequestException)} | Exception of type '{typeof(HttpRequestException).FullName}' was")); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", nameof(HttpRequestException) + $" | {TestExceptionMessage}") + .Replace("{1}", s_validNotification))); }); } [Test] - public async Task ProcessAsync_ValidNotification_ValidScenario_FailedGetDataResponse_ReturnsProcessingResult_Failure() + public async Task ProcessAsync_Failed_TryGetData_ReturnsProcessingResult_Failure() { // Arrange var mockedNotifyScenario = new Mock(MockBehavior.Strict); @@ -159,18 +218,20 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_FailedGetDataResp ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert - VerifyMethodsCalls(1); - Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 1); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); - Assert.That(result.Description, Is.EqualTo( - ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotificationMethod))); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotificationNotSent + .Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotificationMethod)) + .Replace("{1}", s_validNotification))); }); } [Test] - public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataResponse_FailedProcessDataResponse_ReturnsProcessingResult_Failure() + public async Task ProcessAsync_Failed_ProcessData_ReturnsProcessingResult_Failure() { // Arrange var mockedNotifyScenario = new Mock(MockBehavior.Strict); @@ -198,18 +259,20 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataRes ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert - VerifyMethodsCalls(1); - Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 1); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Failure)); - Assert.That(result.Description, Is.EqualTo( - ResourcesText.Processing_ERROR_Scenario_NotificationNotSent.Replace("{0}", processingErrorText))); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", ResourcesText.Processing_ERROR_Scenario_NotificationNotSent + .Replace("{0}", processingErrorText)) + .Replace("{1}", s_validNotification))); }); } [Test] - public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataResponse_SuccessProcessDataResponse_ReturnsProcessingResult_Success() + public async Task ProcessAsync_Success_ProcessData_ReturnsProcessingResult_Success() { // Arrange var mockedNotifyScenario = new Mock(MockBehavior.Strict); @@ -236,19 +299,31 @@ public async Task ProcessAsync_ValidNotification_ValidScenario_SuccessGetDataRes ProcessingResult result = await this._processor.ProcessAsync(s_validNotification); // Assert - VerifyMethodsCalls(1); - Assert.Multiple(() => { + VerifyMethodsCalls(1, 1, 1); + Assert.That(result.Status, Is.EqualTo(ProcessingStatus.Success)); - Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_SUCCESS_Scenario_NotificationSent)); + Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification + .Replace("{0}", ResourcesText.Processing_SUCCESS_Scenario_NotificationSent) + .Replace("{1}", s_validNotification))); }); } #endregion #region Verify - private void VerifyMethodsCalls(int determineScenarioInvokeCount) + private void VerifyMethodsCalls(int deserializeInvokeCount, int validateInvokeCount, int determineScenarioInvokeCount) { + this._mockedSerializer + .Verify(mock => mock.Deserialize( + It.IsAny()), + Times.Exactly(deserializeInvokeCount)); + + this._mockedValidator + .Verify(mock => mock.Validate( + ref It.Ref.IsAny), + Times.Exactly(validateInvokeCount)); + this._mockedResolver .Verify(mock => mock.DetermineScenarioAsync( It.IsAny()), diff --git a/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs b/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs index d803392c..1ae1e03c 100644 --- a/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs +++ b/EventsHandler/Tests/Utilities/EventsHandler.Utilities/_TestHelpers/NotificationEventHandler.cs @@ -40,7 +40,7 @@ internal static class NotificationEventHandler internal static NotificationEvent GetNotification_Test_Ping() { var testUri = new Uri("http://some.hoofdobject.nl/"); - + return new NotificationEvent { Channel = Channels.Unknown, @@ -260,6 +260,7 @@ internal static string GetNotification_Real_MessageReceivedScenario_TheHague() } #endregion + #region Helper methods internal static string GetOrphanSecondValue() { return Orphan_Test_Value_2.ToString().ToLower(); @@ -269,5 +270,11 @@ internal static NotificationEvent Deserialized(this string jsonPayload) { return JsonSerializer.Deserialize(jsonPayload); } + + internal static string Serialized(this NotificationEvent notification) + { + return JsonSerializer.Serialize(notification); + } + #endregion } } \ No newline at end of file From e790fc938bbe960ad6ba13f3fd7c98304f72aaa4 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Fri, 1 Nov 2024 17:09:14 +0100 Subject: [PATCH 04/19] Adjust unit tsts --- .../DataProcessing/NotifyProcessor.cs | 3 +- .../Services/Responding/OmcResponder.cs | 2 +- .../Extensions/ObjectResultExtensions.cs | 16 +++++++- .../DataProcessing/NotifyProcessorTests.cs | 2 +- .../Extensions/ObjectResultExtensionsTests.cs | 40 +++++++++++-------- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs index 74a08e26..aa64d911 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs @@ -57,7 +57,8 @@ async Task IProcessingService.ProcessAsync(object json) if (this._validator.Validate(ref notification) is HealthCheck.ERROR_Invalid) { // STOP: The notification is not complete; any further processing of it would be pointless - return new ProcessingResult(ProcessingStatus.NotPossible, notification.Details.Message, json, details); + return new ProcessingResult(ProcessingStatus.NotPossible, + ResourcesText.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message, json, notification.Details); } // Determine if the received notification is "test" (ping) event => In this case, do nothing diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs index 9c66a972..1fce0312 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs @@ -135,7 +135,7 @@ ProcessingStatus.Skipped or : new ProcessingFailed.Simplified(HttpStatusCode.UnprocessableEntity, result.Description).AsResult_400(), ProcessingStatus.NotPossible - => new DeserializationFailed(result.Details).AsResult_422(), + => new DeserializationFailed(result.Details).AsResult_206(), _ => ObjectResultExtensions.AsResult_501() }; diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs index 00e0069d..353bd429 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs @@ -100,7 +100,7 @@ internal static ObjectResult AsResult_403(string response) /// Creates object result. /// /// The specific custom response to be passed into . - private static ObjectResult AsResult_403(this BaseStandardResponseBody response) + internal static ObjectResult AsResult_403(this BaseStandardResponseBody response) { return new ObjectResult(response) { @@ -152,7 +152,7 @@ internal static ObjectResult AsResult_500(string response) /// Creates object result. /// /// The specific custom response to be passed into . - private static ObjectResult AsResult_500(this BaseStandardResponseBody response) + internal static ObjectResult AsResult_500(this BaseStandardResponseBody response) { return new ObjectResult(response) { @@ -172,6 +172,18 @@ internal static ObjectResult AsResult_501() StatusCode = StatusCodes.Status501NotImplemented }; } + + /// + /// Creates object result. + /// + /// The specific custom response to be passed into . + internal static ObjectResult AsResult_501(this BaseStandardResponseBody response) + { + return new ObjectResult(response) + { + StatusCode = StatusCodes.Status501NotImplemented + }; + } #endregion #region Trim() diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs index 620551cd..4441f552 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/DataProcessing/NotifyProcessorTests.cs @@ -119,7 +119,7 @@ public async Task ProcessAsync_Failed_Validation_ReturnsProcessingResult_NotPoss Assert.That(result.Status, Is.EqualTo(ProcessingStatus.NotPossible)); Assert.That(result.Description, Is.EqualTo(ResourcesText.Processing_STATUS_Notification - .Replace("{0}", TestExceptionMessage) + .Replace("{0}", ResourcesText.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message) .Replace("{1}", "System.Object"))); }); } diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs index b53260f5..6bad93c8 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs @@ -1,9 +1,11 @@ // © 2023, Worth Systems. +using System.Net; using EventsHandler.Services.Responding.Messages.Models.Base; using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Services.Responding.Messages.Models.Information; +using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Extensions; using Microsoft.AspNetCore.Mvc; @@ -30,6 +32,7 @@ public void AsResult_ForResponses_Extensions_ReturnsExpectedObjectResult((Func Response, int StatusCode, string Id)> GetTestCases() { // Arrange - var testSimpleResponse = new ProcessingSkipped(TestStatusDescription); - var testDetails = new InfoDetails(TestMessage, TestCases, new[] { TestReason }); - var testDetailedResponse = new DeserializationFailed(testDetails); // Response-based extensions - yield return (testSimpleResponse.AsResult_206, 206, "#1"); - yield return (testSimpleResponse.AsResult_400, 400, "#2"); + yield return (new ProcessingSucceeded(TestStatusDescription).AsResult_202, 202, "#1"); + yield return (new ProcessingSkipped(TestStatusDescription).AsResult_206, 206, "#2"); + yield return (new HttpRequestFailed.Simplified(testDetails).AsResult_400, 400, "#3"); + yield return (new HttpRequestFailed.Detailed(testDetails).AsResult_400, 400, "#4"); + yield return (new DeserializationFailed(testDetails).AsResult_422, 422, "#5"); + yield return (new InternalError(testDetails).AsResult_500, 500, "#6"); + yield return (new NotImplemented().AsResult_501, 501, "#7"); - yield return (testDetailedResponse.AsResult_202, 202, "#3"); - yield return (testDetailedResponse.AsResult_206, 206, "#4"); - yield return (testDetailedResponse.AsResult_400, 400, "#5"); - yield return (testDetailedResponse.AsResult_422, 422, "#6"); + yield return (new StandardResponseBody(HttpStatusCode.Accepted, TestMessage).AsResult_202, 202, "#8"); + yield return (new StandardResponseBody(HttpStatusCode.PartialContent, TestMessage).AsResult_206, 206, "#9"); + yield return (new StandardResponseBody(HttpStatusCode.BadRequest, TestMessage).AsResult_400, 400, "#10"); + yield return (new StandardResponseBody(HttpStatusCode.Forbidden, TestMessage).AsResult_403, 403, "#11"); + yield return (new StandardResponseBody(HttpStatusCode.InternalServerError, TestMessage).AsResult_500, 500, "#12"); // Details-based extensions - yield return (testDetails.AsResult_400, 400, "#7"); - yield return (testDetails.AsResult_422, 422, "#8"); - yield return (testDetails.AsResult_500, 500, "#9"); + yield return (testDetails.AsResult_400, 400, "#13"); + yield return (testDetails.AsResult_422, 422, "#14"); + yield return (testDetails.AsResult_500, 500, "#15"); // Simple static methods - yield return (() => ObjectResultExtensions.AsResult_202(TestStatusDescription), 202, "#10"); - yield return (() => ObjectResultExtensions.AsResult_400(TestStatusDescription), 400, "#11"); - yield return (() => ObjectResultExtensions.AsResult_403(TestStatusDescription), 403, "#12"); - yield return (() => ObjectResultExtensions.AsResult_500(TestStatusDescription), 500, "#13"); - yield return (ObjectResultExtensions.AsResult_501, 501, "#14"); + yield return (() => ObjectResultExtensions.AsResult_202(TestStatusDescription), 202, "#16"); + yield return (() => ObjectResultExtensions.AsResult_400(TestStatusDescription), 400, "#17"); + yield return (() => ObjectResultExtensions.AsResult_403(TestStatusDescription), 403, "#18"); + yield return (() => ObjectResultExtensions.AsResult_500(TestStatusDescription), 500, "#19"); + yield return (ObjectResultExtensions.AsResult_501, 501, "#20"); } } } \ No newline at end of file From 883ed299b839d9524670f8617a0ba5e35d473603 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Fri, 1 Nov 2024 17:09:39 +0100 Subject: [PATCH 05/19] Sort imports --- .../Results/Extensions/ObjectResultExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs index 6bad93c8..23d68a25 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs @@ -1,6 +1,5 @@ // © 2023, Worth Systems. -using System.Net; using EventsHandler.Services.Responding.Messages.Models.Base; using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Responding.Messages.Models.Errors; @@ -8,6 +7,7 @@ using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Extensions; using Microsoft.AspNetCore.Mvc; +using System.Net; namespace EventsHandler.UnitTests.Services.Responding.Results.Extensions { From 26dff4c45465b754e04534e7cb0eacc5039a4484 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Fri, 1 Nov 2024 17:14:51 +0100 Subject: [PATCH 06/19] Rearrange switch cases --- .../Api/EventsHandler/Services/Responding/OmcResponder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs index 1fce0312..35509dee 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs @@ -126,6 +126,9 @@ ProcessingStatus.Skipped or ProcessingStatus.Aborted => new ProcessingSkipped(result.Description).AsResult_206(), + ProcessingStatus.NotPossible + => new DeserializationFailed(result.Details).AsResult_206(), + ProcessingStatus.Failure => result.Details.Message.StartsWith(DefaultValues.Validation.HttpRequest_ErrorMessage) // NOTE: HTTP Request error messages are always simplified ? new HttpRequestFailed.Simplified(result.Details).AsResult_400() @@ -134,9 +137,6 @@ ProcessingStatus.Skipped or ? new ProcessingFailed.Detailed(HttpStatusCode.UnprocessableEntity, result.Description, result.Details).AsResult_400() : new ProcessingFailed.Simplified(HttpStatusCode.UnprocessableEntity, result.Description).AsResult_400(), - ProcessingStatus.NotPossible - => new DeserializationFailed(result.Details).AsResult_206(), - _ => ObjectResultExtensions.AsResult_501() }; } From 9b2bae5124ee4301c0fcfe9157405de4f1ccc2ac Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Tue, 5 Nov 2024 14:31:07 +0100 Subject: [PATCH 07/19] Update OMC version to 1.12.1 --- Documentation/OMC - Documentation.md | 2 +- EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/OMC - Documentation.md b/Documentation/OMC - Documentation.md index 283416b6..e84e4c68 100644 --- a/Documentation/OMC - Documentation.md +++ b/Documentation/OMC - Documentation.md @@ -1,6 +1,6 @@

OMC Documentation

-v.1.12.0 +v.1.12.1 © 2023-2024, Worth Systems. diff --git a/EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs b/EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs index 0db9d614..e130788d 100644 --- a/EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs +++ b/EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs @@ -12,7 +12,7 @@ internal static class ApiController { internal const string Route = "[controller]"; - internal const string Version = "1.120"; + internal const string Version = "1.121"; } #endregion From 6b9ea8703abf2ddc24e27c64633600a55880026c Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Tue, 5 Nov 2024 15:40:56 +0100 Subject: [PATCH 08/19] Merge branch 'main' of https://github.com/Worth-NL/NotifyNL-OMC-Obfuscated into fature/ApiResponses_Serialization_TreatAs200 From 55b9df954aece673cce09aba2fb7e7f546a7a96b Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Tue, 5 Nov 2024 16:01:52 +0100 Subject: [PATCH 09/19] Cleanup launchSettings.json --- .../Properties/launchSettings.json | 138 +----------------- 1 file changed, 4 insertions(+), 134 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Properties/launchSettings.json b/EventsHandler/Api/EventsHandler/Properties/launchSettings.json index 2f93605c..710d1445 100644 --- a/EventsHandler/Api/EventsHandler/Properties/launchSettings.json +++ b/EventsHandler/Api/EventsHandler/Properties/launchSettings.json @@ -5,7 +5,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Production" }, "dotnetRunMessages": true, "applicationUrl": "http://localhost:5270" @@ -15,147 +15,17 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Production" }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7042;http://localhost:5270" }, - "IIS Express (Workflow v1)": { + "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Production", - - "OMC_AUTH_JWT_SECRET": "5lkLJ%5z$%KQ^^94VsLn6cENql7c%V19Yl3MLv7#odu*QoTYxK37ZF$7x4qQgmea*$S^s!kS0o2Ddaqo&7j3o4e2*Kx&2AXBG7Nu", - "OMC_AUTH_JWT_ISSUER": "https://omc.acc.notifynl.nl/", - "OMC_AUTH_JWT_AUDIENCE": "https://omc.acc.notifynl.nl/", - "OMC_AUTH_JWT_EXPIRESINMIN": "60", - "OMC_AUTH_JWT_USERID": "OMC (Production) | Workflow v1", - "OMC_AUTH_JWT_USERNAME": "OMC (Production) | Workflow v1", - - "OMC_FEATURE_WORKFLOW_VERSION": "1", - - // NOTE: This will be still used for OpenZaak and OpenKlant 1.0 or only for OpenZaak 1.0 if OpenKlant 2.0 is used - "ZGW_AUTH_JWT_SECRET": "worthsys123", - "ZGW_AUTH_JWT_ISSUER": "goc", - "ZGW_AUTH_JWT_AUDIENCE": "", - "ZGW_AUTH_JWT_EXPIRESINMIN": "60", - "ZGW_AUTH_JWT_USERID": "fmolenaar@worth.systems", - "ZGW_AUTH_JWT_USERNAME": "Frank Molenaar", - - "ZGW_AUTH_KEY_OPENKLANT": "", // NOTE: Not required if OMC Workflow v1 is used - "ZGW_AUTH_KEY_OBJECTEN": "d459d947dce4a248c4d0314d1bc65367dda7a246", - "ZGW_AUTH_KEY_OBJECTTYPEN": "09cf17741a29574e3afe8fbc462662d2f2e50696", - - "ZGW_ENDPOINT_OPENNOTIFICATIES": "opennotificaties.test.notifynl.nl/api/v1", - "ZGW_ENDPOINT_OPENZAAK": "openzaak.test.notifynl.nl/zaken/api/v1", - "ZGW_ENDPOINT_OPENKLANT": "openklant.test.notifynl.nl/klanten/api/v1", // NOTE: In OMC Workflow v1 this path is for clients - "ZGW_ENDPOINT_BESLUITEN": "openzaak.test.notifynl.nl/besluiten/api/v1", - "ZGW_ENDPOINT_OBJECTEN": "objecten.test.notifynl.nl/api/v2", - "ZGW_ENDPOINT_OBJECTTYPEN": "objecttypen.test.notifynl.nl/api/v1", - "ZGW_ENDPOINT_CONTACTMOMENTEN": "openklant.test.notifynl.nl/contactmomenten/api/v1", // NOTE: In OMC Workflow v1 this path is for register v1 - - "ZGW_WHITELIST_ZAAKCREATE_IDS": "*", - "ZGW_WHITELIST_ZAAKUPDATE_IDS": "*", - "ZGW_WHITELIST_ZAAKCLOSE_IDS": "*", - "ZGW_WHITELIST_TASKASSIGNED_IDS": "*", - "ZGW_WHITELIST_DECISIONMADE_IDS": "*", - "ZGW_WHITELIST_MESSAGE_ALLOWED": "true", - - "ZGW_VARIABLE_OBJECTTYPE_TASKOBJECTTYPE_UUID": "0236e468-2ad8-43d6-a723-219cb22acb37", - "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_UUID": "38327774-7023-4f25-9386-acb0c6f10636", - "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_VERSION": "2", - "ZGW_VARIABLE_OBJECTTYPE_DECISIONINFOOBJECTTYPE_UUIDS": "f482b7a3-22b7-40e2-a187-52f737d4ef44", - - "NOTIFY_API_BASEURL": "https://api.acc.notifynl.nl", - "NOTIFY_API_KEY": "omc_testdev__real_personalizations-f872ff87-26c9-46ad-9b99-b54cc5f85f75-86ff6134-d32a-4c2b-916a-99b9aa09553f", - - "NOTIFY_TEMPLATEID_DECISIONMADE": "3d1b5c2e-f6f0-4de7-ade8-1b21d49a74c1", - - "NOTIFY_TEMPLATEID_EMAIL_ZAAKCREATE": "3b58f871-93cb-49e0-bb98-acc9ab1a4876", - "NOTIFY_TEMPLATEID_EMAIL_ZAAKUPDATE": "c71bb006-f131-465c-bac2-fb44c398c4cd", - "NOTIFY_TEMPLATEID_EMAIL_ZAAKCLOSE": "f1c5af4f-9145-4049-836e-09edff8eb01d", - "NOTIFY_TEMPLATEID_EMAIL_TASKASSIGNED": "50ed4ca5-8638-4ad3-b774-884d0944c382", - "NOTIFY_TEMPLATEID_EMAIL_MESSAGERECEIVED": "0354a750-d1b1-4bff-b359-1b22a698b3f6", - - "NOTIFY_TEMPLATEID_SMS_ZAAKCREATE": "46c8fe56-e1cd-467c-b762-29af892237f6", - "NOTIFY_TEMPLATEID_SMS_ZAAKUPDATE": "5af94a07-2bff-4546-9439-aa2018b7a4d3", - "NOTIFY_TEMPLATEID_SMS_ZAAKCLOSE": "771e7561-a059-4888-acc3-3a3fbcd5c6e3", - "NOTIFY_TEMPLATEID_SMS_TASKASSIGNED": "6354b855-83c7-4cda-aa33-9a6a8f52eebe", - "NOTIFY_TEMPLATEID_SMS_MESSAGERECEIVED": "b5963a58-6f99-43ef-a2cd-148e9c6b9a54", - - "SENTRY_DSN": "https://1db70f552fb2bdcab8571661a3db6d70@o4507152178741248.ingest.de.sentry.io/4507152289431632", - "SENTRY_ENVIRONMENT": "Worth Production | Workflow v1" - } - }, - "IIS Express (Workflow v2)": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - - "OMC_AUTH_JWT_SECRET": "5lkLJ%5z$%KQ^^94VsLn6cENql7c%V19Yl3MLv7#odu*QoTYxK37ZF$7x4qQgmea*$S^s!kS0o2Ddaqo&7j3o4e2*Kx&2AXBG7Nu", - "OMC_AUTH_JWT_ISSUER": "https://omc.acc.notifynl.nl/", - "OMC_AUTH_JWT_AUDIENCE": "https://omc.acc.notifynl.nl/", - "OMC_AUTH_JWT_EXPIRESINMIN": "60", - "OMC_AUTH_JWT_USERID": "OMC (Development) | Workflow v2", - "OMC_AUTH_JWT_USERNAME": "OMC (Development) | Workflow v2", - - "OMC_FEATURE_WORKFLOW_VERSION": "2", - - // NOTE: This will be still used for OpenZaak and OpenKlant 1.0 or only for OpenZaak 1.0 if OpenKlant 2.0 is used - "ZGW_AUTH_JWT_SECRET": "worthsys123", - "ZGW_AUTH_JWT_ISSUER": "goc", - "ZGW_AUTH_JWT_AUDIENCE": "", - "ZGW_AUTH_JWT_EXPIRESINMIN": "60", - "ZGW_AUTH_JWT_USERID": "fmolenaar@worth.systems", - "ZGW_AUTH_JWT_USERNAME": "Frank Molenaar", - - "ZGW_AUTH_KEY_OPENKLANT": "b734934e83076d77b6b9b2e11ad21ae4196e66e1", // NOTE: Not required if OMC Workflow v1 is used - "ZGW_AUTH_KEY_OBJECTEN": "d459d947dce4a248c4d0314d1bc65367dda7a246", - "ZGW_AUTH_KEY_OBJECTTYPEN": "09cf17741a29574e3afe8fbc462662d2f2e50696", - - "ZGW_ENDPOINT_OPENNOTIFICATIES": "opennotificaties.test.notifynl.nl/api/v1", - "ZGW_ENDPOINT_OPENZAAK": "openzaak.test.notifynl.nl/zaken/api/v1", - "ZGW_ENDPOINT_OPENKLANT": "openklantv2.test.notifynl.nl/klantinteracties/api/v1", // NOTE: Both, OpenKlant 2.0 and register v2 are using path "klantinteracties" - "ZGW_ENDPOINT_BESLUITEN": "openzaak.test.notifynl.nl/besluiten/api/v1", - "ZGW_ENDPOINT_OBJECTEN": "objecten.test.notifynl.nl/api/v2", - "ZGW_ENDPOINT_OBJECTTYPEN": "objecttypen.test.notifynl.nl/api/v1", - "ZGW_ENDPOINT_CONTACTMOMENTEN": "openklantv2.test.notifynl.nl/klantinteracties/api/v1", // NOTE: Both, OpenKlant 2.0 and register v2 are using path "klantinteracties" - - "ZGW_WHITELIST_ZAAKCREATE_IDS": "*", - "ZGW_WHITELIST_ZAAKUPDATE_IDS": "*", - "ZGW_WHITELIST_ZAAKCLOSE_IDS": "*", - "ZGW_WHITELIST_TASKASSIGNED_IDS": "*", - "ZGW_WHITELIST_DECISIONMADE_IDS": "*", - "ZGW_WHITELIST_MESSAGE_ALLOWED": "true", - - "ZGW_VARIABLE_OBJECTTYPE_TASKOBJECTTYPE_UUID": "0236e468-2ad8-43d6-a723-219cb22acb37", - "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_UUID": "38327774-7023-4f25-9386-acb0c6f10636", - "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_VERSION": "2", - "ZGW_VARIABLE_OBJECTTYPE_DECISIONINFOOBJECTTYPE_UUIDS": "f482b7a3-22b7-40e2-a187-52f737d4ef44", - - "NOTIFY_API_BASEURL": "https://api.acc.notifynl.nl", - "NOTIFY_API_KEY": "omc_test_v2-f872ff87-26c9-46ad-9b99-b54cc5f85f75-2501a1db-8fa1-4854-85be-d28cd318a374", - - "NOTIFY_TEMPLATEID_DECISIONMADE": "3d1b5c2e-f6f0-4de7-ade8-1b21d49a74c1", - - "NOTIFY_TEMPLATEID_EMAIL_ZAAKCREATE": "3b58f871-93cb-49e0-bb98-acc9ab1a4876", - "NOTIFY_TEMPLATEID_EMAIL_ZAAKUPDATE": "c71bb006-f131-465c-bac2-fb44c398c4cd", - "NOTIFY_TEMPLATEID_EMAIL_ZAAKCLOSE": "f1c5af4f-9145-4049-836e-09edff8eb01d", - "NOTIFY_TEMPLATEID_EMAIL_TASKASSIGNED": "50ed4ca5-8638-4ad3-b774-884d0944c382", - "NOTIFY_TEMPLATEID_EMAIL_MESSAGERECEIVED": "0354a750-d1b1-4bff-b359-1b22a698b3f6", - - "NOTIFY_TEMPLATEID_SMS_ZAAKCREATE": "46c8fe56-e1cd-467c-b762-29af892237f6", - "NOTIFY_TEMPLATEID_SMS_ZAAKUPDATE": "5af94a07-2bff-4546-9439-aa2018b7a4d3", - "NOTIFY_TEMPLATEID_SMS_ZAAKCLOSE": "771e7561-a059-4888-acc3-3a3fbcd5c6e3", - "NOTIFY_TEMPLATEID_SMS_TASKASSIGNED": "6354b855-83c7-4cda-aa33-9a6a8f52eebe", - "NOTIFY_TEMPLATEID_SMS_MESSAGERECEIVED": "b5963a58-6f99-43ef-a2cd-148e9c6b9a54", - - "SENTRY_DSN": "https://1db70f552fb2bdcab8571661a3db6d70@o4507152178741248.ingest.de.sentry.io/4507152289431632", - "SENTRY_ENVIRONMENT": "Worth Development | Workflow v2" + "ASPNETCORE_ENVIRONMENT": "Production" } }, "Docker": { From e5f738270aa242abd61e8a8ff61b18ea7dd99b74 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 13:43:14 +0100 Subject: [PATCH 10/19] Typos --- .../Api/EventsHandler/Properties/Resources.Designer.cs | 6 +++--- EventsHandler/Api/EventsHandler/Properties/Resources.resx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs index 2ed8ea1e..5a1edb18 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs @@ -151,7 +151,7 @@ internal static string Configuration_ERROR_Loader_NotSet { } /// - /// Looks up a localized string similar to The settings does not contain a given value, or the value is empty: {0}.. + /// Looks up a localized string similar to The settings do not contain a given value, or the value is empty: {0}.. /// internal static string Configuration_ERROR_ValueNotFoundOrEmpty { get { @@ -232,7 +232,7 @@ internal static string Deserialization_ERROR_CannotDeserialize_Message { } /// - /// Looks up a localized string similar to The following properties were missing in JSON but they are required.. + /// Looks up a localized string similar to The following properties were missing in JSON, but they are required.. /// internal static string Deserialization_ERROR_CannotDeserialize_RequiredProperties { get { @@ -952,7 +952,7 @@ internal static string Processing_ERROR_Notification_Test { } /// - /// Looks up a localized string similar to There was not possible to determine URIs of any matching InfoObjects. Continuation of processing the selected notification scenario is pointless.. + /// Looks up a localized string similar to There was no possible to determine URIs of any matching InfoObjects. Continuation of processing the selected notification scenario is pointless.. /// internal static string Processing_ERROR_Scenario_MissingInfoObjectsURIs { get { diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.resx b/EventsHandler/Api/EventsHandler/Properties/Resources.resx index de62da8b..91b7ef79 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.resx +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.resx @@ -285,7 +285,7 @@ {0} = The template GUID that triggered the validation error - The settings does not contain a given value, or the value is empty: {0}. + The settings do not contain a given value, or the value is empty: {0}. {0} = Name of the environment variable holding this value @@ -329,7 +329,7 @@ The given JSON cannot be deserialized | Target model: {0} | Failed: {1} | Reason: {2} | All required properties: {3} | Source JSON: {4} - {0} = The model used as target for deserialzation, {1} = The properties that failed during deserialization, {2} = The reason why deserialization failed, {3} = The properties that are required, {4} = The JSON used as data to be deserialized + {0} = The model used as target for deserialization, {1} = The properties that failed during deserialization, {2} = The reason why deserialization failed, {3} = The properties that are required, {4} = The JSON used as data to be deserialized HTTP Request: The case (obtained from OpenZaak Web API service) does not contain any statuses. @@ -454,7 +454,7 @@ The notification data were processed successfully by the selected notification scenario. - There was not possible to determine URIs of any matching InfoObjects. Continuation of processing the selected notification scenario is pointless. + There was no possible to determine URIs of any matching InfoObjects. Continuation of processing the selected notification scenario is pointless. The notification can not be sent because processing messages is forbidden (set to 'false') in the: {0}. @@ -490,7 +490,7 @@ HTTP Status Code = 422. The developer used wrong method or in a wrong way - The following properties were missing in JSON but they are required. + The following properties were missing in JSON, but they are required. {0} | {1} | {2} From fbad53e5626fb1f797c63c82e80d8482f4ebd946 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 14:08:35 +0100 Subject: [PATCH 11/19] Add missing details cases --- .../Messages/Models/Base/BaseEnhancedStandardResponseBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs index 37eeb262..9fab96bf 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs @@ -28,7 +28,7 @@ protected BaseEnhancedStandardResponseBody(HttpStatusCode statusCode, string sta /// public sealed override string ToString() { - return $"{StatusDescription} | {Details.Message}"; + return $"{this.StatusDescription} | {this.Details.Message} | {this.Details.Cases}"; } } } \ No newline at end of file From 93615bcd3a2676a86299baea9d3c24c7cdee4535 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 14:09:43 +0100 Subject: [PATCH 12/19] Remove concrete implementations and their constructors from abstract classes --- .../Base/BaseSimpleStandardResponseBody.cs | 17 +------------- .../Models/Base/BaseStandardResponseBody.cs | 17 +------------- .../Extensions/ObjectResultExtensionsTests.cs | 22 +++++++------------ 3 files changed, 10 insertions(+), 46 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs index f0459c6c..05702001 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs @@ -28,22 +28,7 @@ protected BaseSimpleStandardResponseBody(HttpStatusCode statusCode, string statu /// public sealed override string ToString() { - return $"{StatusDescription} | {Details.Message}"; - } - } - - /// - /// Concrete implementation of allowing to initialize all properties manually. - /// - /// - internal sealed record SimpleStandardResponseBody : BaseSimpleStandardResponseBody - { - /// - /// Initializes a new instance of the class. - /// - internal SimpleStandardResponseBody(HttpStatusCode statusCodes, string statusDescription, BaseSimpleDetails details) - : base(statusCodes, statusDescription, details) - { + return $"{this.StatusDescription} | {this.Details.Message}"; } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs index 0faf6891..a84568e2 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs @@ -30,22 +30,7 @@ protected BaseStandardResponseBody(HttpStatusCode statusCode, string statusDescr /// public override string ToString() { - return StatusDescription; - } - } - - /// - /// Concrete implementation of allowing to initialize all properties manually. - /// - /// - internal sealed record StandardResponseBody : BaseStandardResponseBody - { - /// - /// Initializes a new instance of the class. - /// - internal StandardResponseBody(HttpStatusCode statusCode, string statusDescription) - : base(statusCode, statusDescription) - { + return this.StatusDescription; } } } \ No newline at end of file diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs index 23d68a25..7dc0932b 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs @@ -68,23 +68,17 @@ public void AsResult_ForResponses_Extensions_ReturnsExpectedObjectResult((Func ObjectResultExtensions.AsResult_202(TestStatusDescription), 202, "#16"); - yield return (() => ObjectResultExtensions.AsResult_400(TestStatusDescription), 400, "#17"); - yield return (() => ObjectResultExtensions.AsResult_403(TestStatusDescription), 403, "#18"); - yield return (() => ObjectResultExtensions.AsResult_500(TestStatusDescription), 500, "#19"); - yield return (ObjectResultExtensions.AsResult_501, 501, "#20"); + yield return (() => ObjectResultExtensions.AsResult_202(TestStatusDescription), 202, "#11"); + yield return (() => ObjectResultExtensions.AsResult_400(TestStatusDescription), 400, "#12"); + yield return (() => ObjectResultExtensions.AsResult_403(TestStatusDescription), 403, "#13"); + yield return (() => ObjectResultExtensions.AsResult_500(TestStatusDescription), 500, "#14"); + yield return (ObjectResultExtensions.AsResult_501, 501, "#15"); } } } \ No newline at end of file From 6a4cc2ffb2dda4873887079ea2ce92a934798c60 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 17:00:06 +0100 Subject: [PATCH 13/19] Include Http Status Code in the message --- .../Messages/Models/Base/BaseEnhancedStandardResponseBody.cs | 2 +- .../Messages/Models/Base/BaseSimpleStandardResponseBody.cs | 2 +- .../Responding/Messages/Models/Base/BaseStandardResponseBody.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs index 9fab96bf..f07adc56 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs @@ -28,7 +28,7 @@ protected BaseEnhancedStandardResponseBody(HttpStatusCode statusCode, string sta /// public sealed override string ToString() { - return $"{this.StatusDescription} | {this.Details.Message} | {this.Details.Cases}"; + return $"{base.ToString()} | {this.Details.Message} | {this.Details.Cases}"; } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs index 05702001..6f64df86 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs @@ -28,7 +28,7 @@ protected BaseSimpleStandardResponseBody(HttpStatusCode statusCode, string statu /// public sealed override string ToString() { - return $"{this.StatusDescription} | {this.Details.Message}"; + return $"{base.ToString()} | {this.Details.Message}"; } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs index a84568e2..4171544d 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs @@ -30,7 +30,7 @@ protected BaseStandardResponseBody(HttpStatusCode statusCode, string statusDescr /// public override string ToString() { - return this.StatusDescription; + return $"{(int)this.StatusCode} {this.StatusCode} | {this.StatusDescription}"; } } } \ No newline at end of file From 2136e6e22671fc784f68f7a8b1373592bd321d38 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 17:00:21 +0100 Subject: [PATCH 14/19] Make the method private --- .../Responding/Results/Extensions/ObjectResultExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs index 353bd429..1bb1aef1 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs @@ -100,7 +100,7 @@ internal static ObjectResult AsResult_403(string response) /// Creates object result. /// /// The specific custom response to be passed into . - internal static ObjectResult AsResult_403(this BaseStandardResponseBody response) + private static ObjectResult AsResult_403(this BaseStandardResponseBody response) { return new ObjectResult(response) { From c9ea1fd9ef2b28571b2649088591cec026c40dc3 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Wed, 6 Nov 2024 17:00:39 +0100 Subject: [PATCH 15/19] Format unexpected API response --- .../Api/EventsHandler/Controllers/Base/OmcController.cs | 8 +++++++- .../Api/EventsHandler/Properties/Resources.Designer.cs | 2 +- EventsHandler/Api/EventsHandler/Properties/Resources.resx | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs index 2a490707..62b5d45f 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs @@ -84,6 +84,12 @@ private static void LogException(Exception exception) #region Helper methods /// /// Determines the log message based on the received . + /// + /// The format: + /// + /// HTTP Status Code | Description | Message (optional) | Cases (optional) + /// + /// /// private static string DetermineResultMessage(ObjectResult objectResult) { @@ -97,7 +103,7 @@ private static string DetermineResultMessage(ObjectResult objectResult) BaseStandardResponseBody baseResponse => baseResponse.ToString(), // Unknown object result - _ => string.Format(Resources.API_Response_ERROR_UnspecifiedResponse, objectResult.StatusCode, nameof(objectResult.Value)) + _ => string.Format(Resources.API_Response_ERROR_UnspecifiedResponse, objectResult.StatusCode, $"The response type {nameof(objectResult.Value)}") }; } #endregion diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs index 5a1edb18..c3ffa4b5 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs @@ -61,7 +61,7 @@ internal Resources() { } /// - /// Looks up a localized string similar to Not standardized (unexpected) API response | {0} | {1}. + /// Looks up a localized string similar to {0} | {1} | Not standardized (unexpected) API response. /// internal static string API_Response_ERROR_UnspecifiedResponse { get { diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.resx b/EventsHandler/Api/EventsHandler/Properties/Resources.resx index 91b7ef79..912dda08 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.resx +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.resx @@ -335,7 +335,7 @@ HTTP Request: The case (obtained from OpenZaak Web API service) does not contain any statuses. - Not standardized (unexpected) API response | {0} | {1} + {0} | {1} | Not standardized (unexpected) API response {0} = HTTP Status Code, {1} = Name of ObjectResult type From 447f22853eb6cb26aa40b68fe634cc890597eaea Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Mon, 11 Nov 2024 16:17:59 +0100 Subject: [PATCH 16/19] Richer and standardized API responses - include HttpStatus code, details, and always add the initial notification --- .../StandardizeApiResponsesAttribute.cs | 9 +- .../Controllers/EventsController.cs | 16 +- .../Controllers/NotifyController.cs | 15 +- .../Controllers/TestController.cs | 60 ++++---- .../Extensions/DetailsExtensions.cs | 32 ++++ EventsHandler/Api/EventsHandler/Program.cs | 11 +- .../Properties/Resources.Designer.cs | 17 ++- .../EventsHandler/Properties/Resources.resx | 20 ++- .../Properties/launchSettings.json | 138 +++++++++++++++++- .../DataProcessing/NotifyProcessor.cs | 24 +-- .../Strategy/Responses/ProcessingResult.cs | 65 ++++++++- .../Interfaces/IRespondingService.cs | 25 +--- .../Base/BaseEnhancedStandardResponseBody.cs | 27 +++- .../Base/BaseSimpleStandardResponseBody.cs | 28 +++- .../Models/Base/BaseStandardResponseBody.cs | 32 +++- .../Details/Base/BaseEnhancedDetails.cs | 23 ++- .../Models/Details/Base/BaseSimpleDetails.cs | 5 +- .../Messages/Models/Details/ErrorDetails.cs | 16 ++ .../Messages/Models/Details/InfoDetails.cs | 5 + .../Messages/Models/Details/UnknownDetails.cs | 15 +- .../Models/Errors/DeserializationFailed.cs | 26 ---- .../Messages/Models/Errors/InternalError.cs | 35 ----- .../Models/Errors/ProcessingFailed.cs | 30 ++-- .../BadRequest.cs} | 38 ++--- .../Models/Errors/Specific/Forbidden.cs | 44 ++++++ .../Models/Errors/Specific/InternalError.cs | 44 ++++++ .../Errors/{ => Specific}/NotImplemented.cs | 2 +- .../Errors/Specific/UnprocessableEntity.cs | 45 ++++++ .../Models/Information/ProcessingSkipped.cs | 9 +- .../Models/Successes/ProcessingSucceeded.cs | 9 +- .../Services/Responding/NotifyResponder.cs | 29 ++-- .../Services/Responding/OmcResponder.cs | 47 ++++-- .../Results/Builder/DetailsBuilder.cs | 5 + .../Extensions/ObjectResultExtensions.cs | 104 ++----------- .../Responding/v1/NotifyCallbackResponder.cs | 10 +- .../Responding/v2/NotifyCallbackResponder.cs | 22 +-- .../Services/Responding/OmcResponderTests.cs | 70 +++++++++ .../Extensions/ObjectResultExtensionsTests.cs | 81 ++++++---- 38 files changed, 822 insertions(+), 411 deletions(-) create mode 100644 EventsHandler/Api/EventsHandler/Extensions/DetailsExtensions.cs delete mode 100644 EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/DeserializationFailed.cs delete mode 100644 EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/InternalError.cs rename EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/{HttpRequestFailed.cs => Specific/BadRequest.cs} (62%) create mode 100644 EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/Forbidden.cs create mode 100644 EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/InternalError.cs rename EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/{ => Specific}/NotImplemented.cs (98%) create mode 100644 EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/UnprocessableEntity.cs create mode 100644 EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs diff --git a/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs b/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs index 32bc8291..76617cab 100644 --- a/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs +++ b/EventsHandler/Api/EventsHandler/Attributes/Validation/StandardizeApiResponsesAttribute.cs @@ -2,10 +2,9 @@ using EventsHandler.Constants; using EventsHandler.Controllers; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Properties; -using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding; using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Base; using Microsoft.AspNetCore.Mvc; @@ -32,9 +31,9 @@ internal sealed class StandardizeApiResponsesAttribute : ActionFilterAttribute static StandardizeApiResponsesAttribute() { // NOTE: Concept similar to strategy design pattern => decide how and which API Controllers are responding to the end-user - s_mappedControllersToResponders.TryAdd(typeof(EventsController), typeof(IRespondingService)); - s_mappedControllersToResponders.TryAdd(typeof(NotifyController), typeof(IRespondingService)); - s_mappedControllersToResponders.TryAdd(typeof(TestController), typeof(IRespondingService)); + s_mappedControllersToResponders.TryAdd(typeof(EventsController), typeof(OmcResponder)); + s_mappedControllersToResponders.TryAdd(typeof(NotifyController), typeof(NotifyResponder)); + s_mappedControllersToResponders.TryAdd(typeof(TestController), typeof(NotifyResponder)); } /// diff --git a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs index 231e89a4..cfd12f8c 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs @@ -7,8 +7,8 @@ using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Services.DataProcessing.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding; using EventsHandler.Services.Responding.Interfaces; -using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Services.Versioning.Interfaces; using EventsHandler.Utilities.Swagger.Examples; using Microsoft.AspNetCore.Mvc; @@ -37,7 +37,7 @@ public sealed class EventsController : OmcController /// The register of versioned services. public EventsController( IProcessingService processor, - IRespondingService responder, + OmcResponder responder, IVersionsRegister register) { this._processor = processor; @@ -60,12 +60,12 @@ public EventsController( [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(NotificationEvent), typeof(NotificationEventExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification was valid, and it was successfully sent to "Notify NL" Web API service - [ProducesResponseType(StatusCodes.Status206PartialContent)] // REASON: The notification was not sent (e.g., "test" ping received or scenario is not yet implemented. No need to retry sending it) - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Detailed))] // REASON: The notification was not sent (e.g., it was invalid due to missing data or improper structure. Retry sending is required) - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProcessingFailed.Detailed))] // REASON: Input deserialization error (e.g. model binding of required properties) - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProcessingFailed.Detailed))] // REASON: Internal server error (if-else / try-catch-finally handle) - [ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(string))] // REASON: Operation is not implemented (a new case is not yet supported) + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification was valid, and it was successfully sent to "Notify NL" Web API service + [ProducesResponseType(StatusCodes.Status206PartialContent)] // REASON: The notification was not sent (e.g., "test" ping received or scenario is not yet implemented. No need to retry sending it) + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The notification was not sent (e.g., it was invalid due to missing data or improper structure. Retry sending is required) + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] // REASON: The notification was not sent (e.g., some conditions predeceasing the request were not met. Retry sending is required) + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: Input deserialization error (e.g. model binding of required properties) + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) public async Task ListenAsync([Required, FromBody] object json) { /* The validation of JSON payload structure and model-binding of [Required] properties are diff --git a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs index 808baaa7..cd0f6b64 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs @@ -3,11 +3,8 @@ using EventsHandler.Attributes.Authorization; using EventsHandler.Attributes.Validation; using EventsHandler.Controllers.Base; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotifyNL; using EventsHandler.Services.Responding; -using EventsHandler.Services.Responding.Interfaces; -using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Utilities.Swagger.Examples; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Filters; @@ -27,9 +24,9 @@ public sealed class NotifyController : OmcController /// Initializes a new instance of the class. /// /// The output standardization service (UX/UI). - public NotifyController(IRespondingService responder) + public NotifyController(NotifyResponder responder) { - this._responder = (NotifyResponder)responder; + this._responder = responder; } /// @@ -47,10 +44,10 @@ public NotifyController(IRespondingService responder) [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(DeliveryReceipt), typeof(DeliveryReceiptExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The delivery receipt with successful status - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Simplified))] // REASON: The delivery receipt with failure status - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProcessingFailed.Simplified))] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The delivery receipt with successful status + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The delivery receipt with failure status + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) public async Task ConfirmAsync([Required, FromBody] object json) { return await this._responder.HandleNotifyCallbackAsync(json); diff --git a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs index 9542b1a0..22111fe8 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs @@ -4,14 +4,14 @@ using EventsHandler.Attributes.Validation; using EventsHandler.Controllers.Base; using EventsHandler.Extensions; -using EventsHandler.Mapping.Enums; using EventsHandler.Properties; using EventsHandler.Services.DataProcessing.Enums; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.DataSending.Responses; using EventsHandler.Services.Register.Interfaces; +using EventsHandler.Services.Responding; using EventsHandler.Services.Responding.Interfaces; -using EventsHandler.Services.Responding.Messages.Models.Errors; using EventsHandler.Services.Serialization.Interfaces; using EventsHandler.Services.Settings.Configuration; using EventsHandler.Utilities.Swagger.Examples; @@ -33,7 +33,7 @@ public sealed class TestController : OmcController private readonly WebApiConfiguration _configuration; private readonly ISerializationService _serializer; private readonly ITelemetryService _telemetry; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; /// /// Initializes a new instance of the class. @@ -46,7 +46,7 @@ public TestController( WebApiConfiguration configuration, ISerializationService serializer, ITelemetryService telemetry, - IRespondingService responder) + NotifyResponder responder) { this._configuration = configuration; this._serializer = serializer; @@ -64,9 +64,9 @@ public TestController( // User experience [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The API service is up and running - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The API service is currently down - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProcessingFailed.Simplified))] // REASON: Unexpected internal error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The API service is up and running + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The API service is currently down + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) public async Task HealthCheckAsync() { try @@ -80,9 +80,9 @@ public async Task HealthCheckAsync() // Response return result.IsSuccessStatusCode // HttpStatus Code: 202 Accepted - ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingStatus.Success, result.ToString())) + ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingResult.Success(result.ToString()))) // HttpStatus Code: 400 Bad Request - : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingStatus.Failure, result.ToString())); + : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingResult.Failure(result.ToString()))); } catch (Exception exception) { @@ -123,12 +123,11 @@ public async Task HealthCheckAsync() [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(Dictionary), typeof(PersonalizationExample))] - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Simplified))] // REASON: Issues on the "Notify NL" API service side - [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ProcessingFailed.Simplified))] // REASON: Base URL or API key to "Notify NL" API service were incorrect - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProcessingFailed.Simplified))] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProcessingFailed.Simplified))] // REASON: Unexpected internal error (if-else / try-catch-finally handle) - [ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(string))] // REASON: Operation is not implemented + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: Issues on the "Notify NL" API service side + [ProducesResponseType(StatusCodes.Status403Forbidden)] // REASON: Base URL or API key to "Notify NL" API service were incorrect + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) public async Task SendEmailAsync( [Required, FromQuery] string emailAddress, [Optional, FromQuery] string? emailTemplateId, @@ -168,12 +167,11 @@ public async Task SendEmailAsync( [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(Dictionary), typeof(PersonalizationExample))] - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Simplified))] // REASON: Issues on the "Notify NL" API service side - [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ProcessingFailed.Simplified))] // REASON: Base URL or API key to "Notify NL" API service were incorrect - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProcessingFailed.Simplified))] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProcessingFailed.Simplified))] // REASON: Unexpected internal error (if-else / try-catch-finally handle) - [ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(string))] // REASON: Operation is not implemented + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: Issues on the "Notify NL" API service side + [ProducesResponseType(StatusCodes.Status403Forbidden)] // REASON: Base URL or API key to "Notify NL" API service were incorrect + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) public async Task SendSmsAsync( [Required, FromQuery] string mobileNumber, [Optional, FromQuery] string? smsTemplateId, @@ -221,10 +219,10 @@ public async Task SendSmsAsync( // User experience [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses [SwaggerRequestExample(typeof(NotifyReference), typeof(NotifyReferenceExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The registration was successfully sent to "Contactmomenten" API Web API service - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Simplified))] // REASON: One of the HTTP Request calls wasn't successful - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProcessingFailed.Simplified))] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProcessingFailed.Simplified))] // REASON: The registration wasn't sent / Unexpected internal error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The registration was successfully sent to "Contactmomenten" API Web API service + [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: One of the HTTP Request calls wasn't successful + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid + [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: The registration wasn't sent / Unexpected internal error (if-else / try-catch-finally handle) public async Task ConfirmAsync( [Required, FromBody] object json, [Required, FromQuery] NotifyMethods notifyMethod, @@ -240,9 +238,9 @@ public async Task ConfirmAsync( return response.IsSuccess // HttpStatus Code: 202 Accepted - ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingStatus.Success, response.JsonResponse)) + ? LogApiResponse(LogLevel.Information, this._responder.GetResponse(ProcessingResult.Success(response.JsonResponse))) // HttpStatus Code: 400 Bad Request - : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingStatus.Failure, response.JsonResponse)); + : LogApiResponse(LogLevel.Error, this._responder.GetResponse(ProcessingResult.Failure(response.JsonResponse))); } catch (Exception exception) { @@ -292,7 +290,7 @@ private async Task SendAsync( default: return LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingStatus.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); + this._responder.GetResponse(ProcessingResult.Failure(Resources.Test_NotifyNL_ERROR_NotSupportedMethod))); } } // Case #2: Personalization was provided by the user @@ -318,14 +316,14 @@ private async Task SendAsync( default: return LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingStatus.Failure, Resources.Test_NotifyNL_ERROR_NotSupportedMethod)); + this._responder.GetResponse(ProcessingResult.Failure(Resources.Test_NotifyNL_ERROR_NotSupportedMethod))); } } // HttpStatus Code: 202 Accepted return LogApiResponse(LogLevel.Information, - this._responder.GetResponse(ProcessingStatus.Success, - string.Format(Resources.Test_NotifyNL_SUCCESS_NotificationSent, notifyMethod.GetEnumName()))); + this._responder.GetResponse(ProcessingResult.Success( + string.Format(Resources.Test_NotifyNL_SUCCESS_NotificationSent, notifyMethod.GetEnumName())))); } catch (Exception exception) { diff --git a/EventsHandler/Api/EventsHandler/Extensions/DetailsExtensions.cs b/EventsHandler/Api/EventsHandler/Extensions/DetailsExtensions.cs new file mode 100644 index 00000000..30d17627 --- /dev/null +++ b/EventsHandler/Api/EventsHandler/Extensions/DetailsExtensions.cs @@ -0,0 +1,32 @@ +// © 2024, Worth Systems. + +using EventsHandler.Services.Responding.Messages.Models.Details.Base; + +namespace EventsHandler.Extensions +{ + /// + /// Extension methods for and . + /// + internal static class DetailsExtensions + { + /// + /// Trims the missing details. + /// + /// The enhanced details. + internal static BaseSimpleDetails Trim(this BaseEnhancedDetails details) + { + // Details without Cases and Reasons + return new SimpleDetails(details.Message); + } + + /// + /// Trims the missing details. + /// + /// The enhanced details. + internal static BaseEnhancedDetails Expand(this BaseSimpleDetails details) + { + // Details without Cases and Reasons + return new EnhancedDetails(details); + } + } +} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Program.cs b/EventsHandler/Api/EventsHandler/Program.cs index 6a2acb99..38071841 100644 --- a/EventsHandler/Api/EventsHandler/Program.cs +++ b/EventsHandler/Api/EventsHandler/Program.cs @@ -3,7 +3,6 @@ using EventsHandler.Constants; using EventsHandler.Controllers; using EventsHandler.Extensions; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Properties; using EventsHandler.Services.DataProcessing; @@ -14,7 +13,6 @@ using EventsHandler.Services.DataProcessing.Strategy.Manager; using EventsHandler.Services.DataProcessing.Strategy.Manager.Interfaces; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; -using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.DataQuerying; using EventsHandler.Services.DataQuerying.Adapter; using EventsHandler.Services.DataQuerying.Adapter.Interfaces; @@ -28,7 +26,6 @@ using EventsHandler.Services.DataSending.Interfaces; using EventsHandler.Services.Register.Interfaces; using EventsHandler.Services.Responding; -using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Results.Builder; using EventsHandler.Services.Responding.Results.Builder.Interface; using EventsHandler.Services.Serialization; @@ -405,11 +402,9 @@ private static void RegisterResponders(this WebApplicationBuilder builder) byte omvWorkflowVersion = builder.Services.GetRequiredService() .OMC.Feature.Workflow_Version(); - // Implicit interface (Adapter) used by EventsController => check "IRespondingService" - builder.Services.AddSingleton, OmcResponder>(); - - // Explicit interfaces (generic) used by other controllers => check "IRespondingService" - builder.Services.AddSingleton(typeof(IRespondingService), DetermineResponderVersion(omvWorkflowVersion)); + // TODO: Named interfaces + builder.Services.AddSingleton(); + builder.Services.AddSingleton(typeof(NotifyResponder), DetermineResponderVersion(omvWorkflowVersion)); return; diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs index c3ffa4b5..713b1a99 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs @@ -682,7 +682,16 @@ internal static string HttpRequest_ERROR_Reason2 { } /// - /// Looks up a localized string similar to The notification could not be recognized (deserialized).. + /// Looks up a localized string similar to The access to the external Web API service is forbidden.. + /// + internal static string Operation_ERROR_AccessDenied { + get { + return ResourceManager.GetString("Operation_ERROR_AccessDenied", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The notification could not be recognized/deserialized.. /// internal static string Operation_ERROR_Deserialization_Failure { get { @@ -691,7 +700,7 @@ internal static string Operation_ERROR_Deserialization_Failure { } /// - /// Looks up a localized string similar to Bad request.. + /// Looks up a localized string similar to The HTTP request wasn't completed successfully.. /// internal static string Operation_ERROR_HttpRequest_Failure { get { @@ -790,7 +799,7 @@ internal static string Operation_ERROR_Unknown_ValidationIssue_Message { } /// - /// Looks up a localized string similar to The notification was partially recognized (deserialized).. + /// Looks up a localized string similar to The notification was partially recognized/deserialized.. /// internal static string Operation_SUCCESS_Deserialization_Partial { get { @@ -799,7 +808,7 @@ internal static string Operation_SUCCESS_Deserialization_Partial { } /// - /// Looks up a localized string similar to The notification was successfully recognized (deserialized).. + /// Looks up a localized string similar to The notification was successfully recognized/deserialized.. /// internal static string Operation_SUCCESS_Deserialization_Success { get { diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.resx b/EventsHandler/Api/EventsHandler/Properties/Resources.resx index 912dda08..a49fbf73 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.resx +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.resx @@ -215,19 +215,23 @@ HTTP Status Code = 500 - The notification could not be recognized (deserialized). + The notification could not be recognized/deserialized. HTTP Status Code = 406 - The notification was partially recognized (deserialized). + The notification was partially recognized/deserialized. HTTP Status Code = 206 - The notification was successfully recognized (deserialized). + The notification was successfully recognized/deserialized. HTTP Status Code = 200 + + The access to the external Web API service is forbidden. + HTTP Status Code = 403 + - Bad request. + The HTTP request wasn't completed successfully. HTTP Status Code = 400 @@ -492,10 +496,6 @@ The following properties were missing in JSON, but they are required. - - {0} | {1} | {2} - {0} = Application name, {1} = Log level, {2} = Log message - Notify NL Exception | {0} {0} = Exception message @@ -508,4 +508,8 @@ {0} | Notification: {1}. {0} = Description of the result, {1} = The initial notification JSON + + {0} | {1} | {2} + {0} = Application name, {1} = Log level, {2} = Log message + \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Properties/launchSettings.json b/EventsHandler/Api/EventsHandler/Properties/launchSettings.json index 710d1445..80daa57f 100644 --- a/EventsHandler/Api/EventsHandler/Properties/launchSettings.json +++ b/EventsHandler/Api/EventsHandler/Properties/launchSettings.json @@ -5,7 +5,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Production" + "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, "applicationUrl": "http://localhost:5270" @@ -15,17 +15,147 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Production" + "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7042;http://localhost:5270" }, - "IIS Express": { + "IIS Express (Workflow v1)": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Production" + "ASPNETCORE_ENVIRONMENT": "Production", + + "OMC_AUTH_JWT_SECRET": "5lkLJ%5z$%KQ^^94VsLn6cENql7c%V19Yl3MLv7#odu*QoTYxK37ZF$7x4qQgmea*$S^s!kS0o2Ddaqo&7j3o4e2*Kx&2AXBG7Nu", + "OMC_AUTH_JWT_ISSUER": "https://omc.acc.notifynl.nl/", + "OMC_AUTH_JWT_AUDIENCE": "https://omc.acc.notifynl.nl/", + "OMC_AUTH_JWT_EXPIRESINMIN": "60", + "OMC_AUTH_JWT_USERID": "OMC (Production) | Workflow v1", + "OMC_AUTH_JWT_USERNAME": "OMC (Production) | Workflow v1", + + "OMC_FEATURE_WORKFLOW_VERSION": "1", + + // NOTE: In OMC Workflow v1 this will be used for OpenZaak, Besluiten, ContactMomenten, and OpenKlant 1, + "ZGW_AUTH_JWT_SECRET": "worthsys123", + "ZGW_AUTH_JWT_ISSUER": "goc", + "ZGW_AUTH_JWT_AUDIENCE": "", + "ZGW_AUTH_JWT_EXPIRESINMIN": "60", + "ZGW_AUTH_JWT_USERID": "fmolenaar@worth.systems", + "ZGW_AUTH_JWT_USERNAME": "Frank Molenaar", + + "ZGW_AUTH_KEY_OPENKLANT": "", // NOTE: Not required if OMC Workflow v1 is used + "ZGW_AUTH_KEY_OBJECTEN": "d459d947dce4a248c4d0314d1bc65367dda7a246", + "ZGW_AUTH_KEY_OBJECTTYPEN": "09cf17741a29574e3afe8fbc462662d2f2e50696", + + "ZGW_ENDPOINT_OPENNOTIFICATIES": "opennotificaties.test.notifynl.nl/api/v1", + "ZGW_ENDPOINT_OPENZAAK": "openzaak.test.notifynl.nl/zaken/api/v1", + "ZGW_ENDPOINT_OPENKLANT": "openklant.test.notifynl.nl/klanten/api/v1", // NOTE: In OMC Workflow v1 this path is for clients + "ZGW_ENDPOINT_BESLUITEN": "openzaak.test.notifynl.nl/besluiten/api/v1", + "ZGW_ENDPOINT_OBJECTEN": "objecten.test.notifynl.nl/api/v2", + "ZGW_ENDPOINT_OBJECTTYPEN": "objecttypen.test.notifynl.nl/api/v1", + "ZGW_ENDPOINT_CONTACTMOMENTEN": "openklant.test.notifynl.nl/contactmomenten/api/v1", // NOTE: In OMC Workflow v1 this path is for register v1 + + "ZGW_WHITELIST_ZAAKCREATE_IDS": "*", + "ZGW_WHITELIST_ZAAKUPDATE_IDS": "*", + "ZGW_WHITELIST_ZAAKCLOSE_IDS": "*", + "ZGW_WHITELIST_TASKASSIGNED_IDS": "*", + "ZGW_WHITELIST_DECISIONMADE_IDS": "*", + "ZGW_WHITELIST_MESSAGE_ALLOWED": "true", + + "ZGW_VARIABLE_OBJECTTYPE_TASKOBJECTTYPE_UUID": "0236e468-2ad8-43d6-a723-219cb22acb37", + "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_UUID": "38327774-7023-4f25-9386-acb0c6f10636", + "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_VERSION": "2", + "ZGW_VARIABLE_OBJECTTYPE_DECISIONINFOOBJECTTYPE_UUIDS": "f482b7a3-22b7-40e2-a187-52f737d4ef44", + + "NOTIFY_API_BASEURL": "https://api.acc.notifynl.nl", + "NOTIFY_API_KEY": "omc_testdev__real_personalizations-f872ff87-26c9-46ad-9b99-b54cc5f85f75-86ff6134-d32a-4c2b-916a-99b9aa09553f", + + "NOTIFY_TEMPLATEID_DECISIONMADE": "3d1b5c2e-f6f0-4de7-ade8-1b21d49a74c1", + + "NOTIFY_TEMPLATEID_EMAIL_ZAAKCREATE": "3b58f871-93cb-49e0-bb98-acc9ab1a4876", + "NOTIFY_TEMPLATEID_EMAIL_ZAAKUPDATE": "c71bb006-f131-465c-bac2-fb44c398c4cd", + "NOTIFY_TEMPLATEID_EMAIL_ZAAKCLOSE": "f1c5af4f-9145-4049-836e-09edff8eb01d", + "NOTIFY_TEMPLATEID_EMAIL_TASKASSIGNED": "50ed4ca5-8638-4ad3-b774-884d0944c382", + "NOTIFY_TEMPLATEID_EMAIL_MESSAGERECEIVED": "0354a750-d1b1-4bff-b359-1b22a698b3f6", + + "NOTIFY_TEMPLATEID_SMS_ZAAKCREATE": "46c8fe56-e1cd-467c-b762-29af892237f6", + "NOTIFY_TEMPLATEID_SMS_ZAAKUPDATE": "5af94a07-2bff-4546-9439-aa2018b7a4d3", + "NOTIFY_TEMPLATEID_SMS_ZAAKCLOSE": "771e7561-a059-4888-acc3-3a3fbcd5c6e3", + "NOTIFY_TEMPLATEID_SMS_TASKASSIGNED": "6354b855-83c7-4cda-aa33-9a6a8f52eebe", + "NOTIFY_TEMPLATEID_SMS_MESSAGERECEIVED": "b5963a58-6f99-43ef-a2cd-148e9c6b9a54", + + "SENTRY_DSN": "https://1db70f552fb2bdcab8571661a3db6d70@o4507152178741248.ingest.de.sentry.io/4507152289431632", + "SENTRY_ENVIRONMENT": "Worth Production | Workflow v1" + } + }, + "IIS Express (Workflow v2)": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + + "OMC_AUTH_JWT_SECRET": "5lkLJ%5z$%KQ^^94VsLn6cENql7c%V19Yl3MLv7#odu*QoTYxK37ZF$7x4qQgmea*$S^s!kS0o2Ddaqo&7j3o4e2*Kx&2AXBG7Nu", + "OMC_AUTH_JWT_ISSUER": "https://omc.acc.notifynl.nl/", + "OMC_AUTH_JWT_AUDIENCE": "https://omc.acc.notifynl.nl/", + "OMC_AUTH_JWT_EXPIRESINMIN": "60", + "OMC_AUTH_JWT_USERID": "OMC (Development) | Workflow v2", + "OMC_AUTH_JWT_USERNAME": "OMC (Development) | Workflow v2", + + "OMC_FEATURE_WORKFLOW_VERSION": "2", + + // NOTE: In OMC Workflow v2 this will be used only for OpenZaak and Besluiten + "ZGW_AUTH_JWT_SECRET": "worthsys123", + "ZGW_AUTH_JWT_ISSUER": "goc", + "ZGW_AUTH_JWT_AUDIENCE": "", + "ZGW_AUTH_JWT_EXPIRESINMIN": "60", + "ZGW_AUTH_JWT_USERID": "fmolenaar@worth.systems", + "ZGW_AUTH_JWT_USERNAME": "Frank Molenaar", + + "ZGW_AUTH_KEY_OPENKLANT": "b734934e83076d77b6b9b2e11ad21ae4196e66e1", // NOTE: Required if OMC Workflow v2 is used + "ZGW_AUTH_KEY_OBJECTEN": "d459d947dce4a248c4d0314d1bc65367dda7a246", + "ZGW_AUTH_KEY_OBJECTTYPEN": "09cf17741a29574e3afe8fbc462662d2f2e50696", + + "ZGW_ENDPOINT_OPENNOTIFICATIES": "opennotificaties.test.notifynl.nl/api/v1", + "ZGW_ENDPOINT_OPENZAAK": "openzaak.test.notifynl.nl/zaken/api/v1", + "ZGW_ENDPOINT_OPENKLANT": "openklantv2.test.notifynl.nl/klantinteracties/api/v1", // NOTE: Both, OpenKlant 2.0 and register v2 are using path "klantinteracties" + "ZGW_ENDPOINT_BESLUITEN": "openzaak.test.notifynl.nl/besluiten/api/v1", + "ZGW_ENDPOINT_OBJECTEN": "objecten.test.notifynl.nl/api/v2", + "ZGW_ENDPOINT_OBJECTTYPEN": "objecttypen.test.notifynl.nl/api/v1", + "ZGW_ENDPOINT_CONTACTMOMENTEN": "openklantv2.test.notifynl.nl/klantinteracties/api/v1", // NOTE: Both, OpenKlant 2.0 and register v2 are using path "klantinteracties" + + "ZGW_WHITELIST_ZAAKCREATE_IDS": "*", + "ZGW_WHITELIST_ZAAKUPDATE_IDS": "*", + "ZGW_WHITELIST_ZAAKCLOSE_IDS": "*", + "ZGW_WHITELIST_TASKASSIGNED_IDS": "*", + "ZGW_WHITELIST_DECISIONMADE_IDS": "*", + "ZGW_WHITELIST_MESSAGE_ALLOWED": "true", + + "ZGW_VARIABLE_OBJECTTYPE_TASKOBJECTTYPE_UUID": "0236e468-2ad8-43d6-a723-219cb22acb37", + "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_UUID": "38327774-7023-4f25-9386-acb0c6f10636", + "ZGW_VARIABLE_OBJECTTYPE_MESSAGEOBJECTTYPE_VERSION": "2", + "ZGW_VARIABLE_OBJECTTYPE_DECISIONINFOOBJECTTYPE_UUIDS": "f482b7a3-22b7-40e2-a187-52f737d4ef44", + + "NOTIFY_API_BASEURL": "https://api.acc.notifynl.nl", + "NOTIFY_API_KEY": "omc_test_v2-f872ff87-26c9-46ad-9b99-b54cc5f85f75-2501a1db-8fa1-4854-85be-d28cd318a374", + + "NOTIFY_TEMPLATEID_DECISIONMADE": "3d1b5c2e-f6f0-4de7-ade8-1b21d49a74c1", + + "NOTIFY_TEMPLATEID_EMAIL_ZAAKCREATE": "3b58f871-93cb-49e0-bb98-acc9ab1a4876", + "NOTIFY_TEMPLATEID_EMAIL_ZAAKUPDATE": "c71bb006-f131-465c-bac2-fb44c398c4cd", + "NOTIFY_TEMPLATEID_EMAIL_ZAAKCLOSE": "f1c5af4f-9145-4049-836e-09edff8eb01d", + "NOTIFY_TEMPLATEID_EMAIL_TASKASSIGNED": "50ed4ca5-8638-4ad3-b774-884d0944c382", + "NOTIFY_TEMPLATEID_EMAIL_MESSAGERECEIVED": "0354a750-d1b1-4bff-b359-1b22a698b3f6", + + "NOTIFY_TEMPLATEID_SMS_ZAAKCREATE": "46c8fe56-e1cd-467c-b762-29af892237f6", + "NOTIFY_TEMPLATEID_SMS_ZAAKUPDATE": "5af94a07-2bff-4546-9439-aa2018b7a4d3", + "NOTIFY_TEMPLATEID_SMS_ZAAKCLOSE": "771e7561-a059-4888-acc3-3a3fbcd5c6e3", + "NOTIFY_TEMPLATEID_SMS_TASKASSIGNED": "6354b855-83c7-4cda-aa33-9a6a8f52eebe", + "NOTIFY_TEMPLATEID_SMS_MESSAGERECEIVED": "b5963a58-6f99-43ef-a2cd-148e9c6b9a54", + + "SENTRY_DSN": "https://1db70f552fb2bdcab8571661a3db6d70@o4507152178741248.ingest.de.sentry.io/4507152289431632", + "SENTRY_ENVIRONMENT": "Worth Development | Workflow v2" } }, "Docker": { diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs index aa64d911..b195b66b 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/NotifyProcessor.cs @@ -57,15 +57,14 @@ async Task IProcessingService.ProcessAsync(object json) if (this._validator.Validate(ref notification) is HealthCheck.ERROR_Invalid) { // STOP: The notification is not complete; any further processing of it would be pointless - return new ProcessingResult(ProcessingStatus.NotPossible, - ResourcesText.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message, json, notification.Details); + return ProcessingResult.NotPossible(ResourcesText.Deserialization_ERROR_NotDeserialized_Notification_Properties_Message, json, notification.Details); } // Determine if the received notification is "test" (ping) event => In this case, do nothing if (IsTest(notification)) { // STOP: The notification SHOULD not be sent; it's just a connectivity test not a failure - return new ProcessingResult(ProcessingStatus.Skipped, ResourcesText.Processing_ERROR_Notification_Test, json, details); + return ProcessingResult.Skipped(ResourcesText.Processing_ERROR_Notification_Test, json, details); } // Choose an adequate business-scenario (strategy) to process the notification @@ -75,9 +74,10 @@ async Task IProcessingService.ProcessAsync(object json) GettingDataResponse gettingDataResponse; if ((gettingDataResponse = await scenario.TryGetDataAsync(notification)).IsFailure) { + string message = string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, gettingDataResponse.Message); + // RETRY: The notification COULD not be sent due to missing or inconsistent data - return new ProcessingResult(ProcessingStatus.Failure, - string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, gettingDataResponse.Message), json, details); + return ProcessingResult.Failure(message, json, details); } // Processing the prepared data in a specific way (e.g., sending to "Notify NL") @@ -85,11 +85,11 @@ async Task IProcessingService.ProcessAsync(object json) return processingDataResponse.IsFailure // RETRY: Something bad happened and "Notify NL" did not send the notification as expected - ? new ProcessingResult(ProcessingStatus.Failure, + ? ProcessingResult.Failure( string.Format(ResourcesText.Processing_ERROR_Scenario_NotificationNotSent, processingDataResponse.Message), json, details) // SUCCESS: The notification was sent and the completion status was reported to the telemetry API - : new ProcessingResult(ProcessingStatus.Success, ResourcesText.Processing_SUCCESS_Scenario_NotificationSent, json, details); + : ProcessingResult.Success(ResourcesText.Processing_SUCCESS_Scenario_NotificationSent, json, details); } // Handling errors in a specific way depends on their types or severities catch (Exception exception) @@ -120,20 +120,20 @@ private static ProcessingResult HandleException(Exception exception, object json return exception switch { // STOP: The JSON payload COULD not be deserialized; any further processing of it would be pointless - JsonException => new ProcessingResult(ProcessingStatus.Skipped, exception.Message, json, details), + JsonException => ProcessingResult.Skipped(exception.Message, json, details), // STOP: The notification COULD not be sent, but it's not a failure - NotImplementedException => new ProcessingResult(ProcessingStatus.Skipped, ResourcesText.Processing_ERROR_Scenario_NotImplemented, json, details), + NotImplementedException => ProcessingResult.Skipped(ResourcesText.Processing_ERROR_Scenario_NotImplemented, json, details), // STOP: The notification SHOULD not be sent due to internal condition - AbortedNotifyingException => new ProcessingResult(ProcessingStatus.Aborted, exception.Message, json, details), + AbortedNotifyingException => ProcessingResult.Aborted(exception.Message, json, details), // RETRY: The notification COULD not be sent because of issues with "Notify NL" (e.g., authorization or service being down) - NotifyClientException => new ProcessingResult(ProcessingStatus.Failure, + NotifyClientException => ProcessingResult.Failure( string.Format(ResourcesText.Processing_ERROR_Exception_Notify, exception.Message), json, details), // RETRY: The notification COULD not be sent - _ => new ProcessingResult(ProcessingStatus.Failure, + _ => ProcessingResult.Failure( string.Format(ResourcesText.Processing_ERROR_Exception_Unhandled, exception.GetType().Name, exception.Message), json, details) }; } diff --git a/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs index 134e0888..49ad95f5 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataProcessing/Strategy/Responses/ProcessingResult.cs @@ -1,7 +1,9 @@ // © 2024, Worth Systems. +using EventsHandler.Extensions; using EventsHandler.Mapping.Enums; using EventsHandler.Properties; +using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Responding.Messages.Models.Details.Base; namespace EventsHandler.Services.DataProcessing.Strategy.Responses @@ -29,11 +31,70 @@ namespace EventsHandler.Services.DataProcessing.Strategy.Responses /// /// Initializes a new instance of the struct. /// - public ProcessingResult(ProcessingStatus status, string description, object json, BaseEnhancedDetails details) + private ProcessingResult(ProcessingStatus status, string description, BaseEnhancedDetails details) { this.Status = status; - this.Description = string.Format(Resources.Processing_STATUS_Notification, description, json); + this.Description = description; this.Details = details; } + + #region Responses + /// + /// Success result. + /// + internal static ProcessingResult Success(string description, object? json = null, BaseEnhancedDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.Success, GetDescription(description, json), details ?? InfoDetails.Empty); + } + + /// + /// Skipped result. + /// + internal static ProcessingResult Skipped(string description, object? json = null, BaseEnhancedDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.Skipped, GetDescription(description, json), details ?? InfoDetails.Empty); + } + + /// + /// Aborted result. + /// + internal static ProcessingResult Aborted(string description, object? json = null, BaseEnhancedDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.Aborted, GetDescription(description, json), details ?? ErrorDetails.Empty); + } + + /// + /// NotPossible result. + /// + internal static ProcessingResult NotPossible(string description, object? json = null, BaseEnhancedDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.NotPossible, GetDescription(description, json), details ?? ErrorDetails.Empty); + } + + /// + /// Failure result. + /// + internal static ProcessingResult Failure(string description, object? json = null, BaseEnhancedDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.Failure, GetDescription(description, json), details ?? ErrorDetails.Empty); + } + + /// + /// Failure result. + /// + internal static ProcessingResult Unknown(string description, object? json = null, BaseSimpleDetails? details = null) + { + return new ProcessingResult(ProcessingStatus.Failure, GetDescription(description, json), (details ?? UnknownDetails.Empty).Expand()); + } + #endregion + + #region Helper methods + private static string GetDescription(string description, object? json = null) + { + return json == null + ? description + : string.Format(Resources.Processing_STATUS_Notification, description, json); + } + #endregion } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs index 7c54b87a..feb8af69 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Interfaces/IRespondingService.cs @@ -56,31 +56,8 @@ public interface IRespondingService : IRespondingService /// Gets standardized based on the received generic . /// /// - /// + /// /// internal ObjectResult GetResponse(TResult result); } - - /// - /// - /// - /// Specialized in processing generic and . - /// - /// - /// The generic type of the processing result. - /// The more insightful details about the processing outcome. - /// - public interface IRespondingService : IRespondingService - { - /// - /// Gets standardized based on the received generic and . - /// - /// - /// - /// - /// - /// - /// - internal ObjectResult GetResponse(TResult result, TDetails details); - } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs index f07adc56..b7e2f8d4 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseEnhancedStandardResponseBody.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Details.Base; using System.Net; using System.Text.Json.Serialization; @@ -12,23 +13,39 @@ namespace EventsHandler.Services.Responding.Messages.Models.Base /// internal abstract record BaseEnhancedStandardResponseBody : BaseStandardResponseBody { - [JsonPropertyName("Details")] [JsonPropertyOrder(2)] public BaseEnhancedDetails Details { get; } /// /// Initializes a new instance of the class. /// - protected BaseEnhancedStandardResponseBody(HttpStatusCode statusCode, string statusDescription, BaseEnhancedDetails details) - : base(statusCode, statusDescription) + /// The HTTP Status Code. + /// The processing result. + protected BaseEnhancedStandardResponseBody(HttpStatusCode statusCode, ProcessingResult result) + : base(statusCode, result) { - this.Details = details; + this.Details = result.Details; + } + + /// + /// + /// + /// The HTTP Status Code. + /// The status description. + /// The processing result. + /// + /// NOTE: "description" is used to replace "result.Description". + /// + protected BaseEnhancedStandardResponseBody(HttpStatusCode statusCode, string description, ProcessingResult result) + : base(statusCode, description, result) + { + this.Details = result.Details; } /// public sealed override string ToString() { - return $"{base.ToString()} | {this.Details.Message} | {this.Details.Cases}"; + return $"{base.ToString()} | {this.Details}"; } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs index 6f64df86..015d581a 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseSimpleStandardResponseBody.cs @@ -1,5 +1,7 @@ // © 2023, Worth Systems. +using EventsHandler.Extensions; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Details.Base; using System.Net; using System.Text.Json.Serialization; @@ -12,23 +14,39 @@ namespace EventsHandler.Services.Responding.Messages.Models.Base /// internal abstract record BaseSimpleStandardResponseBody : BaseStandardResponseBody { - [JsonPropertyName("Details")] [JsonPropertyOrder(2)] public BaseSimpleDetails Details { get; } /// /// Initializes a new instance of the class. /// - protected BaseSimpleStandardResponseBody(HttpStatusCode statusCode, string statusDescription, BaseSimpleDetails details) - : base(statusCode, statusDescription) + /// The HTTP Status Code. + /// The processing result. + protected BaseSimpleStandardResponseBody(HttpStatusCode statusCode, ProcessingResult result) + : base(statusCode, result) { - this.Details = details; + this.Details = result.Details.Trim(); + } + + /// + /// + /// + /// The HTTP Status Code. + /// The status description. + /// The processing result. + /// + /// NOTE: "description" is used to replace "result.Description". + /// + protected BaseSimpleStandardResponseBody(HttpStatusCode statusCode, string description, ProcessingResult result) + : base(statusCode, description, result) + { + this.Details = result.Details.Trim(); } /// public sealed override string ToString() { - return $"{base.ToString()} | {this.Details.Message}"; + return $"{base.ToString()} | {this.Details}"; } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs index 4171544d..e3ab3abd 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Base/BaseStandardResponseBody.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using System.Net; using System.Text.Json.Serialization; @@ -10,27 +11,48 @@ namespace EventsHandler.Services.Responding.Messages.Models.Base /// internal abstract record BaseStandardResponseBody { - [JsonPropertyName("Status code")] [JsonPropertyOrder(0)] public HttpStatusCode StatusCode { get; } - [JsonPropertyName("Status description")] [JsonPropertyOrder(1)] public string StatusDescription { get; } /// /// Initializes a new instance of the class. /// - protected BaseStandardResponseBody(HttpStatusCode statusCode, string statusDescription) + /// The HTTP Status Code. + /// The status description. + protected BaseStandardResponseBody(HttpStatusCode statusCode, string description) { this.StatusCode = statusCode; - this.StatusDescription = statusDescription; + this.StatusDescription = description; + } + + /// + /// + /// + /// The HTTP Status Code. + /// The status description. + /// The processing result. + protected BaseStandardResponseBody(HttpStatusCode statusCode, string description, ProcessingResult result) + : this(statusCode, $"{description} | {result.Description}") + { + } + + /// + /// + /// + /// The HTTP Status Code. + /// The processing result. + protected BaseStandardResponseBody(HttpStatusCode statusCode, ProcessingResult result) + : this(statusCode, result.Description) + { } /// public override string ToString() { - return $"{(int)this.StatusCode} {this.StatusCode} | {this.StatusDescription}"; + return $"{(int)this.StatusCode} {this.StatusCode} | {this.StatusDescription}"; // EXAMPLE: "202 Accepted | Operation successful." } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseEnhancedDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseEnhancedDetails.cs index c21b230c..799f78f5 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseEnhancedDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseEnhancedDetails.cs @@ -13,14 +13,12 @@ public abstract record BaseEnhancedDetails : BaseSimpleDetails /// /// The comma-separated case(s) that caused the occurred situation. /// - [JsonPropertyName(nameof(Cases))] [JsonPropertyOrder(1)] public string Cases { get; internal set; } = string.Empty; /// /// The list of reasons that might be responsible for the occurred situation. /// - [JsonPropertyName(nameof(Reasons))] [JsonPropertyOrder(2)] public string[] Reasons { get; internal set; } = Array.Empty(); @@ -29,7 +27,12 @@ public abstract record BaseEnhancedDetails : BaseSimpleDetails /// protected BaseEnhancedDetails() { } + /// /// + /// + /// The details message. + /// The cases included in details. + /// The reasons of occurred cases. protected BaseEnhancedDetails(string message, string cases, string[] reasons) : base(message) { @@ -37,4 +40,20 @@ protected BaseEnhancedDetails(string message, string cases, string[] reasons) this.Reasons = reasons; } } + + /// + /// Concrete implementation of allowing to initialize all properties manually. + /// + /// + internal sealed record EnhancedDetails : BaseEnhancedDetails + { + /// + /// Initializes a new instance of the class. + /// + /// The details. + internal EnhancedDetails(BaseSimpleDetails details) + : base(details.Message, string.Empty, Array.Empty()) + { + } + } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseSimpleDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseSimpleDetails.cs index 26833ee6..2cbdfc52 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseSimpleDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/Base/BaseSimpleDetails.cs @@ -12,7 +12,6 @@ public abstract record BaseSimpleDetails /// /// The message containing a brief summary of the occurred situation. /// - [JsonPropertyName(nameof(Message))] [JsonPropertyOrder(0)] public string Message { get; internal set; } = string.Empty; @@ -21,7 +20,10 @@ public abstract record BaseSimpleDetails /// protected BaseSimpleDetails() { } + /// /// + /// + /// The details message. protected BaseSimpleDetails(string message) { this.Message = message; @@ -37,6 +39,7 @@ internal sealed record SimpleDetails : BaseSimpleDetails /// /// Initializes a new instance of the class. /// + /// The details message. internal SimpleDetails(string message) : base(message) { diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/ErrorDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/ErrorDetails.cs index 17a97580..ebaba473 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/ErrorDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/ErrorDetails.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Constants; using EventsHandler.Services.Responding.Messages.Models.Details.Base; namespace EventsHandler.Services.Responding.Messages.Models.Details @@ -10,12 +11,27 @@ namespace EventsHandler.Services.Responding.Messages.Models.Details /// internal sealed record ErrorDetails : BaseEnhancedDetails { + /// + /// Gets the default . + /// + internal static ErrorDetails Empty { get; } = new + ( + message: DefaultValues.Models.DefaultEnumValueName, + cases: DefaultValues.Models.DefaultEnumValueName, + reasons: Array.Empty() + ); + /// /// Initializes a new instance of the class. /// public ErrorDetails() { } // NOTE: Used in generic constraints and by object initializer syntax + /// /// + /// + /// The details message. + /// The cases included in details. + /// The reasons of occurred cases. internal ErrorDetails(string message, string cases, string[] reasons) : base(message, cases, reasons) { diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs index 235e4898..0cf5e1db 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/InfoDetails.cs @@ -26,7 +26,12 @@ internal sealed record InfoDetails : BaseEnhancedDetails /// public InfoDetails() { } // NOTE: Used in generic constraints and by object initializer syntax + /// /// + /// + /// The details message. + /// The cases included in details. + /// The reasons of occurred cases. internal InfoDetails(string message, string cases, string[] reasons) : base(message, cases, reasons) { diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/UnknownDetails.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/UnknownDetails.cs index 323502a4..e80c4e0e 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/UnknownDetails.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Details/UnknownDetails.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Constants; using EventsHandler.Services.Responding.Messages.Models.Details.Base; namespace EventsHandler.Services.Responding.Messages.Models.Details @@ -10,15 +11,17 @@ namespace EventsHandler.Services.Responding.Messages.Models.Details /// internal sealed record UnknownDetails : BaseSimpleDetails { + /// + /// Gets the default . + /// + internal static UnknownDetails Empty { get; } = new() + { + Message = DefaultValues.Models.DefaultEnumValueName + }; + /// /// Initializes a new instance of the class. /// public UnknownDetails() { } // NOTE: Used in generic constraints and by object initializer syntax - - /// - internal UnknownDetails(string message) - : base(message) - { - } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/DeserializationFailed.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/DeserializationFailed.cs deleted file mode 100644 index 99bb76a8..00000000 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/DeserializationFailed.cs +++ /dev/null @@ -1,26 +0,0 @@ -// © 2023, Worth Systems. - -using EventsHandler.Mapping.Models.POCOs.NotificatieApi; -using EventsHandler.Properties; -using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; -using System.Net; - -namespace EventsHandler.Services.Responding.Messages.Models.Errors -{ - /// - /// Serialization of was unsuccessful. - /// - /// - internal sealed record DeserializationFailed : BaseEnhancedStandardResponseBody - { - /// - /// Initializes a new instance of the class. - /// - /// The details to be included. - internal DeserializationFailed(BaseEnhancedDetails details) - : base(HttpStatusCode.UnprocessableEntity, Resources.Operation_ERROR_Deserialization_Failure, details) - { - } - } -} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/InternalError.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/InternalError.cs deleted file mode 100644 index 432b24fc..00000000 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/InternalError.cs +++ /dev/null @@ -1,35 +0,0 @@ -// © 2023, Worth Systems. - -using EventsHandler.Properties; -using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; -using System.Net; - -namespace EventsHandler.Services.Responding.Messages.Models.Errors -{ - /// - /// An internal server error occurred. - /// - /// - internal sealed record InternalError : BaseSimpleStandardResponseBody - { - /// - /// Initializes a new instance of the class. - /// - /// The details to be included. - internal InternalError(BaseSimpleDetails details) - : base(HttpStatusCode.InternalServerError, Resources.Operation_ERROR_Internal_Unknown, details) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The details to be included. - internal InternalError(string details) - : base(HttpStatusCode.InternalServerError, Resources.Operation_ERROR_Internal_Unknown, new UnknownDetails(details)) - { - } - } -} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/ProcessingFailed.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/ProcessingFailed.cs index f10052fe..110d486f 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/ProcessingFailed.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/ProcessingFailed.cs @@ -1,7 +1,7 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; using System.Net; namespace EventsHandler.Services.Responding.Messages.Models.Errors @@ -12,31 +12,31 @@ namespace EventsHandler.Services.Responding.Messages.Models.Errors internal static class ProcessingFailed { /// - internal sealed record Detailed : BaseEnhancedStandardResponseBody + /// + internal sealed record Simplified : BaseSimpleStandardResponseBody { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The HTTP Status Code to be used. - /// The description of the notification processing result. - /// The details to be included. - internal Detailed(HttpStatusCode httpStatusCode, string processingResult, BaseEnhancedDetails details) - : base(httpStatusCode, processingResult, details) + /// The HTTP Status Code. + /// The processing result. + internal Simplified(HttpStatusCode statusCode, ProcessingResult result) + : base(statusCode, result) { } } /// - /// - internal sealed record Simplified : BaseStandardResponseBody + /// + internal sealed record Detailed : BaseEnhancedStandardResponseBody { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The HTTP Status Code to be used. - /// The description of the notification processing result. - internal Simplified(HttpStatusCode httpStatusCode, string processingResult) - : base(httpStatusCode, processingResult) + /// The HTTP Status Code. + /// The processing result. + internal Detailed(HttpStatusCode statusCode, ProcessingResult result) + : base(statusCode, result) { } } diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/HttpRequestFailed.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/BadRequest.cs similarity index 62% rename from EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/HttpRequestFailed.cs rename to EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/BadRequest.cs index bb2e0e39..d40cb875 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/HttpRequestFailed.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/BadRequest.cs @@ -1,41 +1,41 @@ // © 2023, Worth Systems. using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; using System.Net; -namespace EventsHandler.Services.Responding.Messages.Models.Errors +namespace EventsHandler.Services.Responding.Messages.Models.Errors.Specific { /// - /// A HTTP Request failed. + /// HTTP request failed. /// - internal abstract record HttpRequestFailed + internal abstract record BadRequest { - /// - /// - internal sealed record Detailed : BaseEnhancedStandardResponseBody + /// + /// + internal sealed record Simplified : BaseSimpleStandardResponseBody { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The details to be included. - internal Detailed(BaseEnhancedDetails details) - : base(HttpStatusCode.BadRequest, Resources.Operation_ERROR_HttpRequest_Failure, details) + /// The processing result. + internal Simplified(ProcessingResult result) + : base(HttpStatusCode.BadRequest, Resources.Operation_ERROR_HttpRequest_Failure, result) { } } - - /// - /// - internal sealed record Simplified : BaseSimpleStandardResponseBody + + /// + /// + internal sealed record Detailed : BaseEnhancedStandardResponseBody { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The details to be included. - internal Simplified(BaseSimpleDetails details) - : base(HttpStatusCode.BadRequest, Resources.Operation_ERROR_HttpRequest_Failure, details) + /// The processing result. + internal Detailed(ProcessingResult result) + : base(HttpStatusCode.BadRequest, Resources.Operation_ERROR_HttpRequest_Failure, result) { } } diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/Forbidden.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/Forbidden.cs new file mode 100644 index 00000000..f75b6ff8 --- /dev/null +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/Forbidden.cs @@ -0,0 +1,44 @@ +// © 2023, Worth Systems. + +using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding.Messages.Models.Base; +using System.Net; + +namespace EventsHandler.Services.Responding.Messages.Models.Errors.Specific +{ + /// + /// Internal server error occurred. + /// + /// + internal abstract record Forbidden + { + /// + /// + internal sealed record Simplified : BaseSimpleStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Simplified(ProcessingResult result) + : base(HttpStatusCode.Forbidden, Resources.Operation_ERROR_AccessDenied, result) + { + } + } + + /// + /// + internal sealed record Detailed : BaseEnhancedStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Detailed(ProcessingResult result) + : base(HttpStatusCode.Forbidden, Resources.Operation_ERROR_AccessDenied, result) + { + } + } + } +} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/InternalError.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/InternalError.cs new file mode 100644 index 00000000..f33cb6f4 --- /dev/null +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/InternalError.cs @@ -0,0 +1,44 @@ +// © 2023, Worth Systems. + +using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding.Messages.Models.Base; +using System.Net; + +namespace EventsHandler.Services.Responding.Messages.Models.Errors.Specific +{ + /// + /// Internal server error occurred. + /// + /// + internal abstract record InternalError + { + /// + /// + internal sealed record Simplified : BaseSimpleStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Simplified(ProcessingResult result) + : base(HttpStatusCode.InternalServerError, Resources.Operation_ERROR_Internal_Unknown, result) + { + } + } + + /// + /// + internal sealed record Detailed : BaseEnhancedStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Detailed(ProcessingResult result) + : base(HttpStatusCode.InternalServerError, Resources.Operation_ERROR_Internal_Unknown, result) + { + } + } + } +} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/NotImplemented.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/NotImplemented.cs similarity index 98% rename from EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/NotImplemented.cs rename to EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/NotImplemented.cs index ab79600a..3a1a71e2 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/NotImplemented.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/NotImplemented.cs @@ -4,7 +4,7 @@ using EventsHandler.Services.Responding.Messages.Models.Base; using System.Net; -namespace EventsHandler.Services.Responding.Messages.Models.Errors +namespace EventsHandler.Services.Responding.Messages.Models.Errors.Specific { /// /// The operation is not implemented. diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/UnprocessableEntity.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/UnprocessableEntity.cs new file mode 100644 index 00000000..a020cd23 --- /dev/null +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Errors/Specific/UnprocessableEntity.cs @@ -0,0 +1,45 @@ +// © 2023, Worth Systems. + +using EventsHandler.Mapping.Models.POCOs.NotificatieApi; +using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding.Messages.Models.Base; +using System.Net; + +namespace EventsHandler.Services.Responding.Messages.Models.Errors.Specific +{ + /// + /// Serialization of was unsuccessful. + /// + /// + internal abstract record UnprocessableEntity + { + /// + /// + internal sealed record Simplified : BaseSimpleStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Simplified(ProcessingResult result) + : base(HttpStatusCode.UnprocessableEntity, Resources.Operation_ERROR_Deserialization_Failure, result) + { + } + } + + /// + /// + internal sealed record Detailed : BaseEnhancedStandardResponseBody + { + /// + /// Initializes a new instance of the class. + /// + /// The processing result. + internal Detailed(ProcessingResult result) + : base(HttpStatusCode.UnprocessableEntity, Resources.Operation_ERROR_Deserialization_Failure, result) + { + } + } + } +} \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Information/ProcessingSkipped.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Information/ProcessingSkipped.cs index e767f83e..9cb6a4ff 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Information/ProcessingSkipped.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Information/ProcessingSkipped.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Base; using System.Net; @@ -9,14 +10,14 @@ namespace EventsHandler.Services.Responding.Messages.Models.Information /// Processing of notification was skipped (due to some expected reasons). /// /// - internal sealed record ProcessingSkipped : BaseStandardResponseBody + internal sealed record ProcessingSkipped : BaseSimpleStandardResponseBody { /// /// Initializes a new instance of the class. /// - /// The description of the notification processing result. - internal ProcessingSkipped(string processingResult) - : base(HttpStatusCode.PartialContent, processingResult) + /// The processing result. + internal ProcessingSkipped(ProcessingResult result) + : base(HttpStatusCode.PartialContent, result) { } } diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Successes/ProcessingSucceeded.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Successes/ProcessingSucceeded.cs index 99bd40be..472eee83 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Successes/ProcessingSucceeded.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Messages/Models/Successes/ProcessingSucceeded.cs @@ -1,5 +1,6 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Base; using System.Net; @@ -9,14 +10,14 @@ namespace EventsHandler.Services.Responding.Messages.Models.Successes /// Processing of notification was successful. /// /// - internal sealed record ProcessingSucceeded : BaseStandardResponseBody + internal sealed record ProcessingSucceeded : BaseSimpleStandardResponseBody { /// /// Initializes a new instance of the class. /// - /// The description of the notification processing result. - internal ProcessingSucceeded(string processingResult) - : base(HttpStatusCode.Accepted, processingResult) + /// The processing result. + internal ProcessingSucceeded(ProcessingResult result) + : base(HttpStatusCode.Accepted, result) { } } diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs index 6a0b9d26..c4e633f8 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs @@ -6,18 +6,23 @@ using EventsHandler.Properties; using EventsHandler.Services.DataProcessing.Enums; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Interfaces; +using EventsHandler.Services.Responding.Messages.Models.Errors; +using EventsHandler.Services.Responding.Messages.Models.Errors.Specific; +using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Extensions; using EventsHandler.Services.Serialization.Interfaces; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Notify.Exceptions; +using System.Net; using System.Text.RegularExpressions; namespace EventsHandler.Services.Responding { - /// - public abstract partial class NotifyResponder : IRespondingService // NOTE: "partial" is introduced by the new RegEx generation approach + /// + public abstract partial class NotifyResponder : IRespondingService // NOTE: "partial" is introduced by the new RegEx generation approach { /// protected ISerializationService Serializer { get; } @@ -99,7 +104,7 @@ ObjectResult IRespondingService.GetExceptionResponse(Exception exception) message = match.Value; // NOTE: This specific error message is inconsistently returned from Notify .NET client with 403 Forbidden status code (unlike others - with 400 BadRequest code) - return ObjectResultExtensions.AsResult_403(message); + return new ProcessingFailed.Simplified(HttpStatusCode.Forbidden, ProcessingResult.Failure(message)).AsResult_403(); } // HttpStatus Code: 400 BadRequest @@ -134,11 +139,11 @@ ObjectResult IRespondingService.GetExceptionResponse(Exception exception) // NOTE: Authorization issues wrapped around 403 Forbidden status code case NotifyAuthException: - return ObjectResultExtensions.AsResult_403(exception.Message); + return new Forbidden.Simplified(ProcessingResult.Failure(exception.Message)).AsResult_403(); // NOTE: Unexpected issues wrapped around 500 Internal Server Error status code default: - return ObjectResultExtensions.AsResult_500(exception.Message); + return new InternalError.Simplified(ProcessingResult.Failure(exception.Message)).AsResult_500(); } } @@ -158,7 +163,7 @@ ObjectResult IRespondingService.GetExceptionResponse(string errorMessage) } // HttpStatus Code: 400 BadRequest - return ObjectResultExtensions.AsResult_400(message ?? errorMessage); + return new BadRequest.Simplified(ProcessingResult.Failure(message ?? errorMessage)).AsResult_400(); } /// @@ -198,19 +203,19 @@ bool IRespondingService.ContainsErrorMessage(IDictionary error #endregion #region IRespondingService - /// - ObjectResult IRespondingService.GetResponse(ProcessingStatus status, string details) + /// + ObjectResult IRespondingService.GetResponse(ProcessingResult result) { - return status switch + return result.Status switch { // HttpStatus Code: 202 Accepted - ProcessingStatus.Success => ObjectResultExtensions.AsResult_202(details), + ProcessingStatus.Success => new ProcessingSucceeded(result).AsResult_202(), // HttpStatus Code: 400 BadRequest - ProcessingStatus.Failure => ((IRespondingService)this).GetExceptionResponse(details), + ProcessingStatus.Failure => ((IRespondingService)this).GetExceptionResponse(result.Description), // HttpStatus Code: 501 Not Implemented - _ => ObjectResultExtensions.AsResult_501() + _ => new NotImplemented().AsResult_501() }; } #endregion diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs index 35509dee..e2fa5c2a 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/OmcResponder.cs @@ -8,6 +8,7 @@ using EventsHandler.Services.Responding.Interfaces; using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Responding.Messages.Models.Errors; +using EventsHandler.Services.Responding.Messages.Models.Errors.Specific; using EventsHandler.Services.Responding.Messages.Models.Information; using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Builder.Interface; @@ -20,7 +21,7 @@ namespace EventsHandler.Services.Responding { /// - internal sealed class OmcResponder : IRespondingService + public sealed class OmcResponder : IRespondingService { private readonly IDetailsBuilder _detailsBuilder; @@ -38,7 +39,10 @@ ObjectResult IRespondingService.GetExceptionResponse(Exception exception) { if (exception is HttpRequestException) { - return this._detailsBuilder.Get(Reasons.HttpRequestError, exception.Message).AsResult_400(); + return new BadRequest.Detailed(ProcessingResult.Failure( + description: exception.Message, + details: this._detailsBuilder.Get(Reasons.HttpRequestError, exception.Message))) + .AsResult_400(); } return ((IRespondingService)this).GetExceptionResponse(exception.Message); @@ -52,18 +56,29 @@ ObjectResult IRespondingService.GetExceptionResponse(string errorMessage) // JSON serialization issues if (errorMessage.StartsWith(DefaultValues.Validation.Deserialization_MissingProperty)) { - return this._detailsBuilder.Get(Reasons.MissingProperties_Notification, GetMissingPropertiesNames(errorMessage)).AsResult_422(); + return new UnprocessableEntity.Detailed(ProcessingResult.Failure( + description: errorMessage, + details: this._detailsBuilder.Get(Reasons.MissingProperties_Notification, GetMissingPropertiesNames(errorMessage)))) + .AsResult_422(); } - return errorMessage.StartsWith(DefaultValues.Validation.Deserialization_InvalidValue) - // JSON serialization issues - ? this._detailsBuilder.Get(Reasons.InvalidProperties_Notification, errorMessage).AsResult_422() - // Invalid JSON structure - : this._detailsBuilder.Get(Reasons.InvalidJson, errorMessage).AsResult_422(); + return new UnprocessableEntity.Detailed(ProcessingResult.Failure( + description: errorMessage, + details : this._detailsBuilder.Get( + errorMessage.StartsWith(DefaultValues.Validation.Deserialization_InvalidValue) + // JSON serialization issues + ? Reasons.InvalidProperties_Notification + // Invalid JSON structure + : Reasons.InvalidJson, + errorMessage))) + .AsResult_422(); } catch { - return this._detailsBuilder.Get(Reasons.ValidationIssue).AsResult_500(); + return new InternalError.Simplified(ProcessingResult.Unknown( + description: errorMessage, + details: this._detailsBuilder.Get(Reasons.ValidationIssue))) + .AsResult_500(); } } @@ -120,24 +135,24 @@ ObjectResult IRespondingService.GetResponse(ProcessingResult r return result.Status switch { ProcessingStatus.Success - => new ProcessingSucceeded(result.Description).AsResult_202(), + => new ProcessingSucceeded(result).AsResult_202(), ProcessingStatus.Skipped or ProcessingStatus.Aborted - => new ProcessingSkipped(result.Description).AsResult_206(), + => new ProcessingSkipped(result).AsResult_206(), ProcessingStatus.NotPossible - => new DeserializationFailed(result.Details).AsResult_206(), + => new UnprocessableEntity.Detailed(result).AsResult_206(), ProcessingStatus.Failure => result.Details.Message.StartsWith(DefaultValues.Validation.HttpRequest_ErrorMessage) // NOTE: HTTP Request error messages are always simplified - ? new HttpRequestFailed.Simplified(result.Details).AsResult_400() + ? new BadRequest.Simplified(result).AsResult_400() : result.Details.Cases.IsNotNullOrEmpty() && result.Details.Reasons.HasAny() - ? new ProcessingFailed.Detailed(HttpStatusCode.UnprocessableEntity, result.Description, result.Details).AsResult_400() - : new ProcessingFailed.Simplified(HttpStatusCode.UnprocessableEntity, result.Description).AsResult_400(), + ? new ProcessingFailed.Detailed(HttpStatusCode.PreconditionFailed, result).AsResult_412() + : new ProcessingFailed.Simplified(HttpStatusCode.PreconditionFailed, result).AsResult_412(), - _ => ObjectResultExtensions.AsResult_501() + _ => new NotImplemented().AsResult_501() }; } #endregion diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Builder/DetailsBuilder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Builder/DetailsBuilder.cs index 495828d3..72228449 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Builder/DetailsBuilder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Builder/DetailsBuilder.cs @@ -17,16 +17,20 @@ public sealed class DetailsBuilder : IDetailsBuilder { private static readonly object s_lock = new(); + #region Cached details (objects) private static readonly Dictionary s_cachedEnhancedDetails = new() { { typeof(ErrorDetails), new ErrorDetails() }, { typeof(InfoDetails), new InfoDetails() } }; + private static readonly Dictionary s_cachedSimpleDetails = new() { { typeof(UnknownDetails), new UnknownDetails() } }; + #endregion + #region Cached details (content) private static readonly Dictionary s_cachedDetailsContents = new() { { @@ -98,6 +102,7 @@ public sealed class DetailsBuilder : IDetailsBuilder (Resources.Operation_ERROR_Unknown_ValidationIssue_Message, Array.Empty()) } }; + #endregion /// TDetails IDetailsBuilder.Get(Reasons reason, string cases) diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs index 1bb1aef1..815a21bd 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/Results/Extensions/ObjectResultExtensions.cs @@ -1,9 +1,6 @@ // © 2023, Worth Systems. -using EventsHandler.Extensions; using EventsHandler.Services.Responding.Messages.Models.Base; -using EventsHandler.Services.Responding.Messages.Models.Details.Base; -using EventsHandler.Services.Responding.Messages.Models.Errors; using Microsoft.AspNetCore.Mvc; using System.Net; @@ -26,18 +23,6 @@ internal static ObjectResult AsResult_202(this BaseStandardResponseBody response StatusCode = StatusCodes.Status202Accepted }; } - - /// - /// Creates object result. - /// - /// The specific custom response to be passed into . - internal static ObjectResult AsResult_202(string response) - { - return new ObjectResult(new ProcessingFailed.Simplified(HttpStatusCode.Accepted, response)) - { - StatusCode = StatusCodes.Status202Accepted - }; - } #endregion #region HTTP Status Code 206 @@ -55,27 +40,6 @@ internal static ObjectResult AsResult_206(this BaseStandardResponseBody response #endregion #region HTTP Status Code 400 - /// - /// Creates object result. - /// - /// The error details to be passed into . - internal static ObjectResult AsResult_400(this BaseEnhancedDetails errorDetails) - { - return errorDetails.Cases.IsNotNullOrEmpty() && // NOTE: Not enough details - errorDetails.Reasons.Any() - ? new HttpRequestFailed.Detailed(errorDetails).AsResult_400() - : new HttpRequestFailed.Simplified(errorDetails.Trim()).AsResult_400(); - } - - /// - /// Creates object result. - /// - /// The specific custom response to be passed into . - internal static ObjectResult AsResult_400(string response) - { - return new ProcessingFailed.Simplified(HttpStatusCode.BadRequest, response).AsResult_400(); - } - /// /// Creates object result. /// @@ -91,63 +55,41 @@ internal static ObjectResult AsResult_400(this BaseStandardResponseBody response /// Creates object result. /// /// The specific custom response to be passed into . - internal static ObjectResult AsResult_403(string response) + internal static ObjectResult AsResult_403(this BaseStandardResponseBody response) { - return new ProcessingFailed.Simplified(HttpStatusCode.Forbidden, response).AsResult_403(); + return new ObjectResult(response) + { + StatusCode = StatusCodes.Status403Forbidden + }; } + #endregion + #region HTTP Status Code 412 /// - /// Creates object result. + /// Creates object result. /// /// The specific custom response to be passed into . - private static ObjectResult AsResult_403(this BaseStandardResponseBody response) + internal static ObjectResult AsResult_412(this BaseStandardResponseBody response) { return new ObjectResult(response) { - StatusCode = StatusCodes.Status403Forbidden + StatusCode = StatusCodes.Status412PreconditionFailed }; } #endregion #region HTTP Status Code 422 - /// - /// Creates object result. - /// - /// The error details to be passed into . - internal static ObjectResult AsResult_422(this BaseEnhancedDetails errorDetails) - { - return new DeserializationFailed(errorDetails).AsResult_422(); - } - /// /// Creates object result. /// /// The specific custom response to be passed into . - internal static ObjectResult AsResult_422(this DeserializationFailed response) + internal static ObjectResult AsResult_422(this BaseStandardResponseBody response) { return new UnprocessableEntityObjectResult(response); } #endregion #region HTTP Status Code 500 - /// - /// Creates object result. - /// - /// The specific custom details to be passed into . - internal static ObjectResult AsResult_500(this BaseSimpleDetails details) - { - return new InternalError(details).AsResult_500(); - } - - /// - /// Creates object result. - /// - /// The specific custom response to be passed into . - internal static ObjectResult AsResult_500(string response) - { - return new InternalError(response).AsResult_500(); - } - /// /// Creates object result. /// @@ -162,17 +104,6 @@ internal static ObjectResult AsResult_500(this BaseStandardResponseBody response #endregion #region HTTP Status Code 501 - /// - /// Creates object result. - /// - internal static ObjectResult AsResult_501() - { - return new ObjectResult(new NotImplemented()) - { - StatusCode = StatusCodes.Status501NotImplemented - }; - } - /// /// Creates object result. /// @@ -185,18 +116,5 @@ internal static ObjectResult AsResult_501(this BaseStandardResponseBody response }; } #endregion - - #region Trim() - // ReSharper disable once SuggestBaseTypeForParameter => Do not allow simplifying simple details (redundancy) - /// - /// Trims the missing details. - /// - /// The enhanced details. - private static BaseSimpleDetails Trim(this BaseEnhancedDetails details) - { - // Details without Cases and Reasons - return new SimpleDetails(details.Message); - } - #endregion } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs index 1c3a5039..767da4ac 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/v1/NotifyCallbackResponder.cs @@ -2,11 +2,11 @@ using EventsHandler.Controllers.Base; using EventsHandler.Extensions; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Enums.NotifyNL; using EventsHandler.Mapping.Models.POCOs.NotifyNL; using EventsHandler.Services.DataProcessing.Enums; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.DataSending.Responses; using EventsHandler.Services.Register.Interfaces; using EventsHandler.Services.Responding.Enums.v2; @@ -21,11 +21,11 @@ namespace EventsHandler.Services.Responding.v1 /// /// Version: "OpenKlant" (1.0) Web API service | "OMC workflow" v1. /// - /// + /// internal sealed class NotifyCallbackResponder : NotifyResponder { private readonly WebApiConfiguration _configuration; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; private readonly ITelemetryService _telemetry; /// @@ -60,11 +60,11 @@ DeliveryStatuses.TemporaryFailure or // Positive status was returned by Notify NL ? OmcController.LogApiResponse(LogLevel.Information, - this._responder.GetResponse(ProcessingStatus.Success, GetDeliveryStatusLogMessage(callback))) + this._responder.GetResponse(ProcessingResult.Success(GetDeliveryStatusLogMessage(callback)))) // Failure status was returned by Notify NL : OmcController.LogApiResponse(LogLevel.Error, - this._responder.GetResponse(ProcessingStatus.Failure, GetDeliveryStatusLogMessage(callback))); + this._responder.GetResponse(ProcessingResult.Failure(GetDeliveryStatusLogMessage(callback)))); } catch (Exception exception) { diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs index 75d8a902..3fc7bdee 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/v2/NotifyCallbackResponder.cs @@ -2,10 +2,10 @@ using EventsHandler.Controllers.Base; using EventsHandler.Extensions; -using EventsHandler.Mapping.Enums; using EventsHandler.Mapping.Models.POCOs.NotifyNL; using EventsHandler.Services.DataProcessing.Enums; using EventsHandler.Services.DataProcessing.Strategy.Models.DTOs; +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Register.Interfaces; using EventsHandler.Services.Responding.Enums.v2; using EventsHandler.Services.Responding.Interfaces; @@ -19,11 +19,11 @@ namespace EventsHandler.Services.Responding.v2 /// /// Version: "OpenKlant" (2.0) Web API service | "OMC workflow" v2. /// - /// + /// internal sealed class NotifyCallbackResponder : NotifyResponder { private readonly WebApiConfiguration _configuration; - private readonly IRespondingService _responder; + private readonly IRespondingService _responder; private readonly ITelemetryService _telemetry; /// @@ -36,7 +36,7 @@ public NotifyCallbackResponder(WebApiConfiguration configuration, ISerialization : base(serializer) { this._configuration = configuration; - this._responder = this; // NOTE: Shortcut to use interface methods faster (parent of this class derives from this interface) + this._responder = this; // NOTE: Shortcut to use interface methods faster ("NotifyResponder" parent derives from "IRespondingService" interface) this._telemetry = telemetry; } @@ -97,12 +97,14 @@ private IActionResult LogContactRegistration(DeliveryReceipt callback, FeedbackT // Log level feedbackType == FeedbackTypes.Failure ? LogLevel.Error : LogLevel.Information, // IActionResult - this._responder.GetResponse(feedbackType - is FeedbackTypes.Success - or FeedbackTypes.Info - ? ProcessingStatus.Success // NOTE: Everything good (either final or intermediate state) - : ProcessingStatus.Failure, // NOTE: The notification couldn't be delivered as planned - GetDeliveryStatusLogMessage(callback))); + this._responder.GetResponse( + feedbackType is FeedbackTypes.Success + or FeedbackTypes.Info + // NOTE: Everything is good (either final or intermediate state) + ? ProcessingResult.Success(GetDeliveryStatusLogMessage(callback)) + // NOTE: The notification couldn't be delivered as planned + : ProcessingResult.Failure(GetDeliveryStatusLogMessage(callback)) + )); } catch (Exception exception) { diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs new file mode 100644 index 00000000..55b4ab9f --- /dev/null +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs @@ -0,0 +1,70 @@ +// © 2024, Worth Systems. + +using EventsHandler.Mapping.Enums; +using EventsHandler.Properties; +using EventsHandler.Services.DataProcessing.Strategy.Responses; +using EventsHandler.Services.Responding; +using EventsHandler.Services.Responding.Interfaces; +using EventsHandler.Services.Responding.Messages.Models.Details; +using EventsHandler.Services.Responding.Messages.Models.Details.Base; +using EventsHandler.Services.Responding.Results.Builder.Interface; +using Microsoft.AspNetCore.Mvc; +using Moq; +using System.Net; + +namespace EventsHandler.UnitTests.Services.Responding +{ + [TestFixture] + public sealed class OmcResponderTests + { + #region Test data + private const string TestDescription = "Test description"; + private const string TestJson = "{ }"; + private const string TestMessage = "Test message"; + private const string TestCases = "Case 1, Case 2"; + private static readonly string[] s_testReasons = { "Reason 1, Reason2, Reason 3" }; + + private static readonly InfoDetails s_infoDetails = new(TestMessage, TestCases, s_testReasons); + private static readonly ErrorDetails s_errorDetails = new(TestMessage, TestCases, s_testReasons); + private static readonly ErrorDetails s_httpErrorDetails = new(Resources.HttpRequest_ERROR_NoCase, TestCases, s_testReasons); + private static readonly SimpleDetails s_simpleDetails = new(TestMessage); + #endregion + + private readonly Mock _mockedBuilder = new(MockBehavior.Strict); + + [TestCaseSource(nameof(GetTestResults))] + public void GetResponse_ProcessingResult_Success_ReturnsExpectedObjectResult( + (string Id, ProcessingResult Result, ProcessingStatus Status, HttpStatusCode Code, int ObjResultCode, string ObjResultName, string Description, string Content) test) + { + // Arrange + IRespondingService omcResponder = new OmcResponder(this._mockedBuilder.Object); + + // Act + ObjectResult actualResponse = omcResponder.GetResponse(test.Result); + + // Assert + Assert.Multiple(() => + { + Assert.That(actualResponse.StatusCode, Is.EqualTo(test.ObjResultCode), FailedTestMessage(test.Id)); + Assert.That(actualResponse.Value?.ToString(), + Is.EqualTo($"{(int)test.Code} {test.Code} | {test.Description} | {test.ObjResultName} {test.Content}"), FailedTestMessage(test.Id)); + }); + + return; + + static string FailedTestMessage(string id) => $"Test {id} failed."; + } + + private static IEnumerable< + (string Id, ProcessingResult Result, ProcessingStatus Status, HttpStatusCode Code, int ObjResultCode, string ObjResultName, string Description, string Content)> GetTestResults() + { + yield return ("#1", ProcessingResult.Success(TestDescription, TestJson, s_infoDetails), ProcessingStatus.Success, HttpStatusCode.Accepted, 202, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + yield return ("#2", ProcessingResult.Skipped(TestDescription, TestJson, s_infoDetails), ProcessingStatus.Skipped, HttpStatusCode.PartialContent, 206, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + yield return ("#3", ProcessingResult.Aborted(TestDescription, TestJson, s_errorDetails), ProcessingStatus.Aborted, HttpStatusCode.PartialContent, 206, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + yield return ("#4", ProcessingResult.NotPossible(TestDescription, TestJson, s_errorDetails), ProcessingStatus.NotPossible, HttpStatusCode.UnprocessableEntity, 206, nameof(SimpleDetails), $"{Resources.Operation_ERROR_Deserialization_Failure} | {TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + yield return ("#5", ProcessingResult.Failure(TestDescription, TestJson, s_httpErrorDetails), ProcessingStatus.Failure, HttpStatusCode.BadRequest, 400, nameof(SimpleDetails), $"{Resources.Operation_ERROR_HttpRequest_Failure} | {TestDescription} | Notification: {TestJson}.", $"{{ Message = {Resources.HttpRequest_ERROR_NoCase} }}"); + yield return ("#6", ProcessingResult.Failure(TestDescription, TestJson, s_errorDetails), ProcessingStatus.Failure, HttpStatusCode.PreconditionFailed, 412, nameof(ErrorDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage}, Cases = {TestCases}, Reasons = {s_testReasons} }}"); + yield return ("#7", ProcessingResult.Unknown(TestDescription, TestJson, s_simpleDetails), ProcessingStatus.Failure, HttpStatusCode.PreconditionFailed, 412, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + } + } +} \ No newline at end of file diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs index 7dc0932b..12a76268 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/Results/Extensions/ObjectResultExtensionsTests.cs @@ -1,26 +1,38 @@ // © 2023, Worth Systems. +using EventsHandler.Services.DataProcessing.Strategy.Responses; using EventsHandler.Services.Responding.Messages.Models.Base; using EventsHandler.Services.Responding.Messages.Models.Details; using EventsHandler.Services.Responding.Messages.Models.Errors; +using EventsHandler.Services.Responding.Messages.Models.Errors.Specific; using EventsHandler.Services.Responding.Messages.Models.Information; using EventsHandler.Services.Responding.Messages.Models.Successes; using EventsHandler.Services.Responding.Results.Extensions; using Microsoft.AspNetCore.Mvc; using System.Net; +using EventsHandler.Properties; namespace EventsHandler.UnitTests.Services.Responding.Results.Extensions { [TestFixture] public sealed class ObjectResultExtensionsTests { - private const string TestStatusDescription = "Test description"; + #region Test data + private const string TestDescription = "Test description"; + private const string TestJson = "{ }"; private const string TestMessage = "Test message"; - private const string TestCases = "Test case"; - private const string TestReason = "Test reason"; + private const string TestCases = "Case 1, Case 2"; + private static readonly string[] s_testReasons = { "Reason 1, Reason2, Reason 3" }; + + private static readonly InfoDetails s_infoDetails = new(TestMessage, TestCases, s_testReasons); + private static readonly ErrorDetails s_errorDetails = new(TestMessage, TestCases, s_testReasons); + + private static readonly ProcessingResult s_successResult = ProcessingResult.Success(TestDescription, TestJson, s_infoDetails); + private static readonly ProcessingResult s_failedResult = ProcessingResult.Failure(TestDescription, TestJson, s_errorDetails); + #endregion [TestCaseSource(nameof(GetTestCases))] - public void AsResult_ForResponses_Extensions_ReturnsExpectedObjectResult((Func Response, int StatusCode, string) test) + public void AsResult_ForResponses_Extensions_ReturnsExpectedObjectResult((string Id, Func Response, int StatusCode, string Description) test) { // Act ObjectResult actualResult = test.Response.Invoke(); @@ -28,57 +40,62 @@ public void AsResult_ForResponses_Extensions_ReturnsExpectedObjectResult((Func { - Assert.That(actualResult.StatusCode, Is.EqualTo(test.StatusCode)); + Assert.That(actualResult.StatusCode, Is.EqualTo(test.StatusCode), FailedTestMessage(test.Id)); if (actualResult.Value is BaseStandardResponseBody baseResponse) { - Assert.That(baseResponse.StatusCode, Is.EqualTo((HttpStatusCode)test.StatusCode)); - Assert.That(baseResponse.StatusDescription, Is.Not.Empty); + Assert.That(baseResponse.StatusCode, Is.EqualTo((HttpStatusCode)test.StatusCode), FailedTestMessage(test.Id)); + Assert.That(baseResponse.StatusDescription, Is.EqualTo(test.Description), FailedTestMessage(test.Id)); if (actualResult.Value is BaseSimpleStandardResponseBody simpleResponse) { - Assert.That(simpleResponse.Details.Message, Is.Not.Empty); + Assert.That(simpleResponse.Details.Message, Is.EqualTo(TestMessage), FailedTestMessage(test.Id)); if (actualResult.Value is BaseEnhancedStandardResponseBody enhancedResponse) { - Assert.That(enhancedResponse.Details.Cases, Is.Not.Empty); - Assert.That(enhancedResponse.Details.Reasons[0], Is.Not.Empty); + Assert.That(enhancedResponse.Details.Cases, Is.EqualTo(TestCases), FailedTestMessage(test.Id)); + Assert.That(enhancedResponse.Details.Reasons, Has.Length.EqualTo(s_testReasons.Length), FailedTestMessage(test.Id)); } } } else { - Assert.Fail($"The response message has invalid type: {actualResult.Value!.GetType()}"); + Assert.Fail($"{FailedTestMessage(test.Id)} | The response message has invalid type: {actualResult.Value!.GetType()}"); } }); + + return; + + static string FailedTestMessage(string testId) + { + return $"Test {testId} failed."; + } } private static IEnumerable<( - Func Response, int StatusCode, string Id)> GetTestCases() + string Id, Func Response, int StatusCode, string Description)> GetTestCases() { - // Arrange - var testDetails = new InfoDetails(TestMessage, TestCases, new[] { TestReason }); - // Response-based extensions - yield return (new ProcessingSucceeded(TestStatusDescription).AsResult_202, 202, "#1"); - yield return (new ProcessingSkipped(TestStatusDescription).AsResult_206, 206, "#2"); - yield return (new HttpRequestFailed.Simplified(testDetails).AsResult_400, 400, "#3"); - yield return (new HttpRequestFailed.Detailed(testDetails).AsResult_400, 400, "#4"); - yield return (new DeserializationFailed(testDetails).AsResult_422, 422, "#5"); - yield return (new InternalError(testDetails).AsResult_500, 500, "#6"); - yield return (new NotImplemented().AsResult_501, 501, "#7"); + yield return ("#01", new ProcessingSucceeded(s_successResult).AsResult_202, 202, $"{TestDescription} | Notification: {TestJson}."); + + yield return ("#02", new ProcessingSkipped(s_successResult).AsResult_206, 206, $"{TestDescription} | Notification: {TestJson}."); - // Details-based extensions - yield return (testDetails.AsResult_400, 400, "#8"); - yield return (testDetails.AsResult_422, 422, "#9"); - yield return (testDetails.AsResult_500, 500, "#10"); + yield return ("#03", new ProcessingFailed.Simplified(HttpStatusCode.PreconditionFailed, s_failedResult).AsResult_412, 412, $"{TestDescription} | Notification: {TestJson}."); + yield return ("#04", new ProcessingFailed.Detailed(HttpStatusCode.PreconditionFailed, s_failedResult).AsResult_412, 412, $"{TestDescription} | Notification: {TestJson}."); - // Simple static methods - yield return (() => ObjectResultExtensions.AsResult_202(TestStatusDescription), 202, "#11"); - yield return (() => ObjectResultExtensions.AsResult_400(TestStatusDescription), 400, "#12"); - yield return (() => ObjectResultExtensions.AsResult_403(TestStatusDescription), 403, "#13"); - yield return (() => ObjectResultExtensions.AsResult_500(TestStatusDescription), 500, "#14"); - yield return (ObjectResultExtensions.AsResult_501, 501, "#15"); + yield return ("#05", new BadRequest.Simplified(s_failedResult).AsResult_400, 400, $"{Resources.Operation_ERROR_HttpRequest_Failure} | {TestDescription} | Notification: {TestJson}."); + yield return ("#06", new BadRequest.Detailed(s_failedResult).AsResult_400, 400, $"{Resources.Operation_ERROR_HttpRequest_Failure} | {TestDescription} | Notification: {TestJson}."); + + yield return ("#07", new Forbidden.Simplified(s_failedResult).AsResult_403, 403, $"{Resources.Operation_ERROR_AccessDenied} | {TestDescription} | Notification: {TestJson}."); + yield return ("#08", new Forbidden.Detailed(s_failedResult).AsResult_403, 403, $"{Resources.Operation_ERROR_AccessDenied} | {TestDescription} | Notification: {TestJson}."); + + yield return ("#09", new UnprocessableEntity.Simplified(s_failedResult).AsResult_422, 422, $"{Resources.Operation_ERROR_Deserialization_Failure} | {TestDescription} | Notification: {TestJson}."); + yield return ("#10", new UnprocessableEntity.Detailed(s_failedResult).AsResult_422, 422, $"{Resources.Operation_ERROR_Deserialization_Failure} | {TestDescription} | Notification: {TestJson}."); + + yield return ("#11", new InternalError.Simplified(s_failedResult).AsResult_500, 500, $"{Resources.Operation_ERROR_Internal_Unknown} | {TestDescription} | Notification: {TestJson}."); + yield return ("#12", new InternalError.Detailed(s_failedResult).AsResult_500, 500, $"{Resources.Operation_ERROR_Internal_Unknown} | {TestDescription} | Notification: {TestJson}."); + + yield return ("#13", new NotImplemented().AsResult_501, 501, Resources.Operation_ERROR_NotImplemented); } } } \ No newline at end of file From 049a227bdf8f66e4cd05c239e597df81ec623819 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Mon, 11 Nov 2024 17:09:39 +0100 Subject: [PATCH 17/19] Cleanup Swagger UI responses documentation --- .../Controllers/Base/OmcController.cs | 5 +++- .../Controllers/EventsController.cs | 11 +++----- .../Controllers/NotifyController.cs | 8 +++--- .../Controllers/TestController.cs | 25 ++++++------------- .../Services/Responding/NotifyResponder.cs | 2 +- 5 files changed, 20 insertions(+), 31 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs index 62b5d45f..086d0e50 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/Base/OmcController.cs @@ -18,7 +18,10 @@ namespace EventsHandler.Controllers.Base [Consumes(DefaultValues.Request.ContentType)] [Produces(DefaultValues.Request.ContentType)] // Swagger UI - [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] // REASON: JWT Token is invalid or expired + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: The HTTP Request wasn't successful + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(BaseStandardResponseBody))] // REASON: JWT Token is invalid or expired + [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(BaseStandardResponseBody))] // REASON: Unexpected internal error + [ProducesResponseType(StatusCodes.Status501NotImplemented, Type = typeof(BaseStandardResponseBody))] // REASON: Something is not implemented public abstract class OmcController : Controller { /// diff --git a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs index cfd12f8c..7344f752 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs @@ -60,12 +60,9 @@ public EventsController( [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(NotificationEvent), typeof(NotificationEventExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification was valid, and it was successfully sent to "Notify NL" Web API service - [ProducesResponseType(StatusCodes.Status206PartialContent)] // REASON: The notification was not sent (e.g., "test" ping received or scenario is not yet implemented. No need to retry sending it) - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The notification was not sent (e.g., it was invalid due to missing data or improper structure. Retry sending is required) - [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] // REASON: The notification was not sent (e.g., some conditions predeceasing the request were not met. Retry sending is required) - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: Input deserialization error (e.g. model binding of required properties) - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification was sent to "Notify NL" Web API service + [ProducesResponseType(StatusCodes.Status206PartialContent)] // REASON: Test ping notification was received, serialization failed + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] // REASON: Some conditions predeceasing the request were not met public async Task ListenAsync([Required, FromBody] object json) { /* The validation of JSON payload structure and model-binding of [Required] properties are @@ -98,7 +95,7 @@ public async Task ListenAsync([Required, FromBody] object json) // User experience [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] public IActionResult Version() { LogApiResponse(LogLevel.Trace, Resources.Events_ApiVersionRequested); diff --git a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs index cd0f6b64..c73dad15 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs @@ -5,6 +5,7 @@ using EventsHandler.Controllers.Base; using EventsHandler.Mapping.Models.POCOs.NotifyNL; using EventsHandler.Services.Responding; +using EventsHandler.Services.Responding.Messages.Models.Base; using EventsHandler.Utilities.Swagger.Examples; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Filters; @@ -16,6 +17,8 @@ namespace EventsHandler.Controllers /// Controller used to get feedback from "Notify NL" Web API service. /// /// + // Swagger UI + [ProducesResponseType(StatusCodes.Status202Accepted, Type = typeof(BaseStandardResponseBody))] // REASON: The API service is up and running public sealed class NotifyController : OmcController { private readonly NotifyResponder _responder; @@ -44,10 +47,7 @@ public NotifyController(NotifyResponder responder) [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(DeliveryReceipt), typeof(DeliveryReceiptExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The delivery receipt with successful status - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The delivery receipt with failure status - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: The JSON structure is invalid public async Task ConfirmAsync([Required, FromBody] object json) { return await this._responder.HandleNotifyCallbackAsync(json); diff --git a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs index 22111fe8..2784bd15 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/TestController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/TestController.cs @@ -12,6 +12,7 @@ using EventsHandler.Services.Register.Interfaces; using EventsHandler.Services.Responding; using EventsHandler.Services.Responding.Interfaces; +using EventsHandler.Services.Responding.Messages.Models.Base; using EventsHandler.Services.Serialization.Interfaces; using EventsHandler.Services.Settings.Configuration; using EventsHandler.Utilities.Swagger.Examples; @@ -28,6 +29,9 @@ namespace EventsHandler.Controllers /// Controller used to test other Web API services from which "Notify NL" OMC is dependent. /// /// + // Swagger UI + [ProducesResponseType(StatusCodes.Status202Accepted, Type = typeof(BaseStandardResponseBody))] // REASON: The API service is up and running + [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(BaseStandardResponseBody))] // REASON: Incorrect URL or API key to "Notify NL" API service public sealed class TestController : OmcController { private readonly WebApiConfiguration _configuration; @@ -63,10 +67,6 @@ public TestController( [ApiAuthorization] // User experience [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses - // Swagger UI - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The API service is up and running - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: The API service is currently down - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) public async Task HealthCheckAsync() { try @@ -123,11 +123,7 @@ public async Task HealthCheckAsync() [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(Dictionary), typeof(PersonalizationExample))] - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: Issues on the "Notify NL" API service side - [ProducesResponseType(StatusCodes.Status403Forbidden)] // REASON: Base URL or API key to "Notify NL" API service were incorrect - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: The JSON structure is invalid public async Task SendEmailAsync( [Required, FromQuery] string emailAddress, [Optional, FromQuery] string? emailTemplateId, @@ -167,11 +163,7 @@ public async Task SendEmailAsync( [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(Dictionary), typeof(PersonalizationExample))] - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification successfully sent to "Notify NL" API service - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: Issues on the "Notify NL" API service side - [ProducesResponseType(StatusCodes.Status403Forbidden)] // REASON: Base URL or API key to "Notify NL" API service were incorrect - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Unexpected internal error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: The JSON structure is invalid public async Task SendSmsAsync( [Required, FromQuery] string mobileNumber, [Optional, FromQuery] string? smsTemplateId, @@ -219,10 +211,7 @@ public async Task SendSmsAsync( // User experience [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses [SwaggerRequestExample(typeof(NotifyReference), typeof(NotifyReferenceExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The registration was successfully sent to "Contactmomenten" API Web API service - [ProducesResponseType(StatusCodes.Status400BadRequest)] // REASON: One of the HTTP Request calls wasn't successful - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] // REASON: The JSON structure is invalid - [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: The registration wasn't sent / Unexpected internal error (if-else / try-catch-finally handle) + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: The JSON structure is invalid public async Task ConfirmAsync( [Required, FromBody] object json, [Required, FromQuery] NotifyMethods notifyMethod, diff --git a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs index c4e633f8..28595736 100644 --- a/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs +++ b/EventsHandler/Api/EventsHandler/Services/Responding/NotifyResponder.cs @@ -202,7 +202,7 @@ bool IRespondingService.ContainsErrorMessage(IDictionary error } #endregion - #region IRespondingService + #region IRespondingService /// ObjectResult IRespondingService.GetResponse(ProcessingResult result) { From 70a0c39bf03f5735b655db6c05b3c870fd4cb509 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Mon, 11 Nov 2024 17:15:12 +0100 Subject: [PATCH 18/19] Missing type of responses in EventsController --- .../Api/EventsHandler/Controllers/EventsController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs index 7344f752..695fd1c9 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Filters; using System.ComponentModel.DataAnnotations; +using EventsHandler.Services.Responding.Messages.Models.Base; using Resources = EventsHandler.Properties.Resources; namespace EventsHandler.Controllers @@ -60,9 +61,9 @@ public EventsController( [StandardizeApiResponses] // NOTE: Replace errors raised by ASP.NET Core with standardized API responses // Swagger UI [SwaggerRequestExample(typeof(NotificationEvent), typeof(NotificationEventExample))] // NOTE: Documentation of expected JSON schema with sample and valid payload values - [ProducesResponseType(StatusCodes.Status202Accepted)] // REASON: The notification was sent to "Notify NL" Web API service - [ProducesResponseType(StatusCodes.Status206PartialContent)] // REASON: Test ping notification was received, serialization failed - [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] // REASON: Some conditions predeceasing the request were not met + [ProducesResponseType(StatusCodes.Status202Accepted, Type = typeof(BaseStandardResponseBody))] // REASON: The notification was sent to "Notify NL" Web API service + [ProducesResponseType(StatusCodes.Status206PartialContent, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: Test ping notification was received, serialization failed + [ProducesResponseType(StatusCodes.Status412PreconditionFailed, Type = typeof(BaseEnhancedStandardResponseBody))] // REASON: Some conditions predeceasing the request were not met public async Task ListenAsync([Required, FromBody] object json) { /* The validation of JSON payload structure and model-binding of [Required] properties are From d928d1a48170c8afda6f8e771777928096fa14e2 Mon Sep 17 00:00:00 2001 From: "Thomas M. Krystyan" Date: Tue, 12 Nov 2024 13:09:07 +0100 Subject: [PATCH 19/19] Adjust unit test --- .../Services/Responding/OmcResponderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs index 55b4ab9f..4d51ee66 100644 --- a/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs +++ b/EventsHandler/Tests/UnitTests/EventsHandler.Tests/Services/Responding/OmcResponderTests.cs @@ -61,7 +61,7 @@ private static IEnumerable< yield return ("#1", ProcessingResult.Success(TestDescription, TestJson, s_infoDetails), ProcessingStatus.Success, HttpStatusCode.Accepted, 202, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); yield return ("#2", ProcessingResult.Skipped(TestDescription, TestJson, s_infoDetails), ProcessingStatus.Skipped, HttpStatusCode.PartialContent, 206, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); yield return ("#3", ProcessingResult.Aborted(TestDescription, TestJson, s_errorDetails), ProcessingStatus.Aborted, HttpStatusCode.PartialContent, 206, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); - yield return ("#4", ProcessingResult.NotPossible(TestDescription, TestJson, s_errorDetails), ProcessingStatus.NotPossible, HttpStatusCode.UnprocessableEntity, 206, nameof(SimpleDetails), $"{Resources.Operation_ERROR_Deserialization_Failure} | {TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}"); + yield return ("#4", ProcessingResult.NotPossible(TestDescription, TestJson, s_errorDetails), ProcessingStatus.NotPossible, HttpStatusCode.UnprocessableEntity, 206, nameof(ErrorDetails), $"{Resources.Operation_ERROR_Deserialization_Failure} | {TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage}, Cases = {TestCases}, Reasons = {s_testReasons} }}"); yield return ("#5", ProcessingResult.Failure(TestDescription, TestJson, s_httpErrorDetails), ProcessingStatus.Failure, HttpStatusCode.BadRequest, 400, nameof(SimpleDetails), $"{Resources.Operation_ERROR_HttpRequest_Failure} | {TestDescription} | Notification: {TestJson}.", $"{{ Message = {Resources.HttpRequest_ERROR_NoCase} }}"); yield return ("#6", ProcessingResult.Failure(TestDescription, TestJson, s_errorDetails), ProcessingStatus.Failure, HttpStatusCode.PreconditionFailed, 412, nameof(ErrorDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage}, Cases = {TestCases}, Reasons = {s_testReasons} }}"); yield return ("#7", ProcessingResult.Unknown(TestDescription, TestJson, s_simpleDetails), ProcessingStatus.Failure, HttpStatusCode.PreconditionFailed, 412, nameof(SimpleDetails), $"{TestDescription} | Notification: {TestJson}.", $"{{ Message = {TestMessage} }}");