diff --git a/.env.example b/.env.example index f71dfbc..0c4c82f 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,7 @@ NbssMailboxId=X26ABC1 MeshApiBaseUrl=http://localhost:8700/messageexchange ASPNETCORE_ENVIRONMENT=Development FileDiscoveryTimerExpression=*/5 * * * * +MeshHandshakeTimerExpression=0 0 0 * * * # Midnight QueueUrl=http://127.0.0.1:10001 FileExtractQueueName=file-extract FileTransformQueueName=file-transform diff --git a/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs b/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs index 3470812..8715200 100644 --- a/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs +++ b/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs @@ -4,7 +4,8 @@ public class AppConfiguration : IFileDiscoveryFunctionConfiguration, IFileExtractFunctionConfiguration, IFileExtractQueueClientConfiguration, - IFileTransformQueueClientConfiguration + IFileTransformQueueClientConfiguration, + IMeshHandshakeFunctionConfiguration { public string NbssMeshMailboxId => GetRequired("NbssMailboxId"); diff --git a/src/ServiceLayer.Mesh/Configuration/IMeshHandshakeFunctionConfiguration.cs b/src/ServiceLayer.Mesh/Configuration/IMeshHandshakeFunctionConfiguration.cs new file mode 100644 index 0000000..340be37 --- /dev/null +++ b/src/ServiceLayer.Mesh/Configuration/IMeshHandshakeFunctionConfiguration.cs @@ -0,0 +1,7 @@ +namespace ServiceLayer.Mesh.Configuration +{ + public interface IMeshHandshakeFunctionConfiguration + { + string NbssMeshMailboxId { get; } + } +} diff --git a/src/ServiceLayer.Mesh/Functions/MeshHandshakeFunction.cs b/src/ServiceLayer.Mesh/Functions/MeshHandshakeFunction.cs index 2dac340..f6978e2 100644 --- a/src/ServiceLayer.Mesh/Functions/MeshHandshakeFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/MeshHandshakeFunction.cs @@ -1,6 +1,40 @@ -namespace ServiceLayer.Mesh.Functions; +using Azure; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using NHS.MESH.Client.Contracts.Services; +using ServiceLayer.Mesh.Configuration; -public class MeshHandshakeFunction +namespace ServiceLayer.Mesh.Functions { + public class MeshHandshakeFunction( + ILogger logger, + IMeshOperationService meshOperationService, + IMeshHandshakeFunctionConfiguration configuration) + { + [Function("MeshHandshakeFunction")] + public async Task Run([TimerTrigger("%MeshHandshakeTimerExpression%")] TimerInfo myTimer) + { + logger.LogInformation("{FunctionName} started", nameof(MeshHandshakeFunction)); + try + { + var response = await meshOperationService.MeshHandshakeAsync(configuration.NbssMeshMailboxId); + + if (response.IsSuccessful) + { + logger.LogInformation("Mesh handshake completed successfully for mailbox {MailboxId}. Status: {Status}", + response.Response.MailboxId, response.IsSuccessful); + } + else + { + logger.LogWarning("Mesh handshake failed for mailbox {MailboxId}. Error: {Error}", + configuration.NbssMeshMailboxId, response.Error); + } + } + catch (Exception ex) + { + logger.LogError(ex, "An error occurred during mesh handshake for mailbox {MailboxId}.", configuration.NbssMeshMailboxId); + } + } + } } diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index ce2cb7a..746d270 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -9,6 +9,8 @@ using Azure.Storage.Blobs; using ServiceLayer.Mesh.Configuration; using ServiceLayer.Mesh.Messaging; +using NHS.MESH.Client.Contracts.Services; +using NHS.MESH.Client.Services; var host = new HostBuilder() .ConfigureFunctionsWebApplication() @@ -63,6 +65,7 @@ services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); }); diff --git a/tests/ServiceLayer.Mesh.Tests/Functions/MeshHandshakeFunctionTests.cs b/tests/ServiceLayer.Mesh.Tests/Functions/MeshHandshakeFunctionTests.cs new file mode 100644 index 0000000..6c16e48 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/Functions/MeshHandshakeFunctionTests.cs @@ -0,0 +1,119 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using Moq; +using NHS.MESH.Client.Contracts.Services; +using NHS.MESH.Client.Models; +using ServiceLayer.Mesh.Configuration; +using ServiceLayer.Mesh.Functions; + +namespace ServiceLayer.Mesh.Tests.Functions; + +public class MeshHandshakeFunctionTests +{ + private readonly Mock> _loggerMock; + private readonly Mock _meshOperationServiceMock; + private readonly Mock _configurationMock; + private readonly MeshHandshakeFunction _function; + private readonly TimerInfo _timerInfo; + private const string TestMailboxId = "test-mailbox-123"; + + public MeshHandshakeFunctionTests() + { + _loggerMock = new Mock>(); + _meshOperationServiceMock = new Mock(); + _configurationMock = new Mock(); + _timerInfo = new TimerInfo(); + + _configurationMock.Setup(c => c.NbssMeshMailboxId).Returns(TestMailboxId); + _function = new MeshHandshakeFunction( + _loggerMock.Object, + _meshOperationServiceMock.Object, + _configurationMock.Object + ); + } + + [Fact] + public async Task Run_SuccessfulHandshake_LogsSuccessAndCompletion() + { + // Arrange + var successfulResponse = new MeshResponse + { + IsSuccessful = true, + Response = new HandshakeResponse { MailboxId = TestMailboxId } + }; + _meshOperationServiceMock + .Setup(s => s.MeshHandshakeAsync(TestMailboxId)) + .ReturnsAsync(successfulResponse); + + // Act + await _function.Run(_timerInfo); + + // Assert + _meshOperationServiceMock.Verify(s => s.MeshHandshakeAsync(TestMailboxId), Times.Once()); + VerifyLogMessage(LogLevel.Information, "MeshHandshakeFunction started"); + VerifyLogMessage(LogLevel.Information, "Mesh handshake completed successfully for mailbox"); + } + + [Fact] + public async Task Run_FailedHandshake_LogsWarningAndCompletion() + { + // Arrange + var failedResponse = new MeshResponse + { + IsSuccessful = false, + Error = new APIErrorResponse + { + ErrorDescription = "Authentication failed" + } + }; + _meshOperationServiceMock + .Setup(s => s.MeshHandshakeAsync(TestMailboxId)) + .ReturnsAsync(failedResponse); + + // Act + await _function.Run(_timerInfo); + + // Assert + _meshOperationServiceMock.Verify(s => s.MeshHandshakeAsync(TestMailboxId), Times.Once()); + VerifyLogMessage(LogLevel.Information, "MeshHandshakeFunction started"); + VerifyLogMessage(LogLevel.Warning, "Mesh handshake failed"); + } + + [Fact] + public async Task Run_ExceptionThrown_LogsErrorAndCompletion() + { + // Arrange + var expectedException = new InvalidOperationException("Connection failed"); + _meshOperationServiceMock + .Setup(s => s.MeshHandshakeAsync(TestMailboxId)) + .ThrowsAsync(expectedException); + + // Act + await _function.Run(_timerInfo); + + // Assert + _meshOperationServiceMock.Verify(s => s.MeshHandshakeAsync(TestMailboxId), Times.Once()); + VerifyLogMessage(LogLevel.Information, "MeshHandshakeFunction started"); + VerifyLogMessage(LogLevel.Error, "An error occurred during mesh handshake"); + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + expectedException, + It.IsAny>()), + Times.Once); + } + + private void VerifyLogMessage(LogLevel level, string expectedMessage) + { + _loggerMock.Verify( + x => x.Log( + level, + It.IsAny(), + It.Is((v, t) => (v.ToString() ?? string.Empty).Contains(expectedMessage)), + It.IsAny(), + It.IsAny>()), + Times.Once); + } +}