diff --git a/EventsHandler/Api/EventsHandler/Behaviors/Mapping/Models/POCOs/NotifyNL/DeliveryReceipt.cs b/EventsHandler/Api/EventsHandler/Behaviors/Mapping/Models/POCOs/NotifyNL/DeliveryReceipt.cs index 5da8b228..5ad3ba1c 100644 --- a/EventsHandler/Api/EventsHandler/Behaviors/Mapping/Models/POCOs/NotifyNL/DeliveryReceipt.cs +++ b/EventsHandler/Api/EventsHandler/Behaviors/Mapping/Models/POCOs/NotifyNL/DeliveryReceipt.cs @@ -21,6 +21,8 @@ namespace EventsHandler.Behaviors.Mapping.Models.POCOs.NotifyNL /// internal struct DeliveryReceipt : IJsonSerializable { + internal static DeliveryReceipt Default { get; } = new(); + /// /// Notify’s id for the status receipts. /// diff --git a/EventsHandler/Api/EventsHandler/Configuration/WebApiConfiguration.cs b/EventsHandler/Api/EventsHandler/Configuration/WebApiConfiguration.cs index 35fe6391..d8e961dd 100644 --- a/EventsHandler/Api/EventsHandler/Configuration/WebApiConfiguration.cs +++ b/EventsHandler/Api/EventsHandler/Configuration/WebApiConfiguration.cs @@ -3,7 +3,6 @@ using EventsHandler.Extensions; using EventsHandler.Services.DataLoading.Interfaces; using EventsHandler.Services.DataLoading.Strategy.Interfaces; -using System.Collections.Concurrent; namespace EventsHandler.Configuration { @@ -39,25 +38,6 @@ public WebApiConfiguration(ILoadersContext loaderContext) // NOTE: The only con /// internal abstract record BaseComponent { - /// - /// A thread-safe dictionary, storing cached configuration values. - /// - /// The reasons to use such solution are: - /// - /// - Some values are validated during retrieval time, whether they have correct format or range. - /// After being loaded for the first time and then validated, there is no reason to check them again. - /// - /// - /// - The methods used to map specific configurations (like in fluent builder design pattern) is very handy - /// in terms of OOP approach, but might introduce some minimal overhead. Thanks to caching values by their - /// configuration nodes, both - flexibility and convenience as well as better performance can be achieved. - /// - /// - /// - private protected static readonly ConcurrentDictionary< - string /* Config path */, - string /* Config value */> s_cachedValues = new(); - /// internal AuthorizationComponent Authorization { get; } @@ -106,27 +86,27 @@ internal JwtComponent(ILoadersContext loadersContext, string parentPath) /// internal string Secret() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(Secret)); + => GetValue(this._loadersContext, this._currentPath, nameof(Secret)); /// internal string Issuer() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(Issuer)); + => GetValue(this._loadersContext, this._currentPath, nameof(Issuer)); /// internal string Audience() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(Audience)); + => GetValue(this._loadersContext, this._currentPath, nameof(Audience)); /// internal ushort ExpiresInMin() - => GetCachedValue(this._loadersContext, this._currentPath, nameof(ExpiresInMin)); + => GetValue(this._loadersContext, this._currentPath, nameof(ExpiresInMin)); /// internal string UserId() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(UserId)); + => GetValue(this._loadersContext, this._currentPath, nameof(UserId)); /// internal string UserName() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(UserName)); + => GetValue(this._loadersContext, this._currentPath, nameof(UserName)); } } } @@ -167,7 +147,7 @@ internal ApiComponent(ILoadersContext loadersContext, string parentPath) /// internal string BaseUrl() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(BaseUrl)); + => GetValue(this._loadersContext, this._currentPath, nameof(BaseUrl)); } } @@ -233,11 +213,11 @@ internal KeyComponent(ILoadersContext loadersContext, string parentPath) /// internal string NotifyNL() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(NotifyNL)); + => GetValue(this._loadersContext, this._currentPath, nameof(NotifyNL)); /// internal string Objecten() - => GetCachedValue(this._loadersContext, s_cachedValues, this._currentPath, nameof(Objecten)); + => GetValue(this._loadersContext, this._currentPath, nameof(Objecten)); } } @@ -246,10 +226,6 @@ internal string Objecten() /// internal sealed record DomainComponent { - private static readonly ConcurrentDictionary< - string /* Config path */, - string /* Config value */> s_cachedDomainValues = new(); - private readonly ILoadersContext _loadersContext; private readonly string _currentPath; @@ -264,23 +240,23 @@ internal DomainComponent(ILoadersContext loadersContext, string parentPath) /// internal string OpenNotificaties() - => GetCachedDomainValue(this._loadersContext, s_cachedDomainValues, this._currentPath, nameof(OpenNotificaties)); + => GetDomainValue(this._loadersContext, this._currentPath, nameof(OpenNotificaties)); /// internal string OpenZaak() - => GetCachedDomainValue(this._loadersContext, s_cachedDomainValues, this._currentPath, nameof(OpenZaak)); + => GetDomainValue(this._loadersContext, this._currentPath, nameof(OpenZaak)); /// internal string OpenKlant() - => GetCachedDomainValue(this._loadersContext, s_cachedDomainValues, this._currentPath, nameof(OpenKlant)); + => GetDomainValue(this._loadersContext, this._currentPath, nameof(OpenKlant)); /// internal string Objecten() - => GetCachedDomainValue(this._loadersContext, s_cachedDomainValues, this._currentPath, nameof(Objecten)); + => GetDomainValue(this._loadersContext, this._currentPath, nameof(Objecten)); /// internal string ObjectTypen() - => GetCachedDomainValue(this._loadersContext, s_cachedDomainValues, this._currentPath, nameof(ObjectTypen)); + => GetDomainValue(this._loadersContext, this._currentPath, nameof(ObjectTypen)); } /// @@ -310,10 +286,6 @@ internal TemplateIdsComponent(ILoadersContext loadersContext, string parentPath) /// internal sealed record SmsComponent { - private static readonly ConcurrentDictionary< - string /* Config path */, - string /* Config value */> s_cachedSmsTemplateValues = new(); - private readonly ILoadersContext _loadersContext; private readonly string _currentPath; @@ -328,15 +300,15 @@ internal SmsComponent(ILoadersContext loadersContext, string parentPath) /// internal string ZaakCreate() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedSmsTemplateValues, this._currentPath, nameof(ZaakCreate)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakCreate)); /// internal string ZaakUpdate() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedSmsTemplateValues, this._currentPath, nameof(ZaakUpdate)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakUpdate)); /// internal string ZaakClose() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedSmsTemplateValues, this._currentPath, nameof(ZaakClose)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakClose)); } /// @@ -344,10 +316,6 @@ internal string ZaakClose() /// internal sealed record EmailComponent { - private static readonly ConcurrentDictionary< - string /* Config path */, - string /* Config value */> s_cachedEmailTemplateValues = new(); - private readonly ILoadersContext _loadersContext; private readonly string _currentPath; @@ -362,15 +330,15 @@ internal EmailComponent(ILoadersContext loadersContext, string parentPath) /// internal string ZaakCreate() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedEmailTemplateValues, this._currentPath, nameof(ZaakCreate)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakCreate)); /// internal string ZaakUpdate() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedEmailTemplateValues, this._currentPath, nameof(ZaakUpdate)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakUpdate)); /// internal string ZaakClose() - => GetCachedTemplateIdValue(this._loadersContext, s_cachedEmailTemplateValues, this._currentPath, nameof(ZaakClose)); + => GetTemplateIdValue(this._loadersContext, this._currentPath, nameof(ZaakClose)); } } } @@ -379,26 +347,17 @@ internal string ZaakClose() /// /// Retrieves cached configuration value. /// - private static string GetCachedValue( - ILoadingService loadersContext, - ConcurrentDictionary cachedValues, - string currentPath, - string nodeName) + private static string GetValue(ILoadingService loadersContext, string currentPath, string nodeName) { string finalPath = loadersContext.GetPathWithNode(currentPath, nodeName); - return cachedValues.GetOrAdd( - nodeName, - loadersContext.GetData(finalPath)); + return loadersContext.GetData(finalPath); } /// /// Retrieves cached configuration value. /// - private static TData GetCachedValue( - ILoadingService loadersContext, - string currentPath, - string nodeName) + private static TData GetValue(ILoadingService loadersContext, string currentPath, string nodeName) { string finalPath = loadersContext.GetPathWithNode(currentPath, nodeName); @@ -408,34 +367,22 @@ private static TData GetCachedValue( /// /// Retrieves cached configuration value, ensuring it will be a domain (without http/s and API endpoint). /// - private static string GetCachedDomainValue( - ILoadingService loadersContext, - ConcurrentDictionary cachedValues, - string currentPath, - string nodeName) + private static string GetDomainValue(ILoadingService loadersContext, string currentPath, string nodeName) { - return cachedValues.GetOrAdd( - nodeName, - GetCachedValue(loadersContext, cachedValues, currentPath, nodeName) + return GetValue(loadersContext, currentPath, nodeName) // NOTE: Validate only once when the value is cached .WithoutHttp() - .WithoutEndpoint()); + .WithoutEndpoint(); } /// /// Retrieves cached configuration value, ensuring it will be a valid Template Id. /// - private static string GetCachedTemplateIdValue( - ILoadingService loadersContext, - ConcurrentDictionary cachedValues, - string currentPath, - string nodeName) + private static string GetTemplateIdValue(ILoadingService loadersContext, string currentPath, string nodeName) { - return cachedValues.GetOrAdd( - nodeName, - GetCachedValue(loadersContext, cachedValues, currentPath, nodeName) + return GetValue(loadersContext, currentPath, nodeName) // NOTE: Validate only once when the value is cached - .ValidTemplateId()); + .ValidTemplateId(); } #endregion } diff --git a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs index bd247389..720cf968 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/EventsController.cs @@ -43,7 +43,7 @@ public sealed class EventsController : OmcController /// The input validating service. /// The input processing service (business logic). /// The output standardization service (UX/UI). - /// The logging service. + /// The logging service registering API events. public EventsController( ISerializationService serializer, IValidationService validator, diff --git a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs index b72a7bd7..05a7c740 100644 --- a/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs +++ b/EventsHandler/Api/EventsHandler/Controllers/NotifyController.cs @@ -9,6 +9,7 @@ using EventsHandler.Constants; using EventsHandler.Controllers.Base; using EventsHandler.Services.Serialization.Interfaces; +using EventsHandler.Services.Telemetry.Interfaces; using EventsHandler.Services.UserCommunication.Interfaces; using EventsHandler.Utilities.Swagger.Examples; using Microsoft.AspNetCore.Mvc; @@ -30,21 +31,25 @@ public sealed class NotifyController : OmcController { private readonly ISerializationService _serializer; private readonly IRespondingService _responder; - + private readonly ITelemetryService _telemetry; + /// /// Initializes a new instance of the class. /// /// The input de(serializing) service. /// The output standardization service (UX/UI). - /// The logging service. + /// The telemetry service registering API events. + /// The logging service registering API events. public NotifyController( ISerializationService serializer, IRespondingService responder, + ITelemetryService telemetry, ILogger logger) : base(logger) { this._serializer = serializer; this._responder = responder; + this._telemetry = telemetry; } /// @@ -60,34 +65,49 @@ public NotifyController( [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProcessingFailed.Detailed))] // REASON: The delivery receipt with failure status [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(string))] // REASON: JWT Token is invalid or expired [ProducesResponseType(StatusCodes.Status500InternalServerError)] // REASON: Internal server error (if-else / try-catch-finally handle) - public IActionResult Confirm([Required, FromBody] object json) + public async Task ConfirmAsync([Required, FromBody] object json) { + DeliveryReceipt callback = DeliveryReceipt.Default; + string callbackDetails = string.Empty; + try { // Deserialize received JSON payload - DeliveryReceipt callback = this._serializer.Deserialize(json); + callback = this._serializer.Deserialize(json); if (callback.Status is not (DeliveryStatus.PermanentFailure or DeliveryStatus.TemporaryFailure or DeliveryStatus.TechnicalFailure)) { return LogAndReturnApiResponse(LogLevel.Information, - this._responder.GetStandardized_Processing_ActionResult(ProcessingResult.Success, GetCallbackDetails(callback))); + this._responder.GetStandardized_Processing_ActionResult(ProcessingResult.Success, callbackDetails = GetCallbackDetails(callback))); } return LogAndReturnApiResponse(LogLevel.Error, - this._responder.GetStandardized_Processing_ActionResult(ProcessingResult.Failure, GetCallbackDetails(callback))); + this._responder.GetStandardized_Processing_ActionResult(ProcessingResult.Failure, callbackDetails = GetCallbackDetails(callback))); } catch (Exception exception) { + // NOTE: If callback.Id == Guid.Empty then to be suspected is exception during DeliveryReceipt deserialization + callbackDetails = GetErrorDetails(callback, exception); + return LogAndReturnApiResponse(LogLevel.Critical, this._responder.GetStandardized_Exception_ActionResult(exception)); } + finally + { + _ = await this._telemetry.ReportCompletionAsync(default, default, callbackDetails); + } } private static string GetCallbackDetails(DeliveryReceipt callback) { return $"The status of notification with ID {callback.Id} is: {callback.Status}."; } + + private static string GetErrorDetails(DeliveryReceipt callback, Exception exception) + { + return $"An unexpected error occurred during processing the notification with ID {callback.Id}: {exception.Message}."; + } } } \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Program.cs b/EventsHandler/Api/EventsHandler/Program.cs index e34c6ad7..c17cac2f 100644 --- a/EventsHandler/Api/EventsHandler/Program.cs +++ b/EventsHandler/Api/EventsHandler/Program.cs @@ -202,7 +202,7 @@ private static WebApplicationBuilder AddCustomServices(this WebApplicationBuilde builder.Services.RegisterClientFactories(); builder.Services.AddSingleton, NotifyTemplatesAnalyzer>(); builder.Services.AddSingleton, NotifySender>(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.RegisterResponders(); return builder; diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs index 9e3fb01d..5886b2f8 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.Designer.cs @@ -555,6 +555,15 @@ internal static string Processing_SUCCESS_Scenario_NotificationSent { } } + /// + /// Looks up a localized string similar to The notification was passed to NotifyNL API.. + /// + internal static string Register_NotifyNL_SUCCESS_NotificationSent { + get { + return ResourceManager.GetString("Register_NotifyNL_SUCCESS_NotificationSent", resourceCulture); + } + } + /// /// Looks up a localized string similar to Insert received JWT token here. /// diff --git a/EventsHandler/Api/EventsHandler/Properties/Resources.resx b/EventsHandler/Api/EventsHandler/Properties/Resources.resx index d0f790f0..49d784a3 100644 --- a/EventsHandler/Api/EventsHandler/Properties/Resources.resx +++ b/EventsHandler/Api/EventsHandler/Properties/Resources.resx @@ -314,4 +314,7 @@ was successfully send to NotifyNL. API Endpoint: TestController/Notify/ + + The notification was passed to NotifyNL API. + \ No newline at end of file diff --git a/EventsHandler/Api/EventsHandler/Services/DataSending/NotifySender.cs b/EventsHandler/Api/EventsHandler/Services/DataSending/NotifySender.cs index 951af203..12e0d3f6 100644 --- a/EventsHandler/Api/EventsHandler/Services/DataSending/NotifySender.cs +++ b/EventsHandler/Api/EventsHandler/Services/DataSending/NotifySender.cs @@ -3,6 +3,7 @@ using EventsHandler.Behaviors.Communication.Strategy.Models.DTOs; using EventsHandler.Behaviors.Mapping.Models.POCOs.NotificatieApi; using EventsHandler.Extensions; +using EventsHandler.Properties; using EventsHandler.Services.DataReceiving.Factories.Interfaces; using EventsHandler.Services.DataSending.Clients.Interfaces; using EventsHandler.Services.DataSending.Interfaces; @@ -20,14 +21,14 @@ internal sealed class NotifySender : ISendingService _clientFactory; - private readonly IFeedbackService _telemetry; + private readonly ITelemetryService _telemetry; /// /// Initializes a new instance of the class. /// public NotifySender( IHttpClientFactory clientFactory, - IFeedbackService telemetry) + ITelemetryService telemetry) { this._clientFactory = clientFactory; this._telemetry = telemetry; @@ -39,8 +40,9 @@ async Task ISendingService.SendSmsAsync(Notificat _ = await ResolveNotifyClient(notification).SendSmsAsync(mobileNumber: package.ContactDetails, templateId: package.TemplateId, personalisation: package.Personalization); - // TODO: Extract some data from Notify NL here - _ = await this._telemetry.ReportCompletionAsync(notification, package.NotificationMethod); + + _ = await this._telemetry.ReportCompletionAsync(notification, package.NotificationMethod, + $"SMS: {Resources.Register_NotifyNL_SUCCESS_NotificationSent}"); } /// @@ -49,8 +51,9 @@ async Task ISendingService.SendEmailAsync(Notific _ = await ResolveNotifyClient(notification).SendEmailAsync(emailAddress: package.ContactDetails, templateId: package.TemplateId, personalisation: package.Personalization); - // TODO: Extract some data from Notify NL here - _ = await this._telemetry.ReportCompletionAsync(notification, package.NotificationMethod); + + _ = await this._telemetry.ReportCompletionAsync(notification, package.NotificationMethod, + $"Email: {Resources.Register_NotifyNL_SUCCESS_NotificationSent}"); } #region IDisposable diff --git a/EventsHandler/Api/EventsHandler/Services/Telemetry/ContactRegistration.cs b/EventsHandler/Api/EventsHandler/Services/Telemetry/ContactRegistration.cs index 48b9f842..6642c5aa 100644 --- a/EventsHandler/Api/EventsHandler/Services/Telemetry/ContactRegistration.cs +++ b/EventsHandler/Api/EventsHandler/Services/Telemetry/ContactRegistration.cs @@ -17,13 +17,13 @@ namespace EventsHandler.Services.Telemetry { /// - /// + /// /// /// Informs external "Contactmomenten" API web service about completion of sending notifications to "NotifyNL" API web service. /// /// - /// - internal sealed class ContactRegistration : IFeedbackService + /// + internal sealed class ContactRegistration : ITelemetryService { private readonly IDataQueryService _dataQuery; @@ -35,20 +35,20 @@ public ContactRegistration(IDataQueryService dataQuery) this._dataQuery = dataQuery; } - /// - async Task IFeedbackService.ReportCompletionAsync(NotificationEvent notification, NotifyMethods notificationMethod) + /// + async Task ITelemetryService.ReportCompletionAsync(NotificationEvent notification, NotifyMethods notificationMethod, string message) { // NOTE: Feedback from "OpenKlant" will be passed to "OpenZaak" return await SendFeedbackToOpenZaakAsync( notification, - await SendFeedbackToOpenKlantAsync(notification, notificationMethod)); + await SendFeedbackToOpenKlantAsync(notification, notificationMethod, message)); } #region Helper methods /// /// Sends the completion feedback to "OpenKlant" Web service. /// - private async Task SendFeedbackToOpenKlantAsync(NotificationEvent notification, NotifyMethods notificationMethod) + private async Task SendFeedbackToOpenKlantAsync(NotificationEvent notification, NotifyMethods notificationMethod, string message) { // Prepare the body CaseStatus caseStatus = (await this._dataQuery.From(notification).GetCaseStatusesAsync()).LastStatus(); // TODO: Regarding performance, think whether we can store such data in a kind of register @@ -58,7 +58,7 @@ private async Task SendFeedbackToOpenKlantAsync(NotificationEvent { "bronorganisatie", notification.GetOrganizationId() }, { "registratiedatum", caseStatus.Created }, { "kanaal", $"{notificationMethod}" }, - { "tekst", string.Empty }, // TODO: To be filled + { "tekst", message }, { "initiatief", "gemeente" } }); HttpContent body = new StringContent(serialized, Encoding.UTF8, DefaultValues.Request.ContentType); diff --git a/EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/IFeedbackService.cs b/EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/ITelemetryService.cs similarity index 83% rename from EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/IFeedbackService.cs rename to EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/ITelemetryService.cs index ba78c5ba..07868533 100644 --- a/EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/IFeedbackService.cs +++ b/EventsHandler/Api/EventsHandler/Services/Telemetry/Interfaces/ITelemetryService.cs @@ -9,17 +9,18 @@ namespace EventsHandler.Services.Telemetry.Interfaces /// /// The service to collect and send feedback about the current business activities to the dedicated external API endpoint. /// - public interface IFeedbackService + public interface ITelemetryService { /// /// Reports to external API service that notification of type was sent to "Notify NL" service. /// /// The notification from "Notificatie API" Web service. /// The notification method. + /// The message to be passed along with the completion report. /// /// The callback URL prepared in response by "OpenKlant" web service. /// /// The completion status could not be sent. - internal Task ReportCompletionAsync(NotificationEvent notification, NotifyMethods notificationMethod); + internal Task ReportCompletionAsync(NotificationEvent notification, NotifyMethods notificationMethod, string message); } } \ No newline at end of file diff --git a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Services/DataSending/NotifySenderTests.cs b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Services/DataSending/NotifySenderTests.cs index c8aefe09..4aef37d5 100644 --- a/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Services/DataSending/NotifySenderTests.cs +++ b/EventsHandler/Tests/IntegrationTests/EventsHandler.IntegrationTests/Services/DataSending/NotifySenderTests.cs @@ -91,7 +91,7 @@ public async Task SendEmailAsync_Calls_NotificationClientMethod_SendEmailAsync() public async Task SendSmsAsync_Calls_IFeedbackServiceMethod_ReportCompletionAsync() { // Arrange - Mock mockedTelemetry = GetMockedTelemetry(); + Mock mockedTelemetry = GetMockedTelemetry(); this._testNotifySender = GetTestSendingService(mockedTelemetry: mockedTelemetry); NotificationEvent testNotification = GetTestNotification(); @@ -102,14 +102,15 @@ public async Task SendSmsAsync_Calls_IFeedbackServiceMethod_ReportCompletionAsyn mockedTelemetry.Verify(mock => mock.ReportCompletionAsync( It.IsAny(), - It.IsAny()), Times.Once); + It.IsAny(), + It.IsAny()), Times.Once); } [Test] public async Task SendEmailAsync_Calls_IFeedbackServiceMethod_ReportCompletionAsync() { // Arrange - Mock mockedTelemetry = GetMockedTelemetry(); + Mock mockedTelemetry = GetMockedTelemetry(); this._testNotifySender = GetTestSendingService(mockedTelemetry: mockedTelemetry); NotificationEvent testNotification = GetTestNotification(); @@ -119,7 +120,8 @@ public async Task SendEmailAsync_Calls_IFeedbackServiceMethod_ReportCompletionAs // Assert mockedTelemetry.Verify(mock => mock.ReportCompletionAsync( It.IsAny(), - It.IsAny()), Times.Once); + It.IsAny(), + It.IsAny()), Times.Once); } [Test] @@ -127,7 +129,7 @@ public async Task HttpClient_IsCached_AsExpected() { #region First phase of the test (HttpClient will be just created) // Arrange - Mock mockedTelemetry = GetMockedTelemetry(); + Mock mockedTelemetry = GetMockedTelemetry(); Mock firstMockedNotifyClient = GetMockedNotifyClient(); var firstMockedClientFactory = new Mock>(MockBehavior.Strict); firstMockedClientFactory @@ -178,7 +180,7 @@ public async Task HttpClient_IsCached_AsExpected() #region Helper methods private static ISendingService GetTestSendingService( Mock? mockedClient = null, - Mock? mockedTelemetry = null) + Mock? mockedTelemetry = null) { var mockedClientFactory = new Mock>(MockBehavior.Strict); mockedClientFactory.Setup(mock => mock.GetHttpClient(It.IsAny())) @@ -208,12 +210,13 @@ private static Mock GetMockedNotifyClient() return notificationClientMock; } - private static Mock GetMockedTelemetry() + private static Mock GetMockedTelemetry() { - var mockedTelemetry = new Mock(MockBehavior.Strict); + var mockedTelemetry = new Mock(MockBehavior.Strict); mockedTelemetry.Setup(mock => mock.ReportCompletionAsync( It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny())) .ReturnsAsync(string.Empty); return mockedTelemetry;